auto import from //depot/cupcake/@135843
diff --git a/media/java/android/drm/mobile1/DrmConstraintInfo.java b/media/java/android/drm/mobile1/DrmConstraintInfo.java
new file mode 100644
index 0000000..50ae8bd
--- /dev/null
+++ b/media/java/android/drm/mobile1/DrmConstraintInfo.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2007 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.drm.mobile1;
+
+import java.util.Date;
+
+/**
+ * This class provides interfaces to access the DRM constraint.
+ */
+public class DrmConstraintInfo {
+    /**
+     * The constraint of count.
+     */
+    private int count;
+
+    /**
+     * The constraint of start date.
+     */
+    private long startDate;
+
+    /**
+     * The constraint of end date.
+     */
+    private long endDate;
+
+    /**
+     * The constraint of interval.
+     */
+    private long interval;
+
+    /**
+     * Construct the DrmConstraint.
+     */
+    DrmConstraintInfo() {
+        count = -1;
+        startDate = -1;
+        endDate = -1;
+        interval = -1;
+    }
+
+    /**
+     * Get the count constraint.
+     *
+     * @return the count or -1 if no limit.
+     */
+    public int getCount() {
+        return count;
+    }
+
+    /**
+     * Get the start date constraint.
+     *
+     * @return the start date or null if no limit.
+     */
+    public Date getStartDate() {
+        if (startDate == -1)
+            return null;
+
+        return new Date(startDate);
+    }
+
+    /**
+     * Get the end date constraint.
+     *
+     * @return the end date or null if no limit.
+     */
+    public Date getEndDate() {
+        if (endDate == -1)
+            return null;
+
+        return new Date(endDate);
+    }
+
+    /**
+     * Get the Interval constraint.
+     *
+     * @return the interval or -1 if no limit.
+     */
+    public long getInterval() {
+        return interval;
+    }
+}
diff --git a/media/java/android/drm/mobile1/DrmException.java b/media/java/android/drm/mobile1/DrmException.java
new file mode 100644
index 0000000..7b06c92
--- /dev/null
+++ b/media/java/android/drm/mobile1/DrmException.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2007 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.drm.mobile1;
+
+import java.io.IOException;
+
+/**
+ * A DrmException is thrown to report errors specific to handle DRM content and rights.
+ */
+public class DrmException extends Exception
+{
+    // TODO: add more specific DRM error codes.
+    
+    private DrmException() {
+    }
+    
+    public DrmException(String message) {
+        super(message);
+    }
+}
diff --git a/media/java/android/drm/mobile1/DrmRawContent.java b/media/java/android/drm/mobile1/DrmRawContent.java
new file mode 100644
index 0000000..046b84a
--- /dev/null
+++ b/media/java/android/drm/mobile1/DrmRawContent.java
@@ -0,0 +1,464 @@
+/*
+ * Copyright (C) 2007 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.drm.mobile1;
+
+import java.io.*;
+
+/**
+ * This class provides interfaces to access the DRM raw content.
+ */
+public class DrmRawContent {
+    /**
+     * The "application/vnd.oma.drm.message" mime type.
+     */
+    public static final String DRM_MIMETYPE_MESSAGE_STRING = "application/vnd.oma.drm.message";
+
+    /**
+     * The "application/vnd.oma.drm.content" mime type.
+     */
+    public static final String DRM_MIMETYPE_CONTENT_STRING = "application/vnd.oma.drm.content";
+
+    /**
+     * The DRM delivery type: Forward-Lock
+     */
+    public static final int DRM_FORWARD_LOCK = 1;
+
+    /**
+     * The DRM delivery type: Combined Delivery
+     */
+    public static final int DRM_COMBINED_DELIVERY = 2;
+
+    /**
+     * The DRM delivery type: Separate Delivery
+     */
+    public static final int DRM_SEPARATE_DELIVERY = 3;
+
+    /**
+     * The DRM delivery type: Separate Delivery in DRM message
+     */
+    public static final int DRM_SEPARATE_DELIVERY_DM = 4;
+
+    /**
+     * The DRM media content length is unknown currently
+     */
+    public static final int DRM_UNKNOWN_DATA_LEN = -1;
+
+
+    /**
+     * The id of "application/vnd.oma.drm.message" mime type.
+     */
+    private static final int DRM_MIMETYPE_MESSAGE = 1;
+
+    /**
+     * The id of "application/vnd.oma.drm.content" mime type.
+     */
+    private static final int DRM_MIMETYPE_CONTENT = 2;
+
+    /**
+     * Successful operation.
+     */
+    private static final int JNI_DRM_SUCCESS = 0;
+
+    /**
+     * General failure.
+     */
+    private static final int JNI_DRM_FAILURE = -1;
+
+    /**
+     * Indicates the end of the DRM content is reached.
+     */
+    private static final int JNI_DRM_EOF = -2;
+
+    /**
+     * The media content length is unknown from native method
+     */
+    private static final int JNI_DRM_UNKNOWN_DATA_LEN = -3;
+
+    /**
+     * The member to save the original InputStream data.
+     */
+    private BufferedInputStream inData;
+
+    /**
+     * The member to save the original InputStream data length.
+     */
+    private int inDataLen;
+
+    /**
+     * The unique id to this DRM content. It will be initialized
+     * in constructor by native method. And it will not be changed
+     * after initialization.
+     */
+    private int id;
+
+    /**
+     * The rights issuer address of this DRM object.
+     */
+    private String rightsIssuer;
+
+    /**
+     * The media content type of this DRM object.
+     */
+    private String mediaType;
+
+    /**
+     * The delivery method type of this DRM object.
+     */
+    private int rawType;
+
+
+    /**
+     * Construct a DrmRawContent object.
+     *
+     * @param inRawdata     object of DRM raw data stream.
+     * @param len           the length of raw data can be read.
+     * @param mimeTypeStr   the mime type of the DRM content.
+     */
+    public DrmRawContent(InputStream inRawdata, int len, String mimeTypeStr) throws DrmException, IOException {
+        int mimeType;
+
+        id = -1;
+        inData = new BufferedInputStream(inRawdata, 1024);
+        inDataLen = len;
+
+        if (DRM_MIMETYPE_MESSAGE_STRING.equals(mimeTypeStr))
+            mimeType = DRM_MIMETYPE_MESSAGE;
+        else if (DRM_MIMETYPE_CONTENT_STRING.equals(mimeTypeStr))
+            mimeType = DRM_MIMETYPE_CONTENT;
+        else
+            throw new IllegalArgumentException("mimeType must be DRM_MIMETYPE_MESSAGE or DRM_MIMETYPE_CONTENT");
+
+        if (len <= 0)
+            throw new IllegalArgumentException("len must be > 0");
+
+        /* call native method to initialize this DRM content */
+        id = nativeConstructDrmContent(inData, inDataLen, mimeType);
+
+        if (JNI_DRM_FAILURE == id)
+            throw new DrmException("nativeConstructDrmContent() returned JNI_DRM_FAILURE");
+
+        /* init the rights issuer field. */
+        rightsIssuer = nativeGetRightsAddress();
+
+        /* init the raw content type. */
+        rawType = nativeGetDeliveryMethod();
+        if (JNI_DRM_FAILURE == rawType)
+            throw new DrmException("nativeGetDeliveryMethod() returned JNI_DRM_FAILURE");
+
+        /* init the media content type. */
+        mediaType = nativeGetContentType();
+        if (null == mediaType)
+            throw new DrmException("nativeGetContentType() returned null");
+    }
+
+    /**
+     * Get rights address from raw Seperate Delivery content.
+     *
+     * @return the string of the rights issuer address,
+     *         or null if no rights issuer.
+     */
+    public String getRightsAddress() {
+        return rightsIssuer;
+    }
+
+    /**
+     * Get the type of the raw DRM content.
+     *
+     * @return one of the following delivery type of this DRM content:
+     *              #DRM_FORWARD_LOCK
+     *              #DRM_COMBINED_DELIVERY
+     *              #DRM_SEPARATE_DELIVERY
+     *              #DRM_SEPARATE_DELIVERY_DM
+     */
+    public int getRawType() {
+        return rawType;
+    }
+
+    /**
+     * Get one InputStream object to read decrypted content.
+     *
+     * @param rights        the rights object contain decrypted key.
+     *
+     * @return the InputStream object of decrypted media content.
+     */
+    public InputStream getContentInputStream(DrmRights rights) {
+        if (null == rights)
+            throw new NullPointerException();
+
+        return new DrmInputStream(rights);
+    }
+
+    /**
+     * Get the type of the decrypted media content.
+     *
+     * @return the decrypted media content type of this DRM content.
+     */
+    public String getContentType() {
+        return mediaType;
+    }
+
+    /**
+     * Get the length of the decrypted media content.
+     *
+     * @param rights        the rights object contain decrypted key.
+     *
+     * @return the length of the decrypted media content.
+     *         #DRM_UNKNOWN_DATA_LEN if the length is unknown currently.
+     */
+    public int getContentLength(DrmRights rights) throws DrmException {
+        /**
+         * Because currently the media object associate with rights object
+         * has been handled in native logic, so here it is not need to deal
+         * the rights. But for the apps, it is mandatory for user to get
+         * the rights object before get the media content length.
+         */
+        if (null == rights)
+            throw new NullPointerException();
+
+        int mediaLen = nativeGetContentLength();
+
+        if (JNI_DRM_FAILURE == mediaLen)
+            throw new DrmException("nativeGetContentLength() returned JNI_DRM_FAILURE");
+
+        if (JNI_DRM_UNKNOWN_DATA_LEN == mediaLen)
+            return DRM_UNKNOWN_DATA_LEN;
+
+        return mediaLen;
+    }
+
+    /**
+     * This class provide a InputStream to the DRM media content.
+     */
+    class DrmInputStream extends InputStream
+    {
+        /**
+         * The flag to indicate whether this stream is closed or not.
+         */
+        private boolean isClosed;
+
+        /**
+         * The offset of this DRM content to be reset.
+         */
+        private int offset;
+
+        /**
+         * A byte of data to be readed.
+         */
+        private byte[] b;
+
+        /**
+         * Construct a DrmInputStream instance.
+         */
+        public DrmInputStream(DrmRights rights) {
+            /**
+             * Because currently the media object associate with rights object
+             * has been handled in native logic, so here it is not need to deal
+             * the rights. But for the apps, it is mandatory for user to get
+             * the rights object before get the media content data.
+             */
+
+            isClosed = false;
+            offset = 0;
+            b = new byte[1];
+        }
+
+        /* Non-javadoc
+         * @see java.io.InputStream#available()
+         */
+        public int available() throws IOException {
+            /* call native method to get this DRM decrypted media content length */
+            int len = nativeGetContentLength();
+
+            if (JNI_DRM_FAILURE == len)
+                throw new IOException();
+
+            /* if the length is unknown, just return 0 for available value */
+            if (JNI_DRM_UNKNOWN_DATA_LEN == len)
+                return 0;
+
+            int availableLen = len - offset;
+            if (availableLen < 0)
+                throw new IOException();
+
+            return availableLen;
+        }
+
+        /* Non-javadoc
+         * @see java.io.InputStream#read()
+         */
+        public int read() throws IOException {
+            int res;
+
+            res = read(b, 0, 1);
+
+            if (-1 == res)
+                return -1;
+
+            return b[0] & 0xff;
+        }
+
+        /* Non-javadoc
+         * @see java.io.InputStream#read(byte)
+         */
+        public int read(byte[] b) throws IOException {
+            return read(b, 0, b.length);
+        }
+
+        /* Non-javadoc
+         * @see java.io.InputStream#read(byte, int, int)
+         */
+        public int read(byte[] b, int off, int len) throws IOException {
+            if (null == b)
+                throw new NullPointerException();
+            if (off < 0 || len < 0 || off + len > b.length)
+                throw new IndexOutOfBoundsException();
+            if (true == isClosed)
+                throw new IOException();
+
+            if (0 == len)
+                return 0;
+
+            len = nativeReadContent(b, off, len, offset);
+
+            if (JNI_DRM_FAILURE == len)
+                throw new IOException();
+            else if (JNI_DRM_EOF == len)
+                return -1;
+
+            offset += len;
+
+            return len;
+        }
+
+        /* Non-javadoc
+         * @see java.io.InputStream#markSupported()
+         */
+        public boolean markSupported() {
+            return false;
+        }
+
+        /* Non-javadoc
+         * @see java.io.InputStream#mark(int)
+         */
+        public void mark(int readlimit) {
+        }
+
+        /* Non-javadoc
+         * @see java.io.InputStream#reset()
+         */
+        public void reset() throws IOException {
+            throw new IOException();
+        }
+
+        /* Non-javadoc
+         * @see java.io.InputStream#skip()
+         */
+        public long skip(long n) throws IOException {
+            return 0;
+        }
+
+        /* Non-javadoc
+         * @see java.io.InputStream#close()
+         */
+        public void close() {
+            isClosed = true;
+        }
+    }
+
+    /**
+     * native method: construct a DRM content according the mime type.
+     *
+     * @param data      input DRM content data to be parsed.
+     * @param len       the length of the data.
+     * @param mimeType  the mime type of this DRM content. the value of this field includes:
+     *                      #DRM_MIMETYPE_MESSAGE
+     *                      #DRM_MIMETYPE_CONTENT
+     *
+     * @return #the id of the DRM content if succeed.
+     *         #JNI_DRM_FAILURE if fail.
+     */
+    private native int nativeConstructDrmContent(InputStream data, int len, int mimeType);
+
+    /**
+     * native method: get this DRM content rights issuer.
+     *
+     * @return the address of rights issuer if in case of separate delivery.
+     *         null if not separete delivery, or otherwise.
+     */
+    private native String nativeGetRightsAddress();
+
+    /**
+     * native method: get this DRM content delivery type.
+     *
+     * @return the delivery method, the value may be one of the following:
+     *              #DRM_FORWARD_LOCK
+     *              #DRM_COMBINED_DELIVERY
+     *              #DRM_SEPARATE_DELIVERY
+     *              #DRM_SEPARATE_DELIVERY_DM
+     *         #JNI_DRM_FAILURE if fail.
+     */
+    private native int nativeGetDeliveryMethod();
+
+    /**
+     * native method: get a piece of media content data.
+     *
+     * @param buf       the buffer to save DRM media content data.
+     * @param bufOff    the offset of the buffer to start to save data.
+     * @param len       the number of byte to read.
+     * @param mediaOff  the offset of the media content data to start to read.
+     *
+     * @return the length of the media content data has been read.
+     *         #JNI_DRM_EOF if reach to end of the media content.
+     *         #JNI_DRM_FAILURE if fail.
+     */
+    private native int nativeReadContent(byte[] buf, int bufOff, int len, int mediaOff);
+
+    /**
+     * native method: get this DRM content type.
+     *
+     * @return the decrypted media content type.
+     *         null if fail.
+     */
+    private native String nativeGetContentType();
+
+    /**
+     * native method: get this DRM decrypted media content length.
+     *
+     * @return the length of decrypted media content.
+     *         #JNI_DRM_FAILURE if fail.
+     *         #JNI_DRM_UNKNOWN_DATA_LEN if the length is unknown currently.
+     */
+    private native int nativeGetContentLength();
+
+    /**
+     * The finalizer of the DRMRawContent. Do some cleanup.
+     */
+    protected native void finalize();
+
+
+    /**
+     * Load the shared library to link the native methods.
+     */
+    static {
+        try {
+            System.loadLibrary("drm1_jni");
+        }
+        catch (UnsatisfiedLinkError ule) {
+            System.err.println("WARNING: Could not load libdrm1_jni.so");
+        }
+    }
+}
diff --git a/media/java/android/drm/mobile1/DrmRights.java b/media/java/android/drm/mobile1/DrmRights.java
new file mode 100644
index 0000000..bcccb6a
--- /dev/null
+++ b/media/java/android/drm/mobile1/DrmRights.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2007 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.drm.mobile1;
+
+/**
+ * This class provides interfaces to access the DRM rights.
+ */
+public class DrmRights {
+    /**
+     * The DRM permission of play.
+     */
+    public static final int DRM_PERMISSION_PLAY = 1;
+
+    /**
+     * The DRM permission of display.
+     */
+    public static final int DRM_PERMISSION_DISPLAY = 2;
+
+    /**
+     * The DRM permission of execute.
+     */
+    public static final int DRM_PERMISSION_EXECUTE = 3;
+
+    /**
+     * The DRM permission of print.
+     */
+    public static final int DRM_PERMISSION_PRINT = 4;
+
+    /**
+     * Successful operation.
+     */
+    private static final int JNI_DRM_SUCCESS = 0;
+
+    /**
+     * General failure.
+     */
+    private static final int JNI_DRM_FAILURE = -1;
+
+    /**
+     * The uid of this rights object.
+     */
+    private String roId = "";
+
+
+    /**
+     * Construct the DrmRights.
+     */
+    public DrmRights() {
+    }
+
+    /**
+     * Get the constraint of the given permission on this rights object.
+     *
+     * @param permission    the given permission.
+     *
+     * @return a DrmConstraint instance.
+     */
+    public DrmConstraintInfo getConstraint(int permission) {
+        DrmConstraintInfo c = new DrmConstraintInfo();
+
+        /* call native method to get latest constraint information */
+        int res = nativeGetConstraintInfo(permission, c);
+
+        if (JNI_DRM_FAILURE == res)
+            return null;
+
+        return c;
+    }
+
+    /**
+     * Consume the rights of the given permission.
+     *
+     * @param permission    the given permission.
+     *
+     * @return true if consume success.
+     *         false if consume failure.
+     */
+    public boolean consumeRights(int permission) {
+        /* call native method to consume and update rights */
+        int res = nativeConsumeRights(permission);
+
+        if (JNI_DRM_FAILURE == res)
+            return false;
+
+        return true;
+    }
+
+
+    /**
+     * native method: get the constraint information of the given permission.
+     *
+     * @param permission    the given permission.
+     * @param constraint    the instance of constraint.
+     *
+     * @return #JNI_DRM_SUCCESS if succeed.
+     *         #JNI_DRM_FAILURE if fail.
+     */
+    private native int nativeGetConstraintInfo(int permission, DrmConstraintInfo constraint);
+
+    /**
+     * native method: consume the rights of the given permission.
+     *
+     * @param permission    the given permission.
+     *
+     * @return #JNI_DRM_SUCCESS if succeed.
+     *         #JNI_DRM_FAILURE if fail.
+     */
+    private native int nativeConsumeRights(int permission);
+
+
+    /**
+     * Load the shared library to link the native methods.
+     */
+    static {
+        try {
+            System.loadLibrary("drm1_jni");
+        }
+        catch (UnsatisfiedLinkError ule) {
+            System.err.println("WARNING: Could not load libdrm1_jni.so");
+        }
+    }
+}
diff --git a/media/java/android/drm/mobile1/DrmRightsManager.java b/media/java/android/drm/mobile1/DrmRightsManager.java
new file mode 100644
index 0000000..1bc36ec
--- /dev/null
+++ b/media/java/android/drm/mobile1/DrmRightsManager.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2007 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.drm.mobile1;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * This class provides interfaces to access the DRM right manager.
+ */
+public class DrmRightsManager {
+    /**
+     * The "application/vnd.oma.drm.rights+xml" mime type.
+     */
+    public static final String DRM_MIMETYPE_RIGHTS_XML_STRING = "application/vnd.oma.drm.rights+xml";
+
+    /**
+     * The "application/vnd.oma.drm.rights+wbxml" mime type.
+     */
+    public static final String DRM_MIMETYPE_RIGHTS_WBXML_STRING = "application/vnd.oma.drm.rights+wbxml";
+
+    /**
+     * The id of "application/vnd.oma.drm.rights+xml" mime type.
+     */
+    private static final int DRM_MIMETYPE_RIGHTS_XML = 3;
+
+    /**
+     * The id of "application/vnd.oma.drm.rights+wbxml" mime type.
+     */
+    private static final int DRM_MIMETYPE_RIGHTS_WBXML = 4;
+
+    /**
+     * The id of "application/vnd.oma.drm.message" mime type.
+     */
+    private static final int DRM_MIMETYPE_MESSAGE = 1;
+
+    /**
+     * Successful operation.
+     */
+    private static final int JNI_DRM_SUCCESS = 0;
+
+    /**
+     * General failure.
+     */
+    private static final int JNI_DRM_FAILURE = -1;
+
+    /**
+     * The instance of the rights manager.
+     */
+    private static DrmRightsManager singleton = null;
+
+
+    /**
+     * Construct a DrmRightsManager
+     */
+    protected DrmRightsManager() {
+    }
+
+    /**
+     * Get the DrmRightsManager instance.
+     *
+     * @return the instance of DrmRightsManager.
+     */
+    public static synchronized DrmRightsManager getInstance() {
+        if (singleton == null) {
+            singleton = new DrmRightsManager();
+        }
+
+        return singleton;
+    }
+
+    /**
+     * Install one DRM rights and return one instance of DrmRights.
+     *
+     * @param rightsData    raw rights data.
+     * @param mimeTypeStr   the mime type of the rights object.
+     *
+     * @return the instance of the installed DrmRights.
+     */
+    public synchronized DrmRights installRights(InputStream rightsData, int len, String mimeTypeStr) throws DrmException, IOException {
+        int mimeType = 0;
+
+        if (DRM_MIMETYPE_RIGHTS_XML_STRING.equals(mimeTypeStr))
+            mimeType = DRM_MIMETYPE_RIGHTS_XML;
+        else if (DRM_MIMETYPE_RIGHTS_WBXML_STRING.equals(mimeTypeStr))
+            mimeType = DRM_MIMETYPE_RIGHTS_WBXML;
+        else if (DrmRawContent.DRM_MIMETYPE_MESSAGE_STRING.equals(mimeTypeStr))
+            mimeType = DRM_MIMETYPE_MESSAGE;
+        else
+            throw new IllegalArgumentException("mimeType must be DRM_MIMETYPE_RIGHTS_XML or DRM_MIMETYPE_RIGHTS_WBXML or DRM_MIMETYPE_MESSAGE");
+
+        if (len <= 0)
+            return null;
+
+        DrmRights rights = new DrmRights();
+
+        /* call native method to install this rights object. */
+        int res = nativeInstallDrmRights(rightsData, len, mimeType, rights);
+
+        if (JNI_DRM_FAILURE == res)
+            throw new DrmException("nativeInstallDrmRights() returned JNI_DRM_FAILURE");
+
+        return rights;
+    }
+
+    /**
+     * Query DRM rights of specified DRM raw content.
+     *
+     * @param content       raw content object.
+     *
+     * @return the instance of DrmRights, or null if there is no rights.
+     */
+    public synchronized DrmRights queryRights(DrmRawContent content) {
+        DrmRights rights = new DrmRights();
+
+        /* call native method to query the rights */
+        int res = nativeQueryRights(content, rights);
+
+        if (JNI_DRM_FAILURE == res)
+            return null;
+
+        return rights;
+    }
+
+    /**
+     * Get the list of all DRM rights saved in local client.
+     *
+     * @return the list of all the rights object.
+     */
+    public synchronized List getRightsList() {
+        List rightsList = new ArrayList();
+
+        /* call native method to get how many rights object in current agent */
+        int num = nativeGetNumOfRights();
+
+        if (JNI_DRM_FAILURE == num)
+            return null;
+
+        if (num > 0) {
+            DrmRights[] rightsArray = new DrmRights[num];
+            int i;
+
+            for (i = 0; i < num; i++)
+                rightsArray[i] = new DrmRights();
+
+            /* call native method to get all the rights information */
+            num = nativeGetRightsList(rightsArray, num);
+
+            if (JNI_DRM_FAILURE == num)
+                return null;
+
+            /* add all rights informations to ArrayList */
+            for (i = 0; i < num; i++)
+                rightsList.add(rightsArray[i]);
+        }
+
+        return rightsList;
+    }
+
+    /**
+     * Delete the specified DRM rights object.
+     *
+     * @param rights    the specified rights object to be deleted.
+     */
+    public synchronized void deleteRights(DrmRights rights) {
+        /* call native method to delete the specified rights object */
+        int res = nativeDeleteRights(rights);
+
+        if (JNI_DRM_FAILURE == res)
+            return;
+    }
+
+
+    /**
+     * native method: install rights object to local client.
+     *
+     * @param data      input DRM rights object data to be installed.
+     * @param len       the length of the data.
+     * @param mimeType  the mime type of this DRM rights object. the value of this field includes:
+     *                      #DRM_MIMETYPE_RIGHTS_XML
+     *                      #DRM_MIMETYPE_RIGHTS_WBXML
+     * @parma rights    the instance of DRMRights to be filled.
+     *
+     * @return #JNI_DRM_SUCCESS if succeed.
+     *         #JNI_DRM_FAILURE if fail.
+     */
+    private native int nativeInstallDrmRights(InputStream data, int len, int mimeType, DrmRights rights);
+
+    /**
+     * native method: query the given DRM content's rights object.
+     *
+     * @param content   the given DRM content.
+     * @param rights    the instance of rights to set if have.
+     *
+     * @return #JNI_DRM_SUCCESS if succeed.
+     *         #JNI_DRM_FAILURE if fail.
+     */
+    private native int nativeQueryRights(DrmRawContent content, DrmRights rights);
+
+    /**
+     * native method: get how many rights object in current DRM agent.
+     *
+     * @return the number of the rights object.
+     *         #JNI_DRM_FAILURE if fail.
+     */
+    private native int nativeGetNumOfRights();
+
+    /**
+     * native method: get all the rights object in current local agent.
+     *
+     * @param rights    the array instance of rights object.
+     * @param numRights how many rights can be saved.
+     *
+     * @return the number of the rights object has been gotten.
+     *         #JNI_DRM_FAILURE if fail.
+     */
+    private native int nativeGetRightsList(DrmRights[] rights, int numRights);
+
+    /**
+     * native method: delete a specified rights object.
+     *
+     * @param rights    the specified rights object to be deleted.
+     *
+     * @return #JNI_DRM_SUCCESS if succeed.
+     *         #JNI_DRM_FAILURE if fail.
+     */
+    private native int nativeDeleteRights(DrmRights rights);
+
+
+    /**
+     * Load the shared library to link the native methods.
+     */
+    static {
+        try {
+            System.loadLibrary("drm1_jni");
+        }
+        catch (UnsatisfiedLinkError ule) {
+            System.err.println("WARNING: Could not load libdrm1_jni.so");
+        }
+    }
+}
diff --git a/media/java/android/drm/mobile1/package.html b/media/java/android/drm/mobile1/package.html
new file mode 100644
index 0000000..1c9bf9d
--- /dev/null
+++ b/media/java/android/drm/mobile1/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+    {@hide}
+</body>
+</html>
diff --git a/media/java/android/media/AmrInputStream.java b/media/java/android/media/AmrInputStream.java
new file mode 100644
index 0000000..d40ca5aa
--- /dev/null
+++ b/media/java/android/media/AmrInputStream.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2008 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.media;
+
+import android.util.Config;
+import android.util.Log;
+
+import java.io.InputStream;
+import java.io.IOException;
+
+
+/**
+ * AmrInputStream
+ * @hide
+ */
+public final class AmrInputStream extends InputStream
+{    
+    static {
+        System.loadLibrary("media_jni");
+    }
+    
+    private final static String TAG = "AmrInputStream";
+    
+    // frame is 20 msec at 8.000 khz
+    private final static int SAMPLES_PER_FRAME = 8000 * 20 / 1000;
+    
+    // pcm input stream
+    private InputStream mInputStream;
+    
+    // native handle
+    private int mGae;
+    
+    // result amr stream
+    private byte[] mBuf = new byte[SAMPLES_PER_FRAME * 2];
+    private int mBufIn = 0;
+    private int mBufOut = 0;
+    
+    // helper for bytewise read()
+    private byte[] mOneByte = new byte[1];
+    
+    /**
+     * Create a new AmrInputStream, which converts 16 bit PCM to AMR
+     * @param inputStream InputStream containing 16 bit PCM.
+     */
+    public AmrInputStream(InputStream inputStream) {
+        mInputStream = inputStream;
+        mGae = GsmAmrEncoderNew();
+        GsmAmrEncoderInitialize(mGae);
+    }
+
+    @Override
+    public int read() throws IOException {
+        int rtn = read(mOneByte, 0, 1);
+        return rtn == 1 ? (0xff & mOneByte[0]) : -1;
+    }
+    
+    @Override
+    public int read(byte[] b) throws IOException {
+        return read(b, 0, b.length);
+    }
+
+    @Override
+    public int read(byte[] b, int offset, int length) throws IOException {
+        if (mGae == 0) throw new IllegalStateException("not open");
+        
+        // local buffer of amr encoded audio empty
+        if (mBufOut >= mBufIn) {
+            // reset the buffer
+            mBufOut = 0;
+            mBufIn = 0;
+            
+            // fetch a 20 msec frame of pcm
+            for (int i = 0; i < SAMPLES_PER_FRAME * 2; ) {
+                int n = mInputStream.read(mBuf, i, SAMPLES_PER_FRAME * 2 - i);
+                if (n == -1) return -1;
+                i += n;
+            }
+            
+            // encode it
+            mBufIn = GsmAmrEncoderEncode(mGae, mBuf, 0, mBuf, 0);
+        }
+        
+        // return encoded audio to user
+        if (length > mBufIn - mBufOut) length = mBufIn - mBufOut;
+        System.arraycopy(mBuf, mBufOut, b, offset, length);
+        mBufOut += length;
+        
+        return length;
+    }
+
+    @Override
+    public void close() throws IOException {
+        try {
+            if (mInputStream != null) mInputStream.close();
+        } finally {
+            mInputStream = null;
+            try {
+                if (mGae != 0) GsmAmrEncoderCleanup(mGae);
+            } finally {
+                try {
+                    if (mGae != 0) GsmAmrEncoderDelete(mGae);
+                } finally {
+                    mGae = 0;
+                }
+            }
+        }
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        if (mGae != 0) {
+            close();
+            throw new IllegalStateException("someone forgot to close AmrInputStream");
+        }
+    }
+    
+    //
+    // AudioRecord JNI interface
+    //
+    private static native int GsmAmrEncoderNew();
+    private static native void GsmAmrEncoderInitialize(int gae);
+    private static native int GsmAmrEncoderEncode(int gae,
+            byte[] pcm, int pcmOffset, byte[] amr, int amrOffset) throws IOException;
+    private static native void GsmAmrEncoderCleanup(int gae);
+    private static native void GsmAmrEncoderDelete(int gae);
+
+}
diff --git a/media/java/android/media/AsyncPlayer.java b/media/java/android/media/AsyncPlayer.java
new file mode 100644
index 0000000..35f0409
--- /dev/null
+++ b/media/java/android/media/AsyncPlayer.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2008 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.media;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.PowerManager;
+import android.util.Log;
+
+import java.io.IOException;
+import java.lang.IllegalStateException;
+
+/**
+ * Plays a series of audio URIs, but does all the hard work on another thread
+ * so that any slowness with preparing or loading doesn't block the calling thread.
+ */
+public class AsyncPlayer {
+    private static final int PLAY = 1;
+    private static final int STOP = 2;
+
+    private static final class Command {
+        Command next;
+        int code;
+        Context context;
+        Uri uri;
+        boolean looping;
+        int stream;
+
+        public String toString() {
+            return "{ code=" + code + " looping=" + looping + " stream=" + stream
+                    + " uri=" + uri + " }";
+        }
+    }
+
+    private final class Thread extends java.lang.Thread {
+        Thread() {
+            super("AsyncPlayer-" + mTag);
+        }
+
+        public void run() {
+            while (true) {
+                Command cmd = null;
+
+                synchronized (mLock) {
+                    if (mHead != null) {
+                        cmd = mHead;
+                        mHead = cmd.next;
+                        if (mTail == cmd) {
+                            mTail = null;
+                        }
+                    }
+                }
+
+                switch (cmd.code) {
+                case PLAY:
+                    try {
+                        // Preparing can be slow, so if there is something else
+                        // is playing, let it continue until we're done, so there
+                        // is less of a glitch.
+                        MediaPlayer player = new MediaPlayer();
+                        player.setAudioStreamType(cmd.stream);
+                        player.setDataSource(cmd.context, cmd.uri);
+                        player.setLooping(cmd.looping);
+                        player.prepare();
+                        player.start();
+                        if (mPlayer != null) {
+                            mPlayer.release();
+                        }
+                        mPlayer = player;
+                    }
+                    catch (IOException e) {
+                        Log.w(mTag, "error loading sound for " + cmd.uri, e);
+                    } catch (IllegalStateException e) {
+                        Log.w(mTag, "IllegalStateException (content provider died?) " + cmd.uri, e);
+                    }
+                    break;
+                case STOP:
+                    if (mPlayer != null) {
+                        mPlayer.stop();
+                        mPlayer.release();
+                        mPlayer = null;
+                    } else {
+                        Log.w(mTag, "STOP command without a player");
+                    }
+                    break;
+                }
+
+                synchronized (mLock) {
+                    if (mHead == null) {
+                        // nothing left to do, quit
+                        // doing this check after we're done prevents the case where they
+                        // added it during the operation from spawning two threads and
+                        // trying to do them in parallel.
+                        mThread = null;
+                        releaseWakeLock();
+                        return;
+                    }
+                }
+            }
+        }
+    }
+
+    private String mTag;
+    private Command mHead;
+    private Command mTail;
+    private Thread mThread;
+    private MediaPlayer mPlayer;
+    private Object mLock = new Object();
+    private PowerManager.WakeLock mWakeLock;
+
+    // The current state according to the caller.  Reality lags behind
+    // because of the asynchronous nature of this class.
+    private int mState = STOP;
+
+    /**
+     * Construct an AsyncPlayer object.
+     *
+     * @param tag a string to use for debugging
+     */
+    public AsyncPlayer(String tag) {
+        if (tag != null) {
+            mTag = tag;
+        } else {
+            mTag = "AsyncPlayer";
+        }
+    }
+
+    /**
+     * Start playing the sound.  It will actually start playing at some
+     * point in the future.  There are no guarantees about latency here.
+     * Calling this before another audio file is done playing will stop
+     * that one and start the new one.
+     *
+     * @param context Your application's context.
+     * @param uri The URI to play.  (see {@link MediaPlayer#setDataSource(Context, Uri)})
+     * @param looping Whether the audio should loop forever.  
+     *          (see {@link MediaPlayer#setLooping(boolean)})
+     * @param stream the AudioStream to use.
+     *          (see {@link MediaPlayer#setAudioStreamType(int)})
+     */
+    public void play(Context context, Uri uri, boolean looping, int stream) {
+        Command cmd = new Command();
+        cmd.code = PLAY;
+        cmd.context = context;
+        cmd.uri = uri;
+        cmd.looping = looping;
+        cmd.stream = stream;
+        synchronized (mLock) {
+            enqueueLocked(cmd);
+            mState = PLAY;
+        }
+    }
+    
+    /**
+     * Stop a previously played sound.  It can't be played again or unpaused
+     * at this point.  Calling this multiple times has no ill effects.
+     */
+    public void stop() {
+        synchronized (mLock) {
+            // This check allows stop to be called multiple times without starting
+            // a thread that ends up doing nothing.
+            if (mState != STOP) {
+                Command cmd = new Command();
+                cmd.code = STOP;
+                enqueueLocked(cmd);
+                mState = STOP;
+            }
+        }
+    }
+
+    private void enqueueLocked(Command cmd) {
+        if (mTail == null) {
+            mHead = cmd;
+        } else {
+            mTail.next = cmd;
+        }
+        mTail = cmd;
+        if (mThread == null) {
+            acquireWakeLock();
+            mThread = new Thread();
+            mThread.start();
+        }
+    }
+
+    /**
+     * We want to hold a wake lock while we do the prepare and play.  The stop probably is
+     * optional, but it won't hurt to have it too.  The problem is that if you start a sound
+     * while you're holding a wake lock (e.g. an alarm starting a notification), you want the
+     * sound to play, but if the CPU turns off before mThread gets to work, it won't.  The
+     * simplest way to deal with this is to make it so there is a wake lock held while the
+     * thread is starting or running.  You're going to need the WAKE_LOCK permission if you're
+     * going to call this.
+     *
+     * This must be called before the first time play is called.
+     *
+     * @hide
+     */
+    public void setUsesWakeLock(Context context) {
+        if (mWakeLock != null || mThread != null) {
+            // if either of these has happened, we've already played something.
+            // and our releases will be out of sync.
+            throw new RuntimeException("assertion failed mWakeLock=" + mWakeLock
+                    + " mThread=" + mThread);
+        }
+        PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
+        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, mTag);
+    }
+
+    private void acquireWakeLock() {
+        if (mWakeLock != null) {
+            mWakeLock.acquire();
+        }
+    }
+
+    private void releaseWakeLock() {
+        if (mWakeLock != null) {
+            mWakeLock.release();
+        }
+    }
+}
+
diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java
new file mode 100644
index 0000000..0732b61
--- /dev/null
+++ b/media/java/android/media/AudioFormat.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2008 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.media;
+
+/**
+ * The AudioFormat class is used to access a number of audio format and
+ * channel configuration constants. They are for instance used
+ * in __link AudioTrack} and __link AudioRecord}.
+ * 
+ */
+public class AudioFormat {
+    
+    //---------------------------------------------------------
+    // Constants
+    //--------------------
+    /** Invalid audio data format */
+    public static final int ENCODING_INVALID = 0;
+    /** Default audio data format */
+    public static final int ENCODING_DEFAULT = 1;
+    /** Audio data format: PCM 16 bit per sample */
+    public static final int ENCODING_PCM_16BIT = 2; // accessed by native code
+    /** Audio data format: PCM 8 bit per sample */
+    public static final int ENCODING_PCM_8BIT = 3;  // accessed by native code
+
+    /** Invalid audio channel configuration */
+    public static final int CHANNEL_CONFIGURATION_INVALID   = 0;
+    /** Default audio channel configuration */
+    public static final int CHANNEL_CONFIGURATION_DEFAULT   = 1;
+    /** Mono audio configuration */
+    public static final int CHANNEL_CONFIGURATION_MONO      = 2;
+    /** Stereo (2 channel) audio configuration */
+    public static final int CHANNEL_CONFIGURATION_STEREO    = 3;
+
+}
+
+
+
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
new file mode 100644
index 0000000..077d016
--- /dev/null
+++ b/media/java/android/media/AudioManager.java
@@ -0,0 +1,1086 @@
+/*
+ * Copyright (C) 2007 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.media;
+
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.provider.Settings;
+import android.util.Log;
+
+/**
+ * AudioManager provides access to volume and ringer mode control.
+ * <p>
+ * Use <code>Context.getSystemService(Context.AUDIO_SERVICE)</code> to get
+ * an instance of this class.
+ */
+public class AudioManager {
+
+    private final Context mContext;
+    private final Handler mHandler;
+
+    // used to listen for updates to the sound effects settings so we don't
+    // poll it for every UI sound
+    private ContentObserver mContentObserver;
+
+
+    private static String TAG = "AudioManager";
+    private static boolean DEBUG = false;
+    private static boolean localLOGV = DEBUG || android.util.Config.LOGV;
+
+    /**
+     * Broadcast intent, a hint for applications that audio is about to become
+     * 'noisy' due to a change in audio outputs. For example, this intent may
+     * be sent when a wired headset is unplugged, or when an A2DP audio
+     * sink is disconnected, and the audio system is about to automatically
+     * switch audio route to the speaker. Applications that are controlling
+     * audio streams may consider pausing, reducing volume or some other action
+     * on receipt of this intent so as not to surprise the user with audio
+     * from the speaker.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_AUDIO_BECOMING_NOISY = "android.media.AUDIO_BECOMING_NOISY";
+
+    /**
+     * Sticky broadcast intent action indicating that the ringer mode has
+     * changed. Includes the new ringer mode.
+     *
+     * @see #EXTRA_RINGER_MODE
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String RINGER_MODE_CHANGED_ACTION = "android.media.RINGER_MODE_CHANGED";
+
+    /**
+     * The new ringer mode.
+     *
+     * @see #RINGER_MODE_CHANGED_ACTION
+     * @see #RINGER_MODE_NORMAL
+     * @see #RINGER_MODE_SILENT
+     * @see #RINGER_MODE_VIBRATE
+     */
+    public static final String EXTRA_RINGER_MODE = "android.media.EXTRA_RINGER_MODE";
+
+    /**
+     * Broadcast intent action indicating that the vibrate setting has
+     * changed. Includes the vibrate type and its new setting.
+     *
+     * @see #EXTRA_VIBRATE_TYPE
+     * @see #EXTRA_VIBRATE_SETTING
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String VIBRATE_SETTING_CHANGED_ACTION = "android.media.VIBRATE_SETTING_CHANGED";
+
+    /**
+     * @hide Broadcast intent when the volume for a particular stream type changes.
+     * Includes the stream and the new volume
+     *
+     * @see #EXTRA_VOLUME_STREAM_TYPE
+     * @see #EXTRA_VOLUME_STREAM_VALUE
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String VOLUME_CHANGED_ACTION = "android.media.VOLUME_CHANGED_ACTION";
+
+    /**
+     * The new vibrate setting for a particular type.
+     *
+     * @see #VIBRATE_SETTING_CHANGED_ACTION
+     * @see #EXTRA_VIBRATE_TYPE
+     * @see #VIBRATE_SETTING_ON
+     * @see #VIBRATE_SETTING_OFF
+     * @see #VIBRATE_SETTING_ONLY_SILENT
+     */
+    public static final String EXTRA_VIBRATE_SETTING = "android.media.EXTRA_VIBRATE_SETTING";
+
+    /**
+     * The vibrate type whose setting has changed.
+     *
+     * @see #VIBRATE_SETTING_CHANGED_ACTION
+     * @see #VIBRATE_TYPE_NOTIFICATION
+     * @see #VIBRATE_TYPE_RINGER
+     */
+    public static final String EXTRA_VIBRATE_TYPE = "android.media.EXTRA_VIBRATE_TYPE";
+
+    /**
+     * @hide The stream type for the volume changed intent.
+     */
+    public static final String EXTRA_VOLUME_STREAM_TYPE = "android.media.EXTRA_VOLUME_STREAM_TYPE";
+
+    /**
+     * @hide The volume associated with the stream for the volume changed intent.
+     */
+    public static final String EXTRA_VOLUME_STREAM_VALUE =
+        "android.media.EXTRA_VOLUME_STREAM_VALUE";
+
+    /** The audio stream for phone calls */
+    public static final int STREAM_VOICE_CALL = AudioSystem.STREAM_VOICE_CALL;
+    /** The audio stream for system sounds */
+    public static final int STREAM_SYSTEM = AudioSystem.STREAM_SYSTEM;
+    /** The audio stream for the phone ring */
+    public static final int STREAM_RING = AudioSystem.STREAM_RING;
+    /** The audio stream for music playback */
+    public static final int STREAM_MUSIC = AudioSystem.STREAM_MUSIC;
+    /** The audio stream for alarms */
+    public static final int STREAM_ALARM = AudioSystem.STREAM_ALARM;
+    /** The audio stream for notifications */
+    public static final int STREAM_NOTIFICATION = AudioSystem.STREAM_NOTIFICATION;
+    /** @hide The audio stream for phone calls when connected to bluetooth */
+    public static final int STREAM_BLUETOOTH_SCO = AudioSystem.STREAM_BLUETOOTH_SCO;
+    /** Number of audio streams */
+    /**
+     * @deprecated Use AudioSystem.getNumStreamTypes() instead
+     */
+    public static final int NUM_STREAMS = AudioSystem.NUM_STREAMS;
+
+
+    /** @hide Maximum volume index values for audio streams */
+    public static final int[] MAX_STREAM_VOLUME = new int[] {
+        6,  // STREAM_VOICE_CALL
+        8,  // STREAM_SYSTEM
+        8,  // STREAM_RING
+        16, // STREAM_MUSIC
+        8,  // STREAM_ALARM
+        8,  // STREAM_NOTIFICATION
+        15, // STREAM_BLUETOOTH_SCO
+    };
+
+    /**  @hide Default volume index values for audio streams */
+    public static final int[] DEFAULT_STREAM_VOLUME = new int[] {
+        4,  // STREAM_VOICE_CALL
+        5,  // STREAM_SYSTEM
+        5,  // STREAM_RING
+        11, // STREAM_MUSIC
+        6,  // STREAM_ALARM
+        5,  // STREAM_NOTIFICATION
+        7   // STREAM_BLUETOOTH_SCO
+    };
+
+    /**
+     * Increase the ringer volume.
+     *
+     * @see #adjustVolume(int, int)
+     * @see #adjustStreamVolume(int, int, int)
+     */
+    public static final int ADJUST_RAISE = 1;
+
+    /**
+     * Decrease the ringer volume.
+     *
+     * @see #adjustVolume(int, int)
+     * @see #adjustStreamVolume(int, int, int)
+     */
+    public static final int ADJUST_LOWER = -1;
+
+    /**
+     * Maintain the previous ringer volume. This may be useful when needing to
+     * show the volume toast without actually modifying the volume.
+     *
+     * @see #adjustVolume(int, int)
+     * @see #adjustStreamVolume(int, int, int)
+     */
+    public static final int ADJUST_SAME = 0;
+
+    // Flags should be powers of 2!
+
+    /**
+     * Show a toast containing the current volume.
+     *
+     * @see #adjustStreamVolume(int, int, int)
+     * @see #adjustVolume(int, int)
+     * @see #setStreamVolume(int, int, int)
+     * @see #setRingerMode(int)
+     */
+    public static final int FLAG_SHOW_UI = 1 << 0;
+
+    /**
+     * Whether to include ringer modes as possible options when changing volume.
+     * For example, if true and volume level is 0 and the volume is adjusted
+     * with {@link #ADJUST_LOWER}, then the ringer mode may switch the silent or
+     * vibrate mode.
+     * <p>
+     * By default this is on for the ring stream. If this flag is included,
+     * this behavior will be present regardless of the stream type being
+     * affected by the ringer mode.
+     * 
+     * @see #adjustVolume(int, int)
+     * @see #adjustStreamVolume(int, int, int)
+     */
+    public static final int FLAG_ALLOW_RINGER_MODES = 1 << 1;
+
+    /**
+     * Whether to play a sound when changing the volume.
+     * <p>
+     * If this is given to {@link #adjustVolume(int, int)} or
+     * {@link #adjustSuggestedStreamVolume(int, int, int)}, it may be ignored
+     * in some cases (for example, the decided stream type is not
+     * {@link AudioManager#STREAM_RING}, or the volume is being adjusted
+     * downward).
+     *
+     * @see #adjustStreamVolume(int, int, int)
+     * @see #adjustVolume(int, int)
+     * @see #setStreamVolume(int, int, int)
+     */
+    public static final int FLAG_PLAY_SOUND = 1 << 2;
+
+    /**
+     * Removes any sounds/vibrate that may be in the queue, or are playing (related to
+     * changing volume).
+     */
+    public static final int FLAG_REMOVE_SOUND_AND_VIBRATE = 1 << 3;
+
+    /**
+     * Whether to vibrate if going into the vibrate ringer mode.
+     */
+    public static final int FLAG_VIBRATE = 1 << 4;
+
+    /**
+     * Ringer mode that will be silent and will not vibrate. (This overrides the
+     * vibrate setting.)
+     *
+     * @see #setRingerMode(int)
+     * @see #getRingerMode()
+     */
+    public static final int RINGER_MODE_SILENT = 0;
+
+    /**
+     * Ringer mode that will be silent and will vibrate. (This will cause the
+     * phone ringer to always vibrate, but the notification vibrate to only
+     * vibrate if set.)
+     *
+     * @see #setRingerMode(int)
+     * @see #getRingerMode()
+     */
+    public static final int RINGER_MODE_VIBRATE = 1;
+
+    /**
+     * Ringer mode that may be audible and may vibrate. It will be audible if
+     * the volume before changing out of this mode was audible. It will vibrate
+     * if the vibrate setting is on.
+     *
+     * @see #setRingerMode(int)
+     * @see #getRingerMode()
+     */
+    public static final int RINGER_MODE_NORMAL = 2;
+
+    /**
+     * Vibrate type that corresponds to the ringer.
+     *
+     * @see #setVibrateSetting(int, int)
+     * @see #getVibrateSetting(int)
+     * @see #shouldVibrate(int)
+     */
+    public static final int VIBRATE_TYPE_RINGER = 0;
+
+    /**
+     * Vibrate type that corresponds to notifications.
+     *
+     * @see #setVibrateSetting(int, int)
+     * @see #getVibrateSetting(int)
+     * @see #shouldVibrate(int)
+     */
+    public static final int VIBRATE_TYPE_NOTIFICATION = 1;
+
+    /**
+     * Vibrate setting that suggests to never vibrate.
+     *
+     * @see #setVibrateSetting(int, int)
+     * @see #getVibrateSetting(int)
+     */
+    public static final int VIBRATE_SETTING_OFF = 0;
+
+    /**
+     * Vibrate setting that suggests to vibrate when possible.
+     *
+     * @see #setVibrateSetting(int, int)
+     * @see #getVibrateSetting(int)
+     */
+    public static final int VIBRATE_SETTING_ON = 1;
+
+    /**
+     * Vibrate setting that suggests to only vibrate when in the vibrate ringer
+     * mode.
+     *
+     * @see #setVibrateSetting(int, int)
+     * @see #getVibrateSetting(int)
+     */
+    public static final int VIBRATE_SETTING_ONLY_SILENT = 2;
+
+    /**
+     * Suggests using the default stream type. This may not be used in all
+     * places a stream type is needed.
+     */
+    public static final int USE_DEFAULT_STREAM_TYPE = Integer.MIN_VALUE;
+
+    private static IAudioService sService;
+
+    /**
+     * @hide
+     */
+    public AudioManager(Context context) {
+        mContext = context;
+        mHandler = new Handler(context.getMainLooper());
+    }
+
+    private static IAudioService getService()
+    {
+        if (sService != null) {
+            return sService;
+        }
+        IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
+        sService = IAudioService.Stub.asInterface(b);
+        return sService;
+    }
+
+    /**
+     * Adjusts the volume of a particular stream by one step in a direction.
+     *
+     * @param streamType The stream type to adjust. One of {@link #STREAM_VOICE_CALL},
+     * {@link #STREAM_SYSTEM}, {@link #STREAM_RING}, {@link #STREAM_MUSIC} or
+     * {@link #STREAM_ALARM}
+     * @param direction The direction to adjust the volume. One of
+     *            {@link #ADJUST_LOWER}, {@link #ADJUST_RAISE}, or
+     *            {@link #ADJUST_SAME}.
+     * @param flags One or more flags.
+     * @see #adjustVolume(int, int)
+     * @see #setStreamVolume(int, int, int)
+     */
+    public void adjustStreamVolume(int streamType, int direction, int flags) {
+        IAudioService service = getService();
+        try {
+            service.adjustStreamVolume(streamType, direction, flags);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Dead object in adjustStreamVolume", e);
+        }
+    }
+
+    /**
+     * Adjusts the volume of the most relevant stream. For example, if a call is
+     * active, it will have the highest priority regardless of if the in-call
+     * screen is showing. Another example, if music is playing in the background
+     * and a call is not active, the music stream will be adjusted.
+     *
+     * @param direction The direction to adjust the volume. One of
+     *            {@link #ADJUST_LOWER}, {@link #ADJUST_RAISE}, or
+     *            {@link #ADJUST_SAME}.
+     * @param flags One or more flags.
+     * @see #adjustSuggestedStreamVolume(int, int, int)
+     * @see #adjustStreamVolume(int, int, int)
+     * @see #setStreamVolume(int, int, int)
+     */
+    public void adjustVolume(int direction, int flags) {
+        IAudioService service = getService();
+        try {
+            service.adjustVolume(direction, flags);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Dead object in adjustVolume", e);
+        }
+    }
+
+    /**
+     * Adjusts the volume of the most relevant stream, or the given fallback
+     * stream.
+     *
+     * @param direction The direction to adjust the volume. One of
+     *            {@link #ADJUST_LOWER}, {@link #ADJUST_RAISE}, or
+     *            {@link #ADJUST_SAME}.
+     * @param suggestedStreamType The stream type that will be used if there
+     *            isn't a relevant stream. {@link #USE_DEFAULT_STREAM_TYPE} is valid here.
+     * @param flags One or more flags.
+     * @see #adjustVolume(int, int)
+     * @see #adjustStreamVolume(int, int, int)
+     * @see #setStreamVolume(int, int, int)
+     */
+    public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags) {
+        IAudioService service = getService();
+        try {
+            service.adjustSuggestedStreamVolume(direction, suggestedStreamType, flags);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Dead object in adjustVolume", e);
+        }
+    }
+
+    /**
+     * Returns the current ringtone mode.
+     *
+     * @return The current ringtone mode, one of {@link #RINGER_MODE_NORMAL},
+     *         {@link #RINGER_MODE_SILENT}, or {@link #RINGER_MODE_VIBRATE}.
+     * @see #setRingerMode(int)
+     */
+    public int getRingerMode() {
+        IAudioService service = getService();
+        try {
+            return service.getRingerMode();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Dead object in getRingerMode", e);
+            return RINGER_MODE_NORMAL;
+        }
+    }
+
+    /**
+     * Returns the maximum volume index for a particular stream.
+     *
+     * @param streamType The stream type whose maximum volume index is returned.
+     * @return The maximum valid volume index for the stream.
+     * @see #getStreamVolume(int)
+     */
+    public int getStreamMaxVolume(int streamType) {
+        IAudioService service = getService();
+        try {
+            return service.getStreamMaxVolume(streamType);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Dead object in getStreamMaxVolume", e);
+            return 0;
+        }
+    }
+
+    /**
+     * Returns the current volume index for a particular stream.
+     *
+     * @param streamType The stream type whose volume index is returned.
+     * @return The current volume index for the stream.
+     * @see #getStreamMaxVolume(int)
+     * @see #setStreamVolume(int, int, int)
+     */
+    public int getStreamVolume(int streamType) {
+        IAudioService service = getService();
+        try {
+            return service.getStreamVolume(streamType);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Dead object in getStreamVolume", e);
+            return 0;
+        }
+    }
+
+    /**
+     * Sets the ringer mode.
+     * <p>
+     * Silent mode will mute the volume and will not vibrate. Vibrate mode will
+     * mute the volume and vibrate. Normal mode will be audible and may vibrate
+     * according to user settings.
+     *
+     * @param ringerMode The ringer mode, one of {@link #RINGER_MODE_NORMAL},
+     *            {@link #RINGER_MODE_SILENT}, or {@link #RINGER_MODE_VIBRATE}.
+     * @see #getRingerMode()
+     */
+    public void setRingerMode(int ringerMode) {
+        IAudioService service = getService();
+        try {
+            service.setRingerMode(ringerMode);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Dead object in setRingerMode", e);
+        }
+    }
+
+    /**
+     * Sets the volume index for a particular stream.
+     *
+     * @param streamType The stream whose volume index should be set.
+     * @param index The volume index to set. See
+     *            {@link #getStreamMaxVolume(int)} for the largest valid value.
+     * @param flags One or more flags.
+     * @see #getStreamMaxVolume(int)
+     * @see #getStreamVolume(int)
+     */
+    public void setStreamVolume(int streamType, int index, int flags) {
+        IAudioService service = getService();
+        try {
+            service.setStreamVolume(streamType, index, flags);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Dead object in setStreamVolume", e);
+        }
+    }
+
+    /**
+     * Solo or unsolo a particular stream. All other streams are muted.
+     * <p>
+     * The solo command is protected against client process death: if a process
+     * with an active solo request on a stream dies, all streams that were muted
+     * because of this request will be unmuted automatically.
+     * <p>
+     * The solo requests for a given stream are cumulative: the AudioManager
+     * can receive several solo requests from one or more clients and the stream
+     * will be unsoloed only when the same number of unsolo requests are received.
+     * <p>
+     * For a better user experience, applications MUST unsolo a soloed stream
+     * in onPause() and solo is again in onResume() if appropriate.
+     *
+     * @param streamType The stream to be soloed/unsoloed.
+     * @param state The required solo state: true for solo ON, false for solo OFF
+     */
+    public void setStreamSolo(int streamType, boolean state) {
+        IAudioService service = getService();
+        try {
+            service.setStreamSolo(streamType, state, mICallBack);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Dead object in setStreamSolo", e);
+        }
+    }
+
+    /**
+     * Mute or unmute an audio stream.
+     * <p>
+     * The mute command is protected against client process death: if a process
+     * with an active mute request on a stream dies, this stream will be unmuted
+     * automatically.
+     * <p>
+     * The mute requests for a given stream are cumulative: the AudioManager
+     * can receive several mute requests from one or more clients and the stream
+     * will be unmuted only when the same number of unmute requests are received.
+     * <p>
+     * For a better user experience, applications MUST unmute a muted stream
+     * in onPause() and mute is again in onResume() if appropriate.
+     *
+     * @param streamType The stream to be muted/unmuted.
+     * @param state The required mute state: true for mute ON, false for mute OFF
+     */
+    public void setStreamMute(int streamType, boolean state) {
+        IAudioService service = getService();
+        try {
+            service.setStreamMute(streamType, state, mICallBack);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Dead object in setStreamMute", e);
+        }
+    }
+
+    /**
+     * Returns whether a particular type should vibrate according to user
+     * settings and the current ringer mode.
+     * <p>
+     * This shouldn't be needed by most clients that use notifications to
+     * vibrate. The notification manager will not vibrate if the policy doesn't
+     * allow it, so the client should always set a vibrate pattern and let the
+     * notification manager control whether or not to actually vibrate.
+     *
+     * @param vibrateType The type of vibrate. One of
+     *            {@link #VIBRATE_TYPE_NOTIFICATION} or
+     *            {@link #VIBRATE_TYPE_RINGER}.
+     * @return Whether the type should vibrate at the instant this method is
+     *         called.
+     * @see #setVibrateSetting(int, int)
+     * @see #getVibrateSetting(int)
+     */
+    public boolean shouldVibrate(int vibrateType) {
+        IAudioService service = getService();
+        try {
+            return service.shouldVibrate(vibrateType);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Dead object in shouldVibrate", e);
+            return false;
+        }
+    }
+
+    /**
+     * Returns whether the user's vibrate setting for a vibrate type.
+     * <p>
+     * This shouldn't be needed by most clients that want to vibrate, instead
+     * see {@link #shouldVibrate(int)}.
+     *
+     * @param vibrateType The type of vibrate. One of
+     *            {@link #VIBRATE_TYPE_NOTIFICATION} or
+     *            {@link #VIBRATE_TYPE_RINGER}.
+     * @return The vibrate setting, one of {@link #VIBRATE_SETTING_ON},
+     *         {@link #VIBRATE_SETTING_OFF}, or
+     *         {@link #VIBRATE_SETTING_ONLY_SILENT}.
+     * @see #setVibrateSetting(int, int)
+     * @see #shouldVibrate(int)
+     */
+    public int getVibrateSetting(int vibrateType) {
+        IAudioService service = getService();
+        try {
+            return service.getVibrateSetting(vibrateType);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Dead object in getVibrateSetting", e);
+            return VIBRATE_SETTING_OFF;
+        }
+    }
+
+    /**
+     * Sets the setting for when the vibrate type should vibrate.
+     *
+     * @param vibrateType The type of vibrate. One of
+     *            {@link #VIBRATE_TYPE_NOTIFICATION} or
+     *            {@link #VIBRATE_TYPE_RINGER}.
+     * @param vibrateSetting The vibrate setting, one of
+     *            {@link #VIBRATE_SETTING_ON},
+     *            {@link #VIBRATE_SETTING_OFF}, or
+     *            {@link #VIBRATE_SETTING_ONLY_SILENT}.
+     * @see #getVibrateSetting(int)
+     * @see #shouldVibrate(int)
+     */
+    public void setVibrateSetting(int vibrateType, int vibrateSetting) {
+        IAudioService service = getService();
+        try {
+            service.setVibrateSetting(vibrateType, vibrateSetting);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Dead object in setVibrateSetting", e);
+        }
+    }
+
+    /**
+     * Sets the speakerphone on or off.
+     *
+     * @param on set <var>true</var> to turn on speakerphone;
+     *           <var>false</var> to turn it off
+     */
+    public void setSpeakerphoneOn(boolean on){
+        setRouting(MODE_IN_CALL, on ? ROUTE_SPEAKER : ROUTE_EARPIECE, ROUTE_ALL);
+    }
+
+    /**
+     * Checks whether the speakerphone is on or off.
+     *
+     * @return true if speakerphone is on, false if it's off
+     */
+    public boolean isSpeakerphoneOn() {
+        return (getRouting(MODE_IN_CALL) & ROUTE_SPEAKER) == 0 ? false : true;
+     }
+
+    /**
+     * Sets audio routing to the Bluetooth headset on or off.
+     *
+     * @param on set <var>true</var> to route SCO (voice) audio to/from Bluetooth
+     *           headset; <var>false</var> to route audio to/from phone earpiece
+     */
+    public void setBluetoothScoOn(boolean on){
+        // Don't disable A2DP when turning off SCO.
+        // A2DP does not affect in-call routing.
+        setRouting(MODE_RINGTONE,
+               on ? ROUTE_BLUETOOTH_SCO: ROUTE_SPEAKER, ROUTE_ALL & ~ROUTE_BLUETOOTH_A2DP);
+        setRouting(MODE_NORMAL,
+                on ? ROUTE_BLUETOOTH_SCO: ROUTE_SPEAKER, ROUTE_ALL & ~ROUTE_BLUETOOTH_A2DP);
+        setRouting(MODE_IN_CALL,
+                on ? ROUTE_BLUETOOTH_SCO: ROUTE_EARPIECE, ROUTE_ALL);
+    }
+
+    /**
+     * Checks whether audio routing to the Bluetooth headset is on or off.
+     *
+     * @return true if SCO audio is being routed to/from Bluetooth headset;
+     *         false if otherwise
+     */
+    public boolean isBluetoothScoOn() {
+        return (getRouting(MODE_IN_CALL) & ROUTE_BLUETOOTH_SCO) == 0 ? false : true;
+    }
+
+    /**
+     * Sets A2DP audio routing to the Bluetooth headset on or off.
+     *
+     * @param on set <var>true</var> to route A2DP audio to/from Bluetooth
+     *           headset; <var>false</var> disable A2DP audio
+     */
+    public void setBluetoothA2dpOn(boolean on){
+        // the audio flinger chooses A2DP as a higher priority,
+        // so there is no need to disable other routes.
+        setRouting(MODE_RINGTONE,
+               on ? ROUTE_BLUETOOTH_A2DP: 0, ROUTE_BLUETOOTH_A2DP);
+        setRouting(MODE_NORMAL,
+                on ? ROUTE_BLUETOOTH_A2DP: 0, ROUTE_BLUETOOTH_A2DP);
+    }
+
+    /**
+     * Checks whether A2DP audio routing to the Bluetooth headset is on or off.
+     *
+     * @return true if A2DP audio is being routed to/from Bluetooth headset;
+     *         false if otherwise
+     */
+    public boolean isBluetoothA2dpOn() {
+        return (getRouting(MODE_NORMAL) & ROUTE_BLUETOOTH_A2DP) == 0 ? false : true;
+    }
+
+    /**
+     * Sets audio routing to the wired headset on or off.
+     *
+     * @param on set <var>true</var> to route audio to/from wired
+     *           headset; <var>false</var> disable wired headset audio
+     * @hide
+     */
+    public void setWiredHeadsetOn(boolean on){
+        // A2DP has higher priority than wired headset, so headset connect/disconnect events
+        // should not affect A2DP routing
+        setRouting(MODE_NORMAL,
+                on ? ROUTE_HEADSET : ROUTE_SPEAKER, ROUTE_ALL & ~ROUTE_BLUETOOTH_A2DP);
+        setRouting(MODE_RINGTONE,
+                on ? ROUTE_HEADSET | ROUTE_SPEAKER : ROUTE_SPEAKER, ROUTE_ALL & ~ROUTE_BLUETOOTH_A2DP);
+        setRouting(MODE_IN_CALL,
+                on ? ROUTE_HEADSET : ROUTE_EARPIECE, ROUTE_ALL);
+    }
+
+    /**
+     * Checks whether audio routing to the wired headset is on or off.
+     *
+     * @return true if audio is being routed to/from wired headset;
+     *         false if otherwise
+     * @hide
+     */
+    public boolean isWiredHeadsetOn() {
+        return (getRouting(MODE_NORMAL) & ROUTE_HEADSET) == 0 ? false : true;
+    }
+
+    /**
+     * Sets the microphone mute on or off.
+     *
+     * @param on set <var>true</var> to mute the microphone;
+     *           <var>false</var> to turn mute off
+     */
+    public void setMicrophoneMute(boolean on){
+        IAudioService service = getService();
+        try {
+            service.setMicrophoneMute(on);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Dead object in setMicrophoneMute", e);
+        }
+    }
+
+    /**
+     * Checks whether the microphone mute is on or off.
+     *
+     * @return true if microphone is muted, false if it's not
+     */
+    public boolean isMicrophoneMute() {
+        IAudioService service = getService();
+        try {
+            return service.isMicrophoneMute();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Dead object in isMicrophoneMute", e);
+            return false;
+        }
+    }
+
+    /**
+     * Sets the audio mode.
+     *
+     * @param mode  the requested audio mode (NORMAL, RINGTONE, or IN_CALL).
+     *              Informs the HAL about the current audio state so that
+     *              it can route the audio appropriately.
+     */
+    public void setMode(int mode) {
+        IAudioService service = getService();
+        try {
+            service.setMode(mode);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Dead object in setMode", e);
+        }
+    }
+
+    /**
+     * Returns the current audio mode.
+     *
+     * @return      the current audio mode (NORMAL, RINGTONE, or IN_CALL).
+     *              Returns the current current audio state from the HAL.
+     */
+    public int getMode() {
+        IAudioService service = getService();
+        try {
+            return service.getMode();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Dead object in getMode", e);
+            return MODE_INVALID;
+        }
+    }
+
+    /* modes for setMode/getMode/setRoute/getRoute */
+    /**
+     * Audio harware modes.
+     */
+    /**
+     * Invalid audio mode.
+     */
+    public static final int MODE_INVALID            = AudioSystem.MODE_INVALID;
+    /**
+     * Current audio mode. Used to apply audio routing to current mode.
+     */
+    public static final int MODE_CURRENT            = AudioSystem.MODE_CURRENT;
+    /**
+     * Normal audio mode: not ringing and no call established.
+     */
+    public static final int MODE_NORMAL             = AudioSystem.MODE_NORMAL;
+    /**
+     * Ringing audio mode. An incoming is being signaled.
+     */
+    public static final int MODE_RINGTONE           = AudioSystem.MODE_RINGTONE;
+    /**
+     * In call audio mode. A call is established.
+     */
+    public static final int MODE_IN_CALL            = AudioSystem.MODE_IN_CALL;
+
+    /* Routing bits for setRouting/getRouting API */
+    /**
+     * Routing audio output to earpiece
+     */
+    public static final int ROUTE_EARPIECE          = AudioSystem.ROUTE_EARPIECE;
+    /**
+     * Routing audio output to spaker
+     */
+    public static final int ROUTE_SPEAKER           = AudioSystem.ROUTE_SPEAKER;
+    /**
+     * @deprecated use {@link #ROUTE_BLUETOOTH_SCO}
+     */
+    @Deprecated public static final int ROUTE_BLUETOOTH = AudioSystem.ROUTE_BLUETOOTH_SCO;
+    /**
+     * Routing audio output to bluetooth SCO
+     */
+    public static final int ROUTE_BLUETOOTH_SCO     = AudioSystem.ROUTE_BLUETOOTH_SCO;
+    /**
+     * Routing audio output to headset
+     */
+    public static final int ROUTE_HEADSET           = AudioSystem.ROUTE_HEADSET;
+    /**
+     * Routing audio output to bluetooth A2DP
+     */
+    public static final int ROUTE_BLUETOOTH_A2DP    = AudioSystem.ROUTE_BLUETOOTH_A2DP;
+    /**
+     * Used for mask parameter of {@link #setRouting(int,int,int)}.
+     */
+    public static final int ROUTE_ALL               = AudioSystem.ROUTE_ALL;
+
+    /**
+     * Sets the audio routing for a specified mode
+     *
+     * @param mode   audio mode to change route. E.g., MODE_RINGTONE.
+     * @param routes bit vector of routes requested, created from one or
+     *               more of ROUTE_xxx types. Set bits indicate that route should be on
+     * @param mask   bit vector of routes to change, created from one or more of
+     * ROUTE_xxx types. Unset bits indicate the route should be left unchanged
+     */
+    public void setRouting(int mode, int routes, int mask) {
+        IAudioService service = getService();
+        try {
+            service.setRouting(mode, routes, mask);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Dead object in setRouting", e);
+        }
+    }
+
+    /**
+     * Returns the current audio routing bit vector for a specified mode.
+     *
+     * @param mode audio mode to get route (e.g., MODE_RINGTONE)
+     * @return an audio route bit vector that can be compared with ROUTE_xxx
+     * bits
+     */
+    public int getRouting(int mode) {
+        IAudioService service = getService();
+        try {
+            return service.getRouting(mode);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Dead object in getRouting", e);
+            return -1;
+        }
+    }
+
+    /**
+     * Checks whether any music is active.
+     *
+     * @return true if any music tracks are active.
+     */
+    public boolean isMusicActive() {
+        IAudioService service = getService();
+        try {
+            return service.isMusicActive();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Dead object in isMusicActive", e);
+            return false;
+        }
+    }
+
+    /*
+     * Sets a generic audio configuration parameter. The use of these parameters
+     * are platform dependant, see libaudio
+     *
+     * ** Temporary interface - DO NOT USE
+     *
+     * TODO: Replace with a more generic key:value get/set mechanism
+     *
+     * param key   name of parameter to set. Must not be null.
+     * param value value of parameter. Must not be null.
+     */
+    /**
+     * @hide
+     */
+    public void setParameter(String key, String value) {
+        IAudioService service = getService();
+        try {
+            service.setParameter(key, value);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Dead object in setParameter", e);
+        }
+    }
+
+    /* Sound effect identifiers */
+    /**
+     * Keyboard and direction pad click sound
+     * @see #playSoundEffect(int)
+     */
+    public static final int FX_KEY_CLICK = 0;
+    /**
+     * Focus has moved up
+     * @see #playSoundEffect(int)
+     */
+    public static final int FX_FOCUS_NAVIGATION_UP = 1;
+    /**
+     * Focus has moved down
+     * @see #playSoundEffect(int)
+     */
+    public static final int FX_FOCUS_NAVIGATION_DOWN = 2;
+    /**
+     * Focus has moved left
+     * @see #playSoundEffect(int)
+     */
+    public static final int FX_FOCUS_NAVIGATION_LEFT = 3;
+    /**
+     * Focus has moved right
+     * @see #playSoundEffect(int)
+     */
+    public static final int FX_FOCUS_NAVIGATION_RIGHT = 4;
+    /**
+     * IME standard keypress sound
+     * @see #playSoundEffect(int)
+     * @hide FIXME: Unhide before release
+     */
+    public static final int FX_KEYPRESS_STANDARD = 5;
+    /**
+     * IME spacebar keypress sound
+     * @see #playSoundEffect(int)
+     * @hide FIXME: Unhide before release
+     */
+    public static final int FX_KEYPRESS_SPACEBAR = 6;
+    /**
+     * IME delete keypress sound
+     * @see #playSoundEffect(int)
+     * @hide FIXME: Unhide before release
+     */
+    public static final int FX_KEYPRESS_DELETE = 7;
+    /**
+     * IME return_keypress sound
+     * @see #playSoundEffect(int)
+     * @hide FIXME: Unhide before release
+     */
+    public static final int FX_KEYPRESS_RETURN = 8;
+    /**
+     * @hide Number of sound effects
+     */
+    public static final int NUM_SOUND_EFFECTS = 9;
+
+    /**
+     * Plays a sound effect (Key clicks, lid open/close...)
+     * @param effectType The type of sound effect. One of
+     *            {@link #FX_KEY_CLICK},
+     *            {@link #FX_FOCUS_NAVIGATION_UP},
+     *            {@link #FX_FOCUS_NAVIGATION_DOWN},
+     *            {@link #FX_FOCUS_NAVIGATION_LEFT},
+     *            {@link #FX_FOCUS_NAVIGATION_RIGHT},
+     *            FIXME: include links before release
+     *            {link #FX_KEYPRESS_STANDARD},
+     *            {link #FX_KEYPRESS_SPACEBAR},
+     *            {link #FX_KEYPRESS_DELETE},
+     *            {link #FX_KEYPRESS_RETURN},
+     * NOTE: This version uses the UI settings to determine
+     * whether sounds are heard or not.
+     */
+    public void  playSoundEffect(int effectType) {
+        if (effectType < 0 || effectType >= NUM_SOUND_EFFECTS) {
+            return;
+        }
+
+        if (!querySoundEffectsEnabled()) {
+            return;
+        }
+
+        IAudioService service = getService();
+        try {
+            service.playSoundEffect(effectType);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Dead object in playSoundEffect"+e);
+        }
+    }
+
+    /**
+     * Plays a sound effect (Key clicks, lid open/close...)
+     * @param effectType The type of sound effect. One of
+     *            {@link #FX_KEY_CLICK},
+     *            {@link #FX_FOCUS_NAVIGATION_UP},
+     *            {@link #FX_FOCUS_NAVIGATION_DOWN},
+     *            {@link #FX_FOCUS_NAVIGATION_LEFT},
+     *            {@link #FX_FOCUS_NAVIGATION_RIGHT},
+     *            FIXME: include links before release
+     *            {link #FX_KEYPRESS_STANDARD},
+     *            {link #FX_KEYPRESS_SPACEBAR},
+     *            {link #FX_KEYPRESS_DELETE},
+     *            {link #FX_KEYPRESS_RETURN},
+     * @param volume Sound effect volume
+     * NOTE: This version is for applications that have their own
+     * settings panel for enabling and controlling volume.
+     *  @hide FIXME: Unhide before release
+     */
+    public void  playSoundEffect(int effectType, float volume) {
+        if (effectType < 0 || effectType >= NUM_SOUND_EFFECTS) {
+            return;
+        }
+
+        IAudioService service = getService();
+        try {
+            service.playSoundEffectVolume(effectType, volume);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Dead object in playSoundEffect"+e);
+        }
+    }
+
+    /**
+     * Settings has an in memory cache, so this is fast.
+     */
+    private boolean querySoundEffectsEnabled() {
+        return Settings.System.getInt(mContext.getContentResolver(), Settings.System.SOUND_EFFECTS_ENABLED, 0) != 0;
+    }
+
+
+    /**
+     *  Load Sound effects.
+     *  This method must be called when sound effects are enabled.
+     */
+    public void loadSoundEffects() {
+        IAudioService service = getService();
+        try {
+            service.loadSoundEffects();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Dead object in loadSoundEffects"+e);
+        }
+    }
+
+    /**
+     *  Unload Sound effects.
+     *  This method can be called to free some memory when
+     *  sound effects are disabled.
+     */
+    public void unloadSoundEffects() {
+        IAudioService service = getService();
+        try {
+            service.unloadSoundEffects();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Dead object in unloadSoundEffects"+e);
+        }
+    }
+
+     /**
+      * {@hide}
+      */
+     private IBinder mICallBack = new Binder();
+}
diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java
new file mode 100644
index 0000000..0ef7760
--- /dev/null
+++ b/media/java/android/media/AudioRecord.java
@@ -0,0 +1,798 @@
+/*
+ * Copyright (C) 2008 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.media;
+
+import java.lang.ref.WeakReference;
+import java.io.OutputStream;
+import java.io.IOException;
+import java.lang.IllegalArgumentException;
+import java.lang.IllegalStateException;
+import java.lang.Thread;
+import java.nio.ByteBuffer;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+
+/**
+ * The AudioRecord class manages the audio resources for Java applications
+ * to record audio from the audio input hardware of the platform. This is
+ * achieved by "pulling" (reading) the data from the AudioRecord object. The
+ * application is responsible for polling the AudioRecord object in time using one of 
+ * the following three methods:  {@link #read(byte[],int, int)}, {@link #read(short[], int, int)}
+ * or {@link #read(ByteBuffer, int)}. The choice of which method to use will be based 
+ * on the audio data storage format that is the most convenient for the user of AudioRecord.
+ * <p>Upon creation, an AudioRecord object initializes its associated audio buffer that it will
+ * fill with the new audio data. The size of this buffer, specified during the construction, 
+ * determines how long an AudioRecord can record before "over-running" data that has not
+ * been read yet. Data should be from the audio hardware in chunks of sizes inferior to
+ * the total recording buffer size.
+ */
+public class AudioRecord
+{
+    //---------------------------------------------------------
+    // Constants
+    //--------------------
+    /**
+     *  State of an AudioRecord that was not successfully initialized upon creation 
+     */
+    public static final int STATE_UNINITIALIZED = 0;
+    /**
+     *  State of an AudioRecord that is ready to be used 
+     */
+    public static final int STATE_INITIALIZED   = 1;
+
+    /**
+     * State of an AudioRecord this is not recording 
+     */
+    public static final int RECORDSTATE_STOPPED = 1;  // matches SL_RECORDSTATE_STOPPED
+    /**
+     * State of an AudioRecord this is recording 
+     */
+    public static final int RECORDSTATE_RECORDING = 3;// matches SL_RECORDSTATE_RECORDING
+
+    // Error codes:
+    // to keep in sync with frameworks/base/core/jni/android_media_AudioRecord.cpp
+    /**
+     * Denotes a successful operation.
+     */
+    public static final int SUCCESS                 = 0;
+    /**
+     * Denotes a generic operation failure.
+     */
+    public static final int ERROR                   = -1;
+    /**
+     * Denotes a failure due to the use of an invalid value.
+     */
+    public static final int ERROR_BAD_VALUE         = -2;
+    /**
+     * Denotes a failure due to the improper use of a method.
+     */
+    public static final int ERROR_INVALID_OPERATION = -3;
+    
+    private static final int AUDIORECORD_ERROR_SETUP_ZEROFRAMECOUNT      = -16;
+    private static final int AUDIORECORD_ERROR_SETUP_INVALIDCHANNELCOUNT = -17;
+    private static final int AUDIORECORD_ERROR_SETUP_INVALIDFORMAT       = -18;
+    private static final int AUDIORECORD_ERROR_SETUP_INVALIDSTREAMTYPE   = -19;
+    private static final int AUDIORECORD_ERROR_SETUP_NATIVEINITFAILED    = -20;
+    
+    // Events:
+    // to keep in sync with frameworks/base/include/media/AudioRecord.h 
+    /**
+     * Event id for when the recording head has reached a previously set marker.
+     */
+    private static final int NATIVE_EVENT_MARKER  = 2;
+    /**
+     * Event id for when the previously set update period has passed during recording.
+     */
+    private static final int NATIVE_EVENT_NEW_POS = 3;
+    
+    private final static String TAG = "AudioRecord-Java";
+
+
+    //---------------------------------------------------------
+    // Used exclusively by native code
+    //--------------------
+    /** 
+     * Accessed by native methods: provides access to C++ AudioRecord object 
+     */
+    @SuppressWarnings("unused")
+    private int mNativeRecorderInJavaObj;
+    /** 
+     * Accessed by native methods: provides access to record source constants 
+     */
+    @SuppressWarnings("unused")
+    private final static int SOURCE_DEFAULT = MediaRecorder.AudioSource.DEFAULT;
+    @SuppressWarnings("unused")
+    private final static int SOURCE_MIC = MediaRecorder.AudioSource.MIC;
+    /** 
+     * Accessed by native methods: provides access to the callback data.
+     */
+    @SuppressWarnings("unused")
+    private int mNativeCallbackCookie;
+    
+
+    //---------------------------------------------------------
+    // Member variables
+    //--------------------    
+    /**
+     * The audio data sampling rate in Hz.
+     */
+    private int mSampleRate = 22050;
+    /**
+     * The number of input audio channels (1 is mono, 2 is stereo)
+     */
+    private int mChannelCount = 1;
+    /**
+     * The current audio channel configuration
+     */
+    private int mChannelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_MONO;
+    /**
+     * The encoding of the audio samples.
+     * @see AudioFormat#ENCODING_PCM_8BIT
+     * @see AudioFormat#ENCODING_PCM_16BIT
+     */
+    private int mAudioFormat = AudioFormat.ENCODING_PCM_16BIT;
+    /**
+     * Where the audio data is recorded from.
+     */
+    private int mRecordSource = MediaRecorder.AudioSource.DEFAULT;
+    /**
+     * Indicates the state of the AudioRecord instance.
+     */
+    private int mState = STATE_UNINITIALIZED;
+    /**
+     * Indicates the recording state of the AudioRecord instance.
+     */
+    private int mRecordingState = RECORDSTATE_STOPPED;
+    /**
+     * Lock to make sure mRecordingState updates are reflecting the actual state of the object.
+     */
+    private Object mRecordingStateLock = new Object();
+    /**
+     * The listener the AudioRecord notifies when a previously set marker is reached.
+     *  @see #setMarkerReachedListener(OnMarkerReachedListener)
+     */
+    private OnMarkerReachedListener mMarkerListener = null;
+    /**
+     * Lock to protect marker listener updates against event notifications
+     */
+    private final Object mMarkerListenerLock = new Object();
+    /**
+     * The listener the AudioRecord notifies periodically during recording.
+     *  @see #setPeriodicNotificationListener(OnPeriodicNotificationListener)
+     */
+    private OnPeriodicNotificationListener mPeriodicListener = null;
+    /**
+     * Lock to protect periodic listener updates against event notifications
+     */
+    private final Object mPeriodicListenerLock = new Object();
+    /**
+     * Handler for events coming from the native code
+     */
+    private NativeEventHandler mNativeEventHandler = null;
+    /**
+     * Size of the native audio buffer.
+     */
+    private int mNativeBufferSizeInBytes = 0;
+
+    
+    //---------------------------------------------------------
+    // Constructor, Finalize
+    //--------------------
+    /**
+     * Class constructor.
+     * @param audioSource the recording source. See {@link MediaRecorder.AudioSource} for
+     *    recording source definitions.
+     * @param sampleRateInHz the sample rate expressed in Hertz. Examples of rates are (but
+     *   not limited to) 44100, 22050 and 11025.
+     * @param channelConfig describes the configuration of the audio channels. 
+     *   See {@link AudioFormat#CHANNEL_CONFIGURATION_MONO} and
+     *   {@link AudioFormat#CHANNEL_CONFIGURATION_STEREO}
+     * @param audioFormat the format in which the audio data is represented. 
+     *   See {@link AudioFormat#ENCODING_PCM_16BIT} and 
+     *   {@link AudioFormat#ENCODING_PCM_8BIT}
+     * @param bufferSizeInBytes the total size (in bytes) of the buffer where audio data is written
+     *   to during the recording. New audio data can be read from this buffer in smaller chunks 
+     *   than this size.
+     * @throws java.lang.IllegalArgumentException
+     */
+    public AudioRecord(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat, 
+            int bufferSizeInBytes)
+    throws IllegalArgumentException {   
+        mState = STATE_UNINITIALIZED;
+        mRecordingState = RECORDSTATE_STOPPED;
+
+        audioParamCheck(audioSource, sampleRateInHz, channelConfig, audioFormat);
+
+        audioBuffSizeCheck(bufferSizeInBytes);
+
+        // native initialization
+        //TODO: update native initialization when information about hardware init failure
+        //      due to capture device already open is available.
+        int initResult = native_setup( new WeakReference<AudioRecord>(this), 
+                mRecordSource, mSampleRate, mChannelCount, mAudioFormat, mNativeBufferSizeInBytes);
+        if (initResult != SUCCESS) {
+            loge("Error code "+initResult+" when initializing native AudioRecord object.");
+            return; // with mState == STATE_UNINITIALIZED
+        }
+
+        mState = STATE_INITIALIZED;
+    }
+
+
+    // Convenience method for the constructor's parameter checks.
+    // This is where constructor IllegalArgumentException-s are thrown
+    // postconditions:
+    //    mRecordSource is valid
+    //    mChannelCount is valid
+    //    mAudioFormat is valid
+    //    mSampleRate is valid
+    private void audioParamCheck(int audioSource, int sampleRateInHz, 
+                                 int channelConfig, int audioFormat) {
+
+        //--------------
+        // audio source
+        if ( (audioSource != MediaRecorder.AudioSource.DEFAULT)
+                && (audioSource != MediaRecorder.AudioSource.MIC) ) {
+            throw (new IllegalArgumentException("Invalid audio source."));
+        } else {
+            mRecordSource = audioSource;
+        }
+        
+        //--------------
+        // sample rate
+        if ( (sampleRateInHz < 4000) || (sampleRateInHz > 48000) ) {
+            throw (new IllegalArgumentException(sampleRateInHz
+                    + "Hz is not a supported sample rate."));
+        } else { 
+            mSampleRate = sampleRateInHz;
+        }
+
+        //--------------
+        // channel config
+        switch (channelConfig) {
+        case AudioFormat.CHANNEL_CONFIGURATION_DEFAULT:
+        case AudioFormat.CHANNEL_CONFIGURATION_MONO:
+            mChannelCount = 1;
+            mChannelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_MONO;
+            break;
+        case AudioFormat.CHANNEL_CONFIGURATION_STEREO:
+            mChannelCount = 2;
+            mChannelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_STEREO;
+            break;
+        default:
+            mChannelCount = 0;
+        mChannelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_INVALID;
+        throw (new IllegalArgumentException("Unsupported channel configuration."));
+        }
+
+        //--------------
+        // audio format
+        switch (audioFormat) {
+        case AudioFormat.ENCODING_DEFAULT:
+            mAudioFormat = AudioFormat.ENCODING_PCM_16BIT;
+            break;
+        case AudioFormat.ENCODING_PCM_16BIT:
+        case AudioFormat.ENCODING_PCM_8BIT:
+            mAudioFormat = audioFormat;
+            break;
+        default:
+            mAudioFormat = AudioFormat.ENCODING_INVALID;
+        throw (new IllegalArgumentException("Unsupported sample encoding." 
+                + " Should be ENCODING_PCM_8BIT or ENCODING_PCM_16BIT."));
+        }
+    }
+
+
+    // Convenience method for the contructor's audio buffer size check.
+    // preconditions:
+    //    mChannelCount is valid
+    //    mAudioFormat is AudioFormat.ENCODING_PCM_8BIT OR AudioFormat.ENCODING_PCM_16BIT
+    // postcondition:
+    //    mNativeBufferSizeInBytes is valid (multiple of frame size, positive)
+    private void audioBuffSizeCheck(int audioBufferSize) {
+        // NB: this section is only valid with PCM data. 
+        // To update when supporting compressed formats
+        int frameSizeInBytes = mChannelCount 
+            * (mAudioFormat == AudioFormat.ENCODING_PCM_8BIT ? 1 : 2);
+        if ((audioBufferSize % frameSizeInBytes != 0) || (audioBufferSize < 1)) {
+            throw (new IllegalArgumentException("Invalid audio buffer size."));
+        }
+
+        mNativeBufferSizeInBytes = audioBufferSize;
+    }    
+    
+    
+    // Convenience method for the creation of the native event handler
+    // It is called only when a non-null event listener is set.
+    // precondition:
+    //    mNativeEventHandler is null
+    private void createNativeEventHandler() {
+        Looper looper;
+        if ((looper = Looper.myLooper()) != null) {
+            mNativeEventHandler = new NativeEventHandler(this, looper);
+        } else if ((looper = Looper.getMainLooper()) != null) {
+            mNativeEventHandler = new NativeEventHandler(this, looper);
+        } else {
+            mNativeEventHandler = null;
+        }
+    }
+    
+
+    /**
+     * Releases the native AudioRecord resources.
+     */
+    public void release() {
+        try {
+            stop();
+        } catch(IllegalStateException ise) { 
+            // don't raise an exception, we're releasing the resources.
+        }
+        native_release();
+        mState = STATE_UNINITIALIZED;
+    }
+    
+
+    @Override
+    protected void finalize() {
+        native_finalize();
+    } 
+
+
+    //--------------------------------------------------------------------------
+    // Getters
+    //--------------------
+    /**
+     * Returns the configured audio data sample rate in Hz
+     */
+    public int getSampleRate() {
+        return mSampleRate;
+    }
+    
+    /**
+     * Returns the audio recording source. 
+     * @see MediaRecorder.AudioSource
+     */
+    public int getAudioSource() {
+        return mRecordSource;
+    }
+
+    /**
+     * Returns the configured audio data format. See {@link AudioFormat#ENCODING_PCM_16BIT}
+     * and {@link AudioFormat#ENCODING_PCM_8BIT}.
+     */
+    public int getAudioFormat() {
+        return mAudioFormat;
+    }
+
+    /**
+     * Returns the configured channel configuration. 
+     * See {@link AudioFormat#CHANNEL_CONFIGURATION_MONO}
+     * and {@link AudioFormat#CHANNEL_CONFIGURATION_STEREO}.
+     */
+    public int getChannelConfiguration() {
+        return mChannelConfiguration;
+    }
+
+    /**
+     * Returns the configured number of channels.
+     */
+    public int getChannelCount() {
+        return mChannelCount;
+    }
+
+    /**
+     * Returns the state of the AudioRecord instance. This is useful after the
+     * AudioRecord instance has been created to check if it was initialized 
+     * properly. This ensures that the appropriate hardware resources have been
+     * acquired.
+     * @see AudioRecord#STATE_INITIALIZED
+     * @see AudioRecord#STATE_UNINITIALIZED
+     */
+    public int getState() {
+        return mState;
+    }
+
+    /**
+     * Returns the recording state of the AudioRecord instance.
+     * @see AudioRecord#RECORDSTATE_STOPPED
+     * @see AudioRecord#RECORDSTATE_RECORDING
+     */
+    public int getRecordingState() {
+        return mRecordingState;
+    }
+    
+    /**
+     * @return marker position in frames
+     */
+    public int getNotificationMarkerPosition() {
+        return native_get_marker_pos();
+    }
+
+    /**
+     * @return update period in frames
+     */
+    public int getPositionNotificationPeriod() {
+        return native_get_pos_update_period();
+    }
+    
+    /**
+     * {@hide}
+     * Returns the minimum buffer size required for the successful creation of an AudioRecord
+     * object.
+     * @param sampleRateInHz the sample rate expressed in Hertz.
+     * @param channelConfig describes the configuration of the audio channels. 
+     *   See {@link AudioFormat#CHANNEL_CONFIGURATION_MONO} and
+     *   {@link AudioFormat#CHANNEL_CONFIGURATION_STEREO}
+     * @param audioFormat the format in which the audio data is represented. 
+     *   See {@link AudioFormat#ENCODING_PCM_16BIT}.
+     * @return {@link #ERROR_BAD_VALUE} if the recording parameters are not supported by the 
+     *  hardware, or an invalid parameter was passed,
+     *  or {@link #ERROR} if the implementation was unable to query the hardware for its 
+     *  output properties, 
+     *   or the minimum buffer size expressed in of bytes.
+     */
+    static public int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat) {
+        int channelCount = 0;
+        switch(channelConfig) {
+        case AudioFormat.CHANNEL_CONFIGURATION_DEFAULT:
+        case AudioFormat.CHANNEL_CONFIGURATION_MONO:
+            channelCount = 1;
+            break;
+        case AudioFormat.CHANNEL_CONFIGURATION_STEREO:
+            channelCount = 2;
+            break;
+        case AudioFormat.CHANNEL_CONFIGURATION_INVALID:
+        default:
+            loge("getMinBufferSize(): Invalid channel configuration.");
+            return AudioRecord.ERROR_BAD_VALUE;
+        }
+        
+        // PCM_8BIT is not supported at the moment
+        if (audioFormat != AudioFormat.ENCODING_PCM_16BIT) {
+            loge("getMinBufferSize(): Invalid audio format.");
+            return AudioRecord.ERROR_BAD_VALUE;
+        }
+        
+        int size = native_get_min_buff_size(sampleRateInHz, channelCount, audioFormat);
+        if (size == 0) {
+            return AudioRecord.ERROR_BAD_VALUE;
+        } 
+        else if (size == -1) {
+            return AudioRecord.ERROR;
+        }
+        else {
+            return size;
+        }
+    }
+
+
+    //---------------------------------------------------------
+    // Transport control methods
+    //--------------------
+    /**
+     * Starts recording from the AudioRecord instance. 
+     * @throws IllegalStateException
+     */
+    public void startRecording()
+    throws IllegalStateException {
+        if (mState != STATE_INITIALIZED) {
+            throw(new IllegalStateException("startRecording() called on an "
+                    +"uninitialized AudioRecord."));
+        }
+
+        // start recording
+        synchronized(mRecordingStateLock) {
+            native_start();
+            mRecordingState = RECORDSTATE_RECORDING;
+        }
+    }
+
+
+
+    /**
+     * Stops recording.
+     * @throws IllegalStateException
+     */
+    public void stop()
+    throws IllegalStateException {
+        if (mState != STATE_INITIALIZED) {
+            throw(new IllegalStateException("stop() called on an uninitialized AudioRecord."));
+        }
+
+        // stop recording
+        synchronized(mRecordingStateLock) {
+            native_stop();
+            mRecordingState = RECORDSTATE_STOPPED;
+        }
+    }
+
+
+    //---------------------------------------------------------
+    // Audio data supply
+    //--------------------
+    /**
+     * Reads audio data from the audio hardware for recording into a buffer.
+     * @param audioData the array to which the recorded audio data is written.
+     * @param offsetInBytes index in audioData from which the data is written.
+     * @param sizeInBytes the number of requested bytes.
+     * @return the number of bytes that were read or or {@link #ERROR_INVALID_OPERATION}
+     *    if the object wasn't properly initialized, or {@link #ERROR_BAD_VALUE} if
+     *    the parameters don't resolve to valid data and indexes.
+     *    The number of bytes will not exceed sizeInBytes.
+     */    
+    public int read(byte[] audioData, int offsetInBytes, int sizeInBytes) {
+        if (mState != STATE_INITIALIZED) {
+            return ERROR_INVALID_OPERATION;
+        }
+        
+        if ( (audioData == null) || (offsetInBytes < 0 ) || (sizeInBytes < 0) 
+                || (offsetInBytes + sizeInBytes > audioData.length)) {
+            return ERROR_BAD_VALUE;
+        }
+
+        return native_read_in_byte_array(audioData, offsetInBytes, sizeInBytes);
+    }
+
+
+    /**
+     * Reads audio data from the audio hardware for recording into a buffer.
+     * @param audioData the array to which the recorded audio data is written.
+     * @param offsetInShorts index in audioData from which the data is written.
+     * @param sizeInShorts the number of requested shorts.
+     * @return the number of bytes that were read or or {@link #ERROR_INVALID_OPERATION}
+     *    if the object wasn't properly initialized, or {@link #ERROR_BAD_VALUE} if
+     *    the parameters don't resolve to valid data and indexes.
+     *    The number of shorts will not exceed sizeInShorts.
+     */    
+    public int read(short[] audioData, int offsetInShorts, int sizeInShorts) {
+        if (mState != STATE_INITIALIZED) {
+            return ERROR_INVALID_OPERATION;
+        }
+        
+        if ( (audioData == null) || (offsetInShorts < 0 ) || (sizeInShorts < 0) 
+                || (offsetInShorts + sizeInShorts > audioData.length)) {
+            return ERROR_BAD_VALUE;
+        }
+
+        return native_read_in_short_array(audioData, offsetInShorts, sizeInShorts);
+    }
+
+
+    /**
+     * Reads audio data from the audio hardware for recording into a direct buffer. If this buffer
+     * is not a direct buffer, this method will always return 0.
+     * @param audioBuffer the direct buffer to which the recorded audio data is written.
+     * @param sizeInBytes the number of requested bytes.
+     * @return the number of bytes that were read or or {@link #ERROR_INVALID_OPERATION}
+     *    if the object wasn't properly initialized, or {@link #ERROR_BAD_VALUE} if
+     *    the parameters don't resolve to valid data and indexes.
+     *    The number of bytes will not exceed sizeInBytes.
+     */    
+    public int read(ByteBuffer audioBuffer, int sizeInBytes) {
+        if (mState != STATE_INITIALIZED) {
+            return ERROR_INVALID_OPERATION;
+        }
+        
+        if ( (audioBuffer == null) || (sizeInBytes < 0) ) {
+            return ERROR_BAD_VALUE;
+        }
+
+        return native_read_in_direct_buffer(audioBuffer, sizeInBytes);
+    }
+
+
+    //--------------------------------------------------------------------------
+    // Initialization / configuration
+    //--------------------  
+    /**
+     * Sets the listener the AudioRecord notifies when a previously set marker is reached.
+     * @param listener
+     */
+    public void setMarkerReachedListener(OnMarkerReachedListener listener) {
+        synchronized (mMarkerListenerLock) {
+            mMarkerListener = listener;
+        }
+        if ((listener != null) && (mNativeEventHandler == null)) {
+            createNativeEventHandler();
+        }
+    }
+    
+    
+    /**
+     * Sets the listener the AudioRecord notifies periodically during recording.
+     * @param listener
+     */
+    public void setPeriodicNotificationListener(OnPeriodicNotificationListener listener) {
+        synchronized (mPeriodicListenerLock) {
+            mPeriodicListener = listener;
+        }
+        if ((listener != null) && (mNativeEventHandler == null)) {
+            createNativeEventHandler();
+        }
+    }
+    
+    
+    /**
+     * Sets the marker position at which the listener, if set with 
+     * {@link #setMarkerReachedListener(OnMarkerReachedListener)}, is called.
+     * @param markerInFrames marker position expressed in frames
+     * @return error code or success, see {@link #SUCCESS}, {@link #ERROR_BAD_VALUE},
+     *  {@link #ERROR_INVALID_OPERATION} 
+     */
+    public int setNotificationMarkerPosition(int markerInFrames) {
+        return native_set_marker_pos(markerInFrames);
+    }
+    
+    
+    /**
+     * Sets the period at which the listener, if set with
+     * {@link #setPositionNotificationPeriod(int)}, is called.
+     * @param periodInFrames update period expressed in frames
+     * @return error code or success, see {@link #SUCCESS}, {@link #ERROR_INVALID_OPERATION}
+     */
+    public int setPositionNotificationPeriod(int periodInFrames) {
+        return native_set_pos_update_period(periodInFrames);
+    }
+
+
+    //---------------------------------------------------------
+    // Interface definitions
+    //--------------------
+    /**
+     * Interface definition for a callback to be invoked when an AudioRecord has
+     * reached a notification marker set by setNotificationMarkerPosition().
+     */
+    public interface OnMarkerReachedListener  {
+        /**
+         * Called on the listener to notify it that the previously set marker has been reached
+         * by the recording head.
+         */
+        void onMarkerReached(AudioRecord recorder);
+    }
+
+
+    /**
+     * Interface definition for a callback to be invoked for each periodic AudioRecord 
+     * update during recording. The update interval is set by setPositionNotificationPeriod().
+     */
+    public interface OnPeriodicNotificationListener  {
+        /**
+         * Called on the listener to periodically notify it that the recording head has reached
+         * a multiple of the notification period.
+         */
+        void onPeriodicNotification(AudioRecord recorder);
+    }
+
+    
+    //---------------------------------------------------------
+    // Inner classes
+    //--------------------
+    /**
+     * Helper class to handle the forwarding of native events to the appropriate listeners
+     */
+    private class NativeEventHandler extends Handler
+    {
+        private AudioRecord mAudioRecord;
+
+        public NativeEventHandler(AudioRecord ar, Looper looper) {
+            super(looper);
+            mAudioRecord = ar;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (mAudioRecord == null) {
+                return;
+            }
+            switch(msg.what) {
+            case NATIVE_EVENT_MARKER:
+                synchronized (mMarkerListenerLock) {
+                    if (mAudioRecord.mMarkerListener != null) {
+                        mAudioRecord.mMarkerListener.onMarkerReached(mAudioRecord);
+                    }
+                }
+                break;
+            case NATIVE_EVENT_NEW_POS:
+                synchronized (mPeriodicListenerLock) {
+                    if (mAudioRecord.mPeriodicListener != null) {
+                        mAudioRecord.mPeriodicListener.onPeriodicNotification(mAudioRecord);
+                    }
+                }
+                break;
+            default:
+                Log.e(TAG, "[ android.media.AudioRecord.NativeEventHandler ] " +
+                        "Unknown event type: " + msg.what);
+                break;
+            }
+        }
+    }
+    
+    
+    //---------------------------------------------------------
+    // Java methods called from the native side
+    //--------------------
+    @SuppressWarnings("unused")
+    private static void postEventFromNative(Object audiorecord_ref,
+            int what, int arg1, int arg2, Object obj) {
+        //logd("Event posted from the native side: event="+ what + " args="+ arg1+" "+arg2);
+        AudioRecord recorder = (AudioRecord)((WeakReference)audiorecord_ref).get();
+        if (recorder == null) {
+            return;
+        }
+        
+        if (recorder.mNativeEventHandler != null) {
+            Message m = recorder.mNativeEventHandler.obtainMessage(what, arg1, arg2, obj);
+            recorder.mNativeEventHandler.sendMessage(m);
+        }
+
+    }
+    
+
+    //---------------------------------------------------------
+    // Native methods called from the Java side
+    //--------------------
+
+    private native final int native_setup(Object audiorecord_this, 
+            int recordSource, int sampleRate, int nbChannels, int audioFormat, int buffSizeInBytes);
+
+    private native final void native_finalize();
+    
+    private native final void native_release();
+
+    private native final void native_start();  
+
+    private native final void native_stop();
+
+    private native final int native_read_in_byte_array(byte[] audioData, 
+            int offsetInBytes, int sizeInBytes);
+
+    private native final int native_read_in_short_array(short[] audioData, 
+            int offsetInShorts, int sizeInShorts);
+
+    private native final int native_read_in_direct_buffer(Object jBuffer, int sizeInBytes);
+    
+    private native final int native_set_marker_pos(int marker);
+    private native final int native_get_marker_pos();
+    
+    private native final int native_set_pos_update_period(int updatePeriod);
+    private native final int native_get_pos_update_period();
+    
+    static private native final int native_get_min_buff_size(
+            int sampleRateInHz, int channelCount, int audioFormat);
+
+    
+    //---------------------------------------------------------
+    // Utility methods
+    //------------------
+
+    private static void logd(String msg) {
+        Log.d(TAG, "[ android.media.AudioRecord ] " + msg);
+    }
+
+    private static void loge(String msg) {
+        Log.e(TAG, "[ android.media.AudioRecord ] " + msg);
+    }
+
+}
+
+
+
+
+
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
new file mode 100644
index 0000000..83ede0d
--- /dev/null
+++ b/media/java/android/media/AudioService.java
@@ -0,0 +1,1236 @@
+/*
+ * Copyright (C) 2006 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.media;
+
+import android.app.ActivityManagerNative;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.media.MediaPlayer.OnCompletionListener;
+import android.media.MediaPlayer.OnErrorListener;
+import android.os.Binder;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.provider.Settings;
+import android.provider.Settings.System;
+import android.util.Log;
+import android.view.VolumePanel;
+
+import com.android.internal.telephony.ITelephony;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+
+/**
+ * The implementation of the volume manager service.
+ * <p>
+ * This implementation focuses on delivering a responsive UI. Most methods are
+ * asynchronous to external calls. For example, the task of setting a volume
+ * will update our internal state, but in a separate thread will set the system
+ * volume and later persist to the database. Similarly, setting the ringer mode
+ * will update the state and broadcast a change and in a separate thread later
+ * persist the ringer mode.
+ *
+ * @hide
+ */
+public class AudioService extends IAudioService.Stub {
+
+    private static final String TAG = "AudioService";
+
+    /** How long to delay before persisting a change in volume/ringer mode. */
+    private static final int PERSIST_DELAY = 3000;
+
+    private Context mContext;
+    private ContentResolver mContentResolver;
+
+    /** The UI */
+    private VolumePanel mVolumePanel;
+
+    // sendMsg() flags
+    /** Used when a message should be shared across all stream types. */
+    private static final int SHARED_MSG = -1;
+    /** If the msg is already queued, replace it with this one. */
+    private static final int SENDMSG_REPLACE = 0;
+    /** If the msg is already queued, ignore this one and leave the old. */
+    private static final int SENDMSG_NOOP = 1;
+    /** If the msg is already queued, queue this one and leave the old. */
+    private static final int SENDMSG_QUEUE = 2;
+
+    // AudioHandler message.whats
+    private static final int MSG_SET_SYSTEM_VOLUME = 0;
+    private static final int MSG_PERSIST_VOLUME = 1;
+    private static final int MSG_PERSIST_RINGER_MODE = 3;
+    private static final int MSG_PERSIST_VIBRATE_SETTING = 4;
+    private static final int MSG_MEDIA_SERVER_DIED = 5;
+    private static final int MSG_MEDIA_SERVER_STARTED = 6;
+    private static final int MSG_PLAY_SOUND_EFFECT = 7;
+
+    /** @see AudioSystemThread */
+    private AudioSystemThread mAudioSystemThread;
+    /** @see AudioHandler */
+    private AudioHandler mAudioHandler;
+    /** @see VolumeStreamState */
+    private VolumeStreamState[] mStreamStates;
+
+    private boolean mMicMute;
+    private int mMode;
+    private int[] mRoutes = new int[AudioSystem.NUM_MODES];
+    private Object mSettingsLock = new Object();
+    private boolean mMediaServerOk;
+
+    private SoundPool mSoundPool;
+    private Object mSoundEffectsLock = new Object();
+    private static final int NUM_SOUNDPOOL_CHANNELS = 4;
+    private static final int SOUND_EFFECT_VOLUME = 1000;
+
+    /* Sound effect file names  */
+    private static final String SOUND_EFFECTS_PATH = "/media/audio/ui/";
+    private static final String[] SOUND_EFFECT_FILES = new String[] {
+        "Effect_Tick.ogg",
+        "KeypressStandard.ogg",
+        "KeypressSpacebar.ogg",
+        "KeypressDelete.ogg",
+        "KeypressReturn.ogg"
+    };
+
+    /* Sound effect file name mapping sound effect id (AudioManager.FX_xxx) to
+     * file index in SOUND_EFFECT_FILES[] (first column) and indicating if effect
+     * uses soundpool (second column) */
+    private int[][] SOUND_EFFECT_FILES_MAP = new int[][] {
+        {0, -1},  // FX_KEY_CLICK
+        {0, -1},  // FX_FOCUS_NAVIGATION_UP
+        {0, -1},  // FX_FOCUS_NAVIGATION_DOWN
+        {0, -1},  // FX_FOCUS_NAVIGATION_LEFT
+        {0, -1},  // FX_FOCUS_NAVIGATION_RIGHT
+        {1, -1},  // FX_KEYPRESS_STANDARD
+        {2, -1},  // FX_KEYPRESS_SPACEBAR
+        {3, -1},  // FX_FOCUS_DELETE
+        {4, -1}   // FX_FOCUS_RETURN
+    };
+
+    private AudioSystem.ErrorCallback mAudioSystemCallback = new AudioSystem.ErrorCallback() {
+        public void onError(int error) {
+            switch (error) {
+            case AudioSystem.AUDIO_STATUS_SERVER_DIED:
+                if (mMediaServerOk) {
+                    sendMsg(mAudioHandler, MSG_MEDIA_SERVER_DIED, SHARED_MSG, SENDMSG_NOOP, 0, 0,
+                            null, 1500);
+                }
+                break;
+            case AudioSystem.AUDIO_STATUS_OK:
+                if (!mMediaServerOk) {
+                    sendMsg(mAudioHandler, MSG_MEDIA_SERVER_STARTED, SHARED_MSG, SENDMSG_NOOP, 0, 0,
+                            null, 0);
+                }
+                break;
+            default:
+                break;
+            }
+       }
+    };
+
+    /**
+     * Current ringer mode from one of {@link AudioManager#RINGER_MODE_NORMAL},
+     * {@link AudioManager#RINGER_MODE_SILENT}, or
+     * {@link AudioManager#RINGER_MODE_VIBRATE}.
+     */
+    private int mRingerMode;
+
+    /** @see System#MODE_RINGER_STREAMS_AFFECTED */
+    private int mRingerModeAffectedStreams;
+
+    /** @see System#MUTE_STREAMS_AFFECTED */
+    private int mMuteAffectedStreams;
+
+    /**
+     * Has multiple bits per vibrate type to indicate the type's vibrate
+     * setting. See {@link #setVibrateSetting(int, int)}.
+     * <p>
+     * NOTE: This is not the final decision of whether vibrate is on/off for the
+     * type since it depends on the ringer mode. See {@link #shouldVibrate(int)}.
+     */
+    private int mVibrateSetting;
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Construction
+    ///////////////////////////////////////////////////////////////////////////
+
+    /** @hide */
+    public AudioService(Context context) {
+        mContext = context;
+        mContentResolver = context.getContentResolver();
+        mVolumePanel = new VolumePanel(context, this);
+
+        createAudioSystemThread();
+        createStreamStates();
+        readPersistedSettings();
+        readAudioSettings();
+        mMediaServerOk = true;
+        AudioSystem.setErrorCallback(mAudioSystemCallback);
+        loadSoundEffects();
+    }
+
+    private void createAudioSystemThread() {
+        mAudioSystemThread = new AudioSystemThread();
+        mAudioSystemThread.start();
+        waitForAudioHandlerCreation();
+    }
+
+    /** Waits for the volume handler to be created by the other thread. */
+    private void waitForAudioHandlerCreation() {
+        synchronized(this) {
+            while (mAudioHandler == null) {
+                try {
+                    // Wait for mAudioHandler to be set by the other thread
+                    wait();
+                } catch (InterruptedException e) {
+                    Log.e(TAG, "Interrupted while waiting on volume handler.");
+                }
+            }
+        }
+    }
+
+    private void createStreamStates() {
+        final int[] volumeLevelsPhone =
+            createVolumeLevels(0, AudioManager.MAX_STREAM_VOLUME[AudioManager.STREAM_VOICE_CALL]);
+        final int[] volumeLevelsCoarse =
+            createVolumeLevels(0, AudioManager.MAX_STREAM_VOLUME[AudioManager.STREAM_SYSTEM]);
+        final int[] volumeLevelsFine =
+            createVolumeLevels(0, AudioManager.MAX_STREAM_VOLUME[AudioManager.STREAM_MUSIC]);
+        final int[] volumeLevelsBtPhone =
+            createVolumeLevels(0,
+                    AudioManager.MAX_STREAM_VOLUME[AudioManager.STREAM_BLUETOOTH_SCO]);
+
+        int numStreamTypes = AudioSystem.getNumStreamTypes();
+        VolumeStreamState[] streams = mStreamStates = new VolumeStreamState[numStreamTypes];
+
+        for (int i = 0; i < numStreamTypes; i++) {
+            final int[] levels;
+
+            switch (i) {
+
+                case AudioSystem.STREAM_MUSIC:
+                    levels = volumeLevelsFine;
+                    break;
+
+                case AudioSystem.STREAM_VOICE_CALL:
+                    levels = volumeLevelsPhone;
+                    break;
+
+                case AudioSystem.STREAM_BLUETOOTH_SCO:
+                    levels = volumeLevelsBtPhone;
+                    break;
+
+                default:
+                    levels = volumeLevelsCoarse;
+                    break;
+            }
+
+            if (i == AudioSystem.STREAM_BLUETOOTH_SCO) {
+                streams[i] = new VolumeStreamState(AudioManager.DEFAULT_STREAM_VOLUME[i], i,levels);
+            } else {
+                streams[i] = new VolumeStreamState(System.VOLUME_SETTINGS[i], i, levels);
+            }
+        }
+    }
+
+    private static int[] createVolumeLevels(int offset, int numlevels) {
+        double curve = 1.0f; // 1.4f
+        int [] volumes = new int[numlevels + offset];
+        for (int i = 0; i < offset; i++) {
+            volumes[i] = 0;
+        }
+
+        double val = 0;
+        double max = Math.pow(numlevels - 1, curve);
+        for (int i = 0; i < numlevels; i++) {
+            val = Math.pow(i, curve) / max;
+            volumes[offset + i] = (int) (val * 100.0f);
+        }
+        return volumes;
+    }
+
+    private void readPersistedSettings() {
+        final ContentResolver cr = mContentResolver;
+
+        mRingerMode = System.getInt(cr, System.MODE_RINGER, AudioManager.RINGER_MODE_NORMAL);
+        mRingerModeAffectedStreams = System.getInt(mContentResolver,
+                System.MODE_RINGER_STREAMS_AFFECTED, 1 << AudioSystem.STREAM_RING);
+
+        mVibrateSetting = System.getInt(cr, System.VIBRATE_ON, 0);
+
+        mMuteAffectedStreams = System.getInt(cr,
+                System.MUTE_STREAMS_AFFECTED,
+                ((1 << AudioSystem.STREAM_MUSIC)|(1 << AudioSystem.STREAM_RING)|(1 << AudioSystem.STREAM_SYSTEM)));
+
+        // Each stream will read its own persisted settings
+
+        // Broadcast the sticky intent
+        broadcastRingerMode();
+
+        // Broadcast vibrate settings
+        broadcastVibrateSetting(AudioManager.VIBRATE_TYPE_RINGER);
+        broadcastVibrateSetting(AudioManager.VIBRATE_TYPE_NOTIFICATION);
+    }
+
+    private void readAudioSettings() {
+        synchronized (mSettingsLock) {
+            mMicMute = AudioSystem.isMicrophoneMuted();
+            mMode = AudioSystem.getMode();
+            for (int mode = 0; mode < AudioSystem.NUM_MODES; mode++) {
+                mRoutes[mode] = AudioSystem.getRouting(mode);
+            }
+        }
+    }
+
+    private void applyAudioSettings() {
+        synchronized (mSettingsLock) {
+            AudioSystem.muteMicrophone(mMicMute);
+            AudioSystem.setMode(mMode);
+            for (int mode = 0; mode < AudioSystem.NUM_MODES; mode++) {
+                AudioSystem.setRouting(mode, mRoutes[mode], AudioSystem.ROUTE_ALL);
+            }
+        }
+   }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // IPC methods
+    ///////////////////////////////////////////////////////////////////////////
+
+    /** @see AudioManager#adjustVolume(int, int) */
+    public void adjustVolume(int direction, int flags) {
+        adjustSuggestedStreamVolume(direction, AudioManager.USE_DEFAULT_STREAM_TYPE, flags);
+    }
+
+    /** @see AudioManager#adjustVolume(int, int, int) */
+    public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags) {
+
+        int streamType = getActiveStreamType(suggestedStreamType);
+
+        // Don't play sound on other streams
+        if (streamType != AudioSystem.STREAM_RING && (flags & AudioManager.FLAG_PLAY_SOUND) != 0) {
+            flags &= ~AudioManager.FLAG_PLAY_SOUND;
+        }
+
+        adjustStreamVolume(streamType, direction, flags);
+    }
+
+    /** @see AudioManager#adjustStreamVolume(int, int, int) */
+    public void adjustStreamVolume(int streamType, int direction, int flags) {
+        ensureValidDirection(direction);
+        ensureValidStreamType(streamType);
+
+        boolean notificationsUseRingVolume = Settings.System.getInt(mContentResolver,
+                Settings.System.NOTIFICATIONS_USE_RING_VOLUME, 1) == 1;
+        if (notificationsUseRingVolume && streamType == AudioManager.STREAM_NOTIFICATION) {
+            // Redirect the volume change to the ring stream
+            streamType = AudioManager.STREAM_RING;
+        }
+
+        VolumeStreamState streamState = mStreamStates[streamType];
+        final int oldIndex = streamState.mIndex;
+        boolean adjustVolume = true;
+
+        // If either the client forces allowing ringer modes for this adjustment,
+        // or the stream type is one that is affected by ringer modes
+        if ((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0
+                || streamType == AudioManager.STREAM_RING) {
+            // Check if the ringer mode changes with this volume adjustment. If
+            // it does, it will handle adjusting the volume, so we won't below
+            adjustVolume = checkForRingerModeChange(oldIndex, direction);
+        }
+
+        if (adjustVolume && streamState.adjustIndex(direction)) {
+
+            boolean alsoUpdateNotificationVolume =  notificationsUseRingVolume &&
+                    streamType == AudioManager.STREAM_RING;
+            if (alsoUpdateNotificationVolume) {
+                mStreamStates[AudioManager.STREAM_NOTIFICATION].adjustIndex(direction);
+            }
+
+            // Post message to set system volume (it in turn will post a message
+            // to persist). Do not change volume if stream is muted.
+            if (streamState.muteCount() == 0) {
+                sendMsg(mAudioHandler, MSG_SET_SYSTEM_VOLUME, streamType, SENDMSG_NOOP, 0, 0,
+                        streamState, 0);
+
+                if (alsoUpdateNotificationVolume) {
+                    sendMsg(mAudioHandler, MSG_SET_SYSTEM_VOLUME, AudioManager.STREAM_NOTIFICATION,
+                            SENDMSG_NOOP, 0, 0, mStreamStates[AudioManager.STREAM_NOTIFICATION], 0);
+                }
+            }
+        }
+
+        // UI
+        mVolumePanel.postVolumeChanged(streamType, flags);
+        // Broadcast Intent
+        sendVolumeUpdate(streamType);
+    }
+
+    /** @see AudioManager#setStreamVolume(int, int, int) */
+    public void setStreamVolume(int streamType, int index, int flags) {
+        ensureValidStreamType(streamType);
+        syncRingerAndNotificationStreamVolume(streamType, index, false);
+
+        setStreamVolumeInt(streamType, index, false);
+
+        // UI, etc.
+        mVolumePanel.postVolumeChanged(streamType, flags);
+        // Broadcast Intent
+        sendVolumeUpdate(streamType);
+    }
+
+    private void sendVolumeUpdate(int streamType) {
+        Intent intent = new Intent(AudioManager.VOLUME_CHANGED_ACTION);
+        intent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, streamType);
+        intent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, getStreamVolume(streamType));
+
+        // Currently, sending the intent only when the stream is BLUETOOTH_SCO
+        if (streamType == AudioManager.STREAM_BLUETOOTH_SCO) {
+            mContext.sendBroadcast(intent);
+        }
+    }
+
+    /**
+     * Sync the STREAM_RING and STREAM_NOTIFICATION volumes if mandated by the
+     * value in Settings.
+     *
+     * @param streamType Type of the stream
+     * @param index Volume index for the stream
+     * @param force If true, set the volume even if the current and desired
+     * volume as same
+     */
+    private void syncRingerAndNotificationStreamVolume(int streamType, int index, boolean force) {
+        boolean notificationsUseRingVolume = Settings.System.getInt(mContentResolver,
+                Settings.System.NOTIFICATIONS_USE_RING_VOLUME, 1) == 1;
+        if (notificationsUseRingVolume) {
+            if (streamType == AudioManager.STREAM_NOTIFICATION) {
+                // Redirect the volume change to the ring stream
+                streamType = AudioManager.STREAM_RING;
+            }
+            if (streamType == AudioManager.STREAM_RING) {
+                // One-off to sync notification volume to ringer volume
+                setStreamVolumeInt(AudioManager.STREAM_NOTIFICATION, index, force);
+            }
+        }
+    }
+
+
+    /**
+     * Sets the stream state's index, and posts a message to set system volume.
+     * This will not call out to the UI. Assumes a valid stream type.
+     *
+     * @param streamType Type of the stream
+     * @param index Desired volume index of the stream
+     * @param force If true, set the volume even if the desired volume is same
+     * as the current volume.
+     */
+    private void setStreamVolumeInt(int streamType, int index, boolean force) {
+        VolumeStreamState streamState = mStreamStates[streamType];
+        if (streamState.setIndex(index) || force) {
+            // Post message to set system volume (it in turn will post a message
+            // to persist). Do not change volume if stream is muted.
+            if (streamState.muteCount() == 0) {
+                sendMsg(mAudioHandler, MSG_SET_SYSTEM_VOLUME, streamType, SENDMSG_NOOP, 0, 0,
+                        streamState, 0);
+            }
+        }
+    }
+
+    /** @see AudioManager#setStreamSolo(int, boolean) */
+    public void setStreamSolo(int streamType, boolean state, IBinder cb) {
+        for (int stream = 0; stream < mStreamStates.length; stream++) {
+            if (!isStreamAffectedByMute(stream) || stream == streamType) continue;
+            // Bring back last audible volume
+            mStreamStates[stream].mute(cb, state);
+         }
+    }
+
+    /** @see AudioManager#setStreamMute(int, boolean) */
+    public void setStreamMute(int streamType, boolean state, IBinder cb) {
+        if (isStreamAffectedByMute(streamType)) {
+            mStreamStates[streamType].mute(cb, state);
+        }
+    }
+
+    /** @see AudioManager#getStreamVolume(int) */
+    public int getStreamVolume(int streamType) {
+        ensureValidStreamType(streamType);
+        return mStreamStates[streamType].mIndex;
+    }
+
+    /** @see AudioManager#getStreamMaxVolume(int) */
+    public int getStreamMaxVolume(int streamType) {
+        ensureValidStreamType(streamType);
+        return mStreamStates[streamType].getMaxIndex();
+    }
+
+    /** @see AudioManager#getRingerMode() */
+    public int getRingerMode() {
+        return mRingerMode;
+    }
+
+    /** @see AudioManager#setRingerMode(int) */
+    public void setRingerMode(int ringerMode) {
+        if (ringerMode != mRingerMode) {
+            mRingerMode = ringerMode;
+
+            // Adjust volumes via posting message
+            int numStreamTypes = AudioSystem.getNumStreamTypes();
+            if (mRingerMode == AudioManager.RINGER_MODE_NORMAL) {
+                for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
+                    if (!isStreamAffectedByRingerMode(streamType)) continue;
+                    // Bring back last audible volume
+                    setStreamVolumeInt(streamType, mStreamStates[streamType].mLastAudibleIndex,
+                                       false);
+                }
+            } else {
+                for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
+                    if (!isStreamAffectedByRingerMode(streamType)) continue;
+                    // Either silent or vibrate, either way volume is 0
+                    setStreamVolumeInt(streamType, 0, false);
+                }
+            }
+
+            // Send sticky broadcast
+            broadcastRingerMode();
+
+            // Post a persist ringer mode msg
+            sendMsg(mAudioHandler, MSG_PERSIST_RINGER_MODE, SHARED_MSG,
+                    SENDMSG_REPLACE, 0, 0, null, PERSIST_DELAY);
+        }
+    }
+
+    /** @see AudioManager#shouldVibrate(int) */
+    public boolean shouldVibrate(int vibrateType) {
+
+        switch (getVibrateSetting(vibrateType)) {
+
+            case AudioManager.VIBRATE_SETTING_ON:
+                return mRingerMode != AudioManager.RINGER_MODE_SILENT;
+
+            case AudioManager.VIBRATE_SETTING_ONLY_SILENT:
+                return mRingerMode == AudioManager.RINGER_MODE_VIBRATE;
+
+            case AudioManager.VIBRATE_SETTING_OFF:
+                // Phone ringer should always vibrate in vibrate mode
+                if (vibrateType == AudioManager.VIBRATE_TYPE_RINGER) {
+                    return mRingerMode == AudioManager.RINGER_MODE_VIBRATE;
+                }
+
+            default:
+                return false;
+        }
+    }
+
+    /** @see AudioManager#getVibrateSetting(int) */
+    public int getVibrateSetting(int vibrateType) {
+        return (mVibrateSetting >> (vibrateType * 2)) & 3;
+    }
+
+    /** @see AudioManager#setVibrateSetting(int, int) */
+    public void setVibrateSetting(int vibrateType, int vibrateSetting) {
+
+        mVibrateSetting = getValueForVibrateSetting(mVibrateSetting, vibrateType, vibrateSetting);
+
+        // Broadcast change
+        broadcastVibrateSetting(vibrateType);
+
+        // Post message to set ringer mode (it in turn will post a message
+        // to persist)
+        sendMsg(mAudioHandler, MSG_PERSIST_VIBRATE_SETTING, SHARED_MSG, SENDMSG_NOOP, 0, 0,
+                null, 0);
+    }
+
+    /**
+     * @see #setVibrateSetting(int, int)
+     * @hide
+     */
+    public static int getValueForVibrateSetting(int existingValue, int vibrateType,
+            int vibrateSetting) {
+
+        // First clear the existing setting. Each vibrate type has two bits in
+        // the value. Note '3' is '11' in binary.
+        existingValue &= ~(3 << (vibrateType * 2));
+
+        // Set into the old value
+        existingValue |= (vibrateSetting & 3) << (vibrateType * 2);
+
+        return existingValue;
+    }
+
+    /** @see AudioManager#setMicrophoneMute(boolean) */
+    public void setMicrophoneMute(boolean on) {
+        if (!checkAudioSettingsPermission("setMicrophoneMute()")) {
+            return;
+        }
+        synchronized (mSettingsLock) {
+            if (on != mMicMute) {
+                AudioSystem.muteMicrophone(on);
+                mMicMute = on;
+            }
+        }
+    }
+
+    /** @see AudioManager#isMicrophoneMute() */
+    public boolean isMicrophoneMute() {
+        return mMicMute;
+    }
+
+    /** @see AudioManager#setMode(int) */
+    public void setMode(int mode) {
+        if (!checkAudioSettingsPermission("setMode()")) {
+            return;
+        }
+        synchronized (mSettingsLock) {
+            if (mode != mMode) {
+                AudioSystem.setMode(mode);
+                mMode = mode;
+            }
+            int streamType = getActiveStreamType(AudioManager.USE_DEFAULT_STREAM_TYPE);
+            int index = mStreamStates[streamType].mIndex;
+            syncRingerAndNotificationStreamVolume(streamType, index, true);
+            setStreamVolumeInt(streamType, index, true);
+        }
+    }
+
+    /** @see AudioManager#getMode() */
+    public int getMode() {
+        return mMode;
+    }
+
+    /** @see AudioManager#setRouting(int, int, int) */
+    public void setRouting(int mode, int routes, int mask) {
+        if (!checkAudioSettingsPermission("setRouting()")) {
+            return;
+        }
+        synchronized (mSettingsLock) {
+            if ((mRoutes[mode] & mask) != (routes & mask)) {
+                AudioSystem.setRouting(mode, routes, mask);
+                mRoutes[mode] = (mRoutes[mode] & ~mask) | (routes & mask);
+            }
+            int streamType = getActiveStreamType(AudioManager.USE_DEFAULT_STREAM_TYPE);
+            int index = mStreamStates[streamType].mIndex;
+            syncRingerAndNotificationStreamVolume(streamType, index, true);
+            setStreamVolumeInt(streamType, index, true);
+        }
+    }
+
+    /** @see AudioManager#getRouting(int) */
+    public int getRouting(int mode) {
+        return mRoutes[mode];
+    }
+
+    /** @see AudioManager#isMusicActive() */
+    public boolean isMusicActive() {
+        return AudioSystem.isMusicActive();
+    }
+
+    /** @see AudioManager#setParameter(String, String) */
+    public void setParameter(String key, String value) {
+        AudioSystem.setParameter(key, value);
+    }
+
+    /** @see AudioManager#playSoundEffect(int) */
+    public void playSoundEffect(int effectType) {
+        sendMsg(mAudioHandler, MSG_PLAY_SOUND_EFFECT, SHARED_MSG, SENDMSG_NOOP,
+                effectType, SOUND_EFFECT_VOLUME, null, 0);
+    }
+
+    /** @see AudioManager#playSoundEffect(int, float) */
+    /* @hide FIXME: unhide before release */
+    public void playSoundEffectVolume(int effectType, float volume) {
+        sendMsg(mAudioHandler, MSG_PLAY_SOUND_EFFECT, SHARED_MSG, SENDMSG_NOOP,
+                effectType, (int) (volume * 1000), null, 0);
+    }
+
+    /**
+     * Loads samples into the soundpool.
+     * This method must be called at when sound effects are enabled
+     */
+    public boolean loadSoundEffects() {
+        synchronized (mSoundEffectsLock) {
+            mSoundPool = new SoundPool(NUM_SOUNDPOOL_CHANNELS, AudioSystem.STREAM_SYSTEM, 0);
+            if (mSoundPool == null) {
+                return false;
+            }
+            /*
+             * poolId table: The value -1 in this table indicates that corresponding
+             * file (same index in SOUND_EFFECT_FILES[] has not been loaded.
+             * Once loaded, the value in poolId is the sample ID and the same
+             * sample can be reused for another effect using the same file.
+             */
+            int[] poolId = new int[SOUND_EFFECT_FILES.length];
+            for (int fileIdx = 0; fileIdx < SOUND_EFFECT_FILES.length; fileIdx++) {
+                poolId[fileIdx] = -1;
+            }
+            /*
+             * Effects whose value in SOUND_EFFECT_FILES_MAP[effect][1] is -1 must be loaded.
+             * If load succeeds, value in SOUND_EFFECT_FILES_MAP[effect][1] is > 0:
+             * this indicates we have a valid sample loaded for this effect.
+             */
+            for (int effect = 0; effect < AudioManager.NUM_SOUND_EFFECTS; effect++) {
+                // Do not load sample if this effect uses the MediaPlayer
+                if (SOUND_EFFECT_FILES_MAP[effect][1] == 0) {
+                    continue;
+                }
+                if (poolId[SOUND_EFFECT_FILES_MAP[effect][0]] == -1) {
+                    String filePath = Environment.getRootDirectory() + SOUND_EFFECTS_PATH + SOUND_EFFECT_FILES[SOUND_EFFECT_FILES_MAP[effect][0]];
+                    int sampleId = mSoundPool.load(filePath, 0);
+                    SOUND_EFFECT_FILES_MAP[effect][1] = sampleId;
+                    poolId[SOUND_EFFECT_FILES_MAP[effect][0]] = sampleId;
+                    if (sampleId <= 0) {
+                        Log.w(TAG, "Soundpool could not load file: "+filePath);
+                    }
+                } else {
+                    SOUND_EFFECT_FILES_MAP[effect][1] = poolId[SOUND_EFFECT_FILES_MAP[effect][0]];
+                }
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     *  Unloads samples from the sound pool.
+     *  This method can be called to free some memory when
+     *  sound effects are disabled.
+     */
+    public void unloadSoundEffects() {
+        synchronized (mSoundEffectsLock) {
+            if (mSoundPool == null) {
+                return;
+            }
+            int[] poolId = new int[SOUND_EFFECT_FILES.length];
+            for (int fileIdx = 0; fileIdx < SOUND_EFFECT_FILES.length; fileIdx++) {
+                poolId[fileIdx] = 0;
+            }
+
+            for (int effect = 0; effect < AudioManager.NUM_SOUND_EFFECTS; effect++) {
+                if (SOUND_EFFECT_FILES_MAP[effect][1] <= 0) {
+                    continue;
+                }
+                if (poolId[SOUND_EFFECT_FILES_MAP[effect][0]] == 0) {
+                    mSoundPool.unload(SOUND_EFFECT_FILES_MAP[effect][1]);
+                    SOUND_EFFECT_FILES_MAP[effect][1] = -1;
+                    poolId[SOUND_EFFECT_FILES_MAP[effect][0]] = -1;
+                }
+            }
+            mSoundPool = null;
+        }
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Internal methods
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Checks if the adjustment should change ringer mode instead of just
+     * adjusting volume. If so, this will set the proper ringer mode and volume
+     * indices on the stream states.
+     */
+    private boolean checkForRingerModeChange(int oldIndex, int direction) {
+        boolean adjustVolumeIndex = true;
+        int newRingerMode = mRingerMode;
+
+        if (mRingerMode == AudioManager.RINGER_MODE_NORMAL && oldIndex == 1
+                && direction == AudioManager.ADJUST_LOWER) {
+            newRingerMode = AudioManager.RINGER_MODE_VIBRATE;
+        } else if (mRingerMode == AudioManager.RINGER_MODE_VIBRATE) {
+            if (direction == AudioManager.ADJUST_RAISE) {
+                newRingerMode = AudioManager.RINGER_MODE_NORMAL;
+            } else if (direction == AudioManager.ADJUST_LOWER) {
+                newRingerMode = AudioManager.RINGER_MODE_SILENT;
+            }
+        } else if (direction == AudioManager.ADJUST_RAISE
+                && mRingerMode == AudioManager.RINGER_MODE_SILENT) {
+            newRingerMode = AudioManager.RINGER_MODE_VIBRATE;
+        }
+
+        if (newRingerMode != mRingerMode) {
+            setRingerMode(newRingerMode);
+
+            /*
+             * If we are changing ringer modes, do not increment/decrement the
+             * volume index. Instead, the handler for the message above will
+             * take care of changing the index.
+             */
+            adjustVolumeIndex = false;
+        }
+
+        return adjustVolumeIndex;
+    }
+
+    public boolean isStreamAffectedByRingerMode(int streamType) {
+        return (mRingerModeAffectedStreams & (1 << streamType)) != 0;
+    }
+
+    public boolean isStreamAffectedByMute(int streamType) {
+        return (mMuteAffectedStreams & (1 << streamType)) != 0;
+    }
+
+    private void ensureValidDirection(int direction) {
+        if (direction < AudioManager.ADJUST_LOWER || direction > AudioManager.ADJUST_RAISE) {
+            throw new IllegalArgumentException("Bad direction " + direction);
+        }
+    }
+
+    private void ensureValidStreamType(int streamType) {
+        if (streamType < 0 || streamType >= mStreamStates.length) {
+            throw new IllegalArgumentException("Bad stream type " + streamType);
+        }
+    }
+
+    private int getActiveStreamType(int suggestedStreamType) {
+        boolean isOffhook = false;
+        try {
+            ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
+            if (phone != null) isOffhook = phone.isOffhook();
+        } catch (RemoteException e) {
+            Log.w(TAG, "Couldn't connect to phone service", e);
+        }
+
+        if ((getRouting(AudioSystem.MODE_IN_CALL) & AudioSystem.ROUTE_BLUETOOTH_SCO) != 0) {
+            // Log.v(TAG, "getActiveStreamType: Forcing STREAM_BLUETOOTH_SCO...");
+            return AudioSystem.STREAM_BLUETOOTH_SCO;
+        } else if (isOffhook) {
+            // Log.v(TAG, "getActiveStreamType: Forcing STREAM_VOICE_CALL...");
+            return AudioSystem.STREAM_VOICE_CALL;
+        } else if (AudioSystem.isMusicActive()) {
+            // Log.v(TAG, "getActiveStreamType: Forcing STREAM_MUSIC...");
+            return AudioSystem.STREAM_MUSIC;
+        } else if (suggestedStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) {
+            // Log.v(TAG, "getActiveStreamType: Forcing STREAM_RING...");
+            return AudioSystem.STREAM_RING;
+        } else {
+            // Log.v(TAG, "getActiveStreamType: Returning suggested type " + suggestedStreamType);
+            return suggestedStreamType;
+        }
+    }
+
+    private void broadcastRingerMode() {
+        // Send sticky broadcast
+        if (ActivityManagerNative.isSystemReady()) {
+            Intent broadcast = new Intent(AudioManager.RINGER_MODE_CHANGED_ACTION);
+            broadcast.putExtra(AudioManager.EXTRA_RINGER_MODE, mRingerMode);
+            long origCallerIdentityToken = Binder.clearCallingIdentity();
+            mContext.sendStickyBroadcast(broadcast);
+            Binder.restoreCallingIdentity(origCallerIdentityToken);
+        }
+    }
+
+    private void broadcastVibrateSetting(int vibrateType) {
+        // Send broadcast
+        if (ActivityManagerNative.isSystemReady()) {
+            Intent broadcast = new Intent(AudioManager.VIBRATE_SETTING_CHANGED_ACTION);
+            broadcast.putExtra(AudioManager.EXTRA_VIBRATE_TYPE, vibrateType);
+            broadcast.putExtra(AudioManager.EXTRA_VIBRATE_SETTING, getVibrateSetting(vibrateType));
+            mContext.sendBroadcast(broadcast);
+        }
+    }
+
+    // Message helper methods
+    private static int getMsg(int baseMsg, int streamType) {
+        return (baseMsg & 0xffff) | streamType << 16;
+    }
+
+    private static int getMsgBase(int msg) {
+        return msg & 0xffff;
+    }
+
+    private static void sendMsg(Handler handler, int baseMsg, int streamType,
+            int existingMsgPolicy, int arg1, int arg2, Object obj, int delay) {
+        int msg = (streamType == SHARED_MSG) ? baseMsg : getMsg(baseMsg, streamType);
+
+        if (existingMsgPolicy == SENDMSG_REPLACE) {
+            handler.removeMessages(msg);
+        } else if (existingMsgPolicy == SENDMSG_NOOP && handler.hasMessages(msg)) {
+            return;
+        }
+
+        handler
+                .sendMessageDelayed(handler.obtainMessage(msg, arg1, arg2, obj), delay);
+    }
+
+    boolean checkAudioSettingsPermission(String method) {
+        if (mContext.checkCallingOrSelfPermission("android.permission.MODIFY_AUDIO_SETTINGS")
+                == PackageManager.PERMISSION_GRANTED) {
+            return true;
+        }
+        String msg = "Audio Settings Permission Denial: " + method + " from pid="
+                + Binder.getCallingPid()
+                + ", uid=" + Binder.getCallingUid();
+        Log.w(TAG, msg);
+        return false;
+    }
+
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Inner classes
+    ///////////////////////////////////////////////////////////////////////////
+
+    public class VolumeStreamState {
+        private final String mVolumeIndexSettingName;
+        private final String mLastAudibleVolumeIndexSettingName;
+        private final int mStreamType;
+
+        private final int[] mVolumes;
+        private int mIndex;
+        private int mLastAudibleIndex;
+        private ArrayList<VolumeDeathHandler> mDeathHandlers; //handles mute/solo requests client death
+
+        private VolumeStreamState(String settingName, int streamType, int[] volumes) {
+
+            mVolumeIndexSettingName = settingName;
+            mLastAudibleVolumeIndexSettingName = settingName + System.APPEND_FOR_LAST_AUDIBLE;
+
+            mStreamType = streamType;
+            mVolumes = volumes;
+
+            final ContentResolver cr = mContentResolver;
+            mIndex = getValidIndex(Settings.System.getInt(cr, mVolumeIndexSettingName, AudioManager.DEFAULT_STREAM_VOLUME[streamType]));
+            mLastAudibleIndex = getValidIndex(Settings.System.getInt(cr,
+                    mLastAudibleVolumeIndexSettingName, mIndex > 0 ? mIndex : AudioManager.DEFAULT_STREAM_VOLUME[streamType]));
+
+            AudioSystem.setVolume(streamType, volumes[mIndex]);
+            mDeathHandlers = new ArrayList<VolumeDeathHandler>();
+        }
+
+        /**
+         * Constructor to be used when there is no setting associated with the VolumeStreamState.
+         *
+         * @param defaultVolume Default volume of the stream to use.
+         * @param streamType Type of the stream.
+         * @param volumes Volumes levels associated with this stream.
+         */
+        private VolumeStreamState(int defaultVolume, int streamType, int[] volumes) {
+            mVolumeIndexSettingName = null;
+            mLastAudibleVolumeIndexSettingName = null;
+            mIndex = mLastAudibleIndex = defaultVolume;
+            mStreamType = streamType;
+            mVolumes = volumes;
+            AudioSystem.setVolume(mStreamType, defaultVolume);
+            mDeathHandlers = new ArrayList<VolumeDeathHandler>();
+        }
+
+        public boolean adjustIndex(int deltaIndex) {
+            return setIndex(mIndex + deltaIndex);
+        }
+
+        public boolean setIndex(int index) {
+            int oldIndex = mIndex;
+            mIndex = getValidIndex(index);
+
+            if (oldIndex != mIndex) {
+                if (mIndex > 0) {
+                    mLastAudibleIndex = mIndex;
+                }
+                return true;
+            } else {
+                return false;
+            }
+        }
+
+        public int getMaxIndex() {
+            return mVolumes.length - 1;
+        }
+
+        public void mute(IBinder cb, boolean state) {
+            VolumeDeathHandler handler = getDeathHandler(cb, state);
+            if (handler == null) {
+                Log.e(TAG, "Could not get client death handler for stream: "+mStreamType);
+                return;
+            }
+            handler.mute(state);
+        }
+
+        private int getValidIndex(int index) {
+            if (index < 0) {
+                return 0;
+            } else if (index >= mVolumes.length) {
+                return mVolumes.length - 1;
+            }
+
+            return index;
+        }
+
+        private class VolumeDeathHandler implements IBinder.DeathRecipient {
+            private IBinder mICallback; // To be notified of client's death
+            private int mMuteCount; // Number of active mutes for this client
+
+            VolumeDeathHandler(IBinder cb) {
+                mICallback = cb;
+            }
+
+            public void mute(boolean state) {
+                synchronized(mDeathHandlers) {
+                    if (state) {
+                        if (mMuteCount == 0) {
+                            // Register for client death notification
+                            try {
+                                mICallback.linkToDeath(this, 0);
+                                mDeathHandlers.add(this);
+                                // If the stream is not yet muted by any client, set lvel to 0
+                                if (muteCount() == 0) {
+                                    setIndex(0);
+                                    sendMsg(mAudioHandler, MSG_SET_SYSTEM_VOLUME, mStreamType, SENDMSG_NOOP, 0, 0,
+                                            VolumeStreamState.this, 0);
+                                }
+                            } catch (RemoteException e) {
+                                // Client has died!
+                                binderDied();
+                                mDeathHandlers.notify();
+                                return;
+                            }
+                        } else {
+                            Log.w(TAG, "stream: "+mStreamType+" was already muted by this client");
+                        }
+                        mMuteCount++;
+                    } else {
+                        if (mMuteCount == 0) {
+                            Log.e(TAG, "unexpected unmute for stream: "+mStreamType);
+                        } else {
+                            mMuteCount--;
+                            if (mMuteCount == 0) {
+                                // Unregistr from client death notification
+                                mDeathHandlers.remove(this);
+                                mICallback.unlinkToDeath(this, 0);
+                                if (muteCount() == 0) {
+                                    // If the stream is not mut any more, restore it's volume if
+                                    // ringer mode allows it
+                                    if (!isStreamAffectedByRingerMode(mStreamType) || mRingerMode == AudioManager.RINGER_MODE_NORMAL) {
+                                        setIndex(mLastAudibleIndex);
+                                        sendMsg(mAudioHandler, MSG_SET_SYSTEM_VOLUME, mStreamType, SENDMSG_NOOP, 0, 0,
+                                                VolumeStreamState.this, 0);
+                                    }
+                                }
+                            }
+                        }
+                    }
+                    mDeathHandlers.notify();
+                }
+            }
+
+            public void binderDied() {
+                Log.w(TAG, "Volume service client died for stream: "+mStreamType);
+                if (mMuteCount != 0) {
+                    // Reset all active mute requests from this client.
+                    mMuteCount = 1;
+                    mute(false);
+                }
+            }
+        }
+
+        private int muteCount() {
+            int count = 0;
+            int size = mDeathHandlers.size();
+            for (int i = 0; i < size; i++) {
+                count += mDeathHandlers.get(i).mMuteCount;
+            }
+            return count;
+        }
+
+        private VolumeDeathHandler getDeathHandler(IBinder cb, boolean state) {
+            synchronized(mDeathHandlers) {
+                VolumeDeathHandler handler;
+                int size = mDeathHandlers.size();
+                for (int i = 0; i < size; i++) {
+                    handler = mDeathHandlers.get(i);
+                    if (cb.equals(handler.mICallback)) {
+                        return handler;
+                    }
+                }
+                // If this is the first mute request for this client, create a new
+                // client death handler. Otherwise, it is an out of sequence unmute request.
+                if (state) {
+                    handler = new VolumeDeathHandler(cb);
+                } else {
+                    Log.w(TAG, "stream was not muted by this client");
+                    handler = null;
+                }
+                return handler;
+            }
+        }
+    }
+
+    /** Thread that handles native AudioSystem control. */
+    private class AudioSystemThread extends Thread {
+        AudioSystemThread() {
+            super("AudioService");
+        }
+
+        @Override
+        public void run() {
+            // Set this thread up so the handler will work on it
+            Looper.prepare();
+
+            synchronized(AudioService.this) {
+                mAudioHandler = new AudioHandler();
+
+                // Notify that the handler has been created
+                AudioService.this.notify();
+            }
+
+            // Listen for volume change requests that are set by VolumePanel
+            Looper.loop();
+        }
+    }
+
+    /** Handles internal volume messages in separate volume thread. */
+    private class AudioHandler extends Handler {
+
+        private void setSystemVolume(VolumeStreamState streamState) {
+
+            // Adjust volume
+            AudioSystem
+                    .setVolume(streamState.mStreamType, streamState.mVolumes[streamState.mIndex]);
+
+            // Post a persist volume msg
+            sendMsg(mAudioHandler, MSG_PERSIST_VOLUME, streamState.mStreamType,
+                    SENDMSG_REPLACE, 0, 0, streamState, PERSIST_DELAY);
+        }
+
+        private void persistVolume(VolumeStreamState streamState) {
+            System.putInt(mContentResolver, streamState.mVolumeIndexSettingName,
+                    streamState.mIndex);
+            System.putInt(mContentResolver, streamState.mLastAudibleVolumeIndexSettingName,
+                    streamState.mLastAudibleIndex);
+        }
+
+        private void persistRingerMode() {
+            System.putInt(mContentResolver, System.MODE_RINGER, mRingerMode);
+        }
+
+        private void persistVibrateSetting() {
+            System.putInt(mContentResolver, System.VIBRATE_ON, mVibrateSetting);
+        }
+
+        private void playSoundEffect(int effectType, int volume) {
+            synchronized (mSoundEffectsLock) {
+                if (mSoundPool == null) {
+                    return;
+                }
+
+                if (SOUND_EFFECT_FILES_MAP[effectType][1] > 0) {
+                    float v = (float) volume / 1000.0f;
+                    mSoundPool.play(SOUND_EFFECT_FILES_MAP[effectType][1], v, v, 0, 0, 1.0f);
+                } else {
+                    MediaPlayer mediaPlayer = new MediaPlayer();
+                    if (mediaPlayer != null) {
+                        try {
+                            String filePath = Environment.getRootDirectory() + SOUND_EFFECTS_PATH + SOUND_EFFECT_FILES[SOUND_EFFECT_FILES_MAP[effectType][0]];
+                            mediaPlayer.setDataSource(filePath);
+                            mediaPlayer.setAudioStreamType(AudioSystem.STREAM_SYSTEM);
+                            mediaPlayer.prepare();
+                            mediaPlayer.setOnCompletionListener(new OnCompletionListener() {
+                                public void onCompletion(MediaPlayer mp) {
+                                    cleanupPlayer(mp);
+                                }
+                            });
+                            mediaPlayer.setOnErrorListener(new OnErrorListener() {
+                                public boolean onError(MediaPlayer mp, int what, int extra) {
+                                    cleanupPlayer(mp);
+                                    return true;
+                                }
+                            });
+                            mediaPlayer.start();
+                        } catch (IOException ex) {
+                            Log.w(TAG, "MediaPlayer IOException: "+ex);
+                        } catch (IllegalArgumentException ex) {
+                            Log.w(TAG, "MediaPlayer IllegalArgumentException: "+ex);
+                        } catch (IllegalStateException ex) {
+                            Log.w(TAG, "MediaPlayer IllegalStateException: "+ex);
+                        }
+                    }
+                }
+            }
+        }
+
+        private void cleanupPlayer(MediaPlayer mp) {
+            if (mp != null) {
+                try {
+                    mp.stop();
+                    mp.release();
+                } catch (IllegalStateException ex) {
+                    Log.w(TAG, "MediaPlayer IllegalStateException: "+ex);
+                }
+            }
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            int baseMsgWhat = getMsgBase(msg.what);
+
+            switch (baseMsgWhat) {
+
+                case MSG_SET_SYSTEM_VOLUME:
+                    setSystemVolume((VolumeStreamState) msg.obj);
+                    break;
+
+                case MSG_PERSIST_VOLUME:
+                    persistVolume((VolumeStreamState) msg.obj);
+                    break;
+
+                case MSG_PERSIST_RINGER_MODE:
+                    persistRingerMode();
+                    break;
+
+                case MSG_PERSIST_VIBRATE_SETTING:
+                    persistVibrateSetting();
+                    break;
+
+                case MSG_MEDIA_SERVER_DIED:
+                    Log.e(TAG, "Media server died.");
+                    // Force creation of new IAudioflinger interface
+                    mMediaServerOk = false;
+                    AudioSystem.getMode();
+                    break;
+
+                case MSG_MEDIA_SERVER_STARTED:
+                    Log.e(TAG, "Media server started.");
+                    // Restore audio routing and stream volumes
+                    applyAudioSettings();
+                    int numStreamTypes = AudioSystem.getNumStreamTypes();
+                    for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
+                        int volume;
+                        VolumeStreamState streamState = mStreamStates[streamType];
+                        if (streamState.muteCount() == 0) {
+                            volume = streamState.mVolumes[streamState.mIndex];
+                        } else {
+                            volume = streamState.mVolumes[0];
+                        }
+                        AudioSystem.setVolume(streamType, volume);
+                    }
+                    setRingerMode(mRingerMode);
+                    mMediaServerOk = true;
+                    break;
+
+                case MSG_PLAY_SOUND_EFFECT:
+                    playSoundEffect(msg.arg1, msg.arg2);
+                    break;
+            }
+        }
+    }
+
+}
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
new file mode 100644
index 0000000..d0fa795
--- /dev/null
+++ b/media/java/android/media/AudioSystem.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2006 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.media;
+
+
+/* IF YOU CHANGE ANY OF THE CONSTANTS IN THIS FILE, DO NOT FORGET
+ * TO UPDATE THE CORRESPONDING NATIVE GLUE.  THANK YOU FOR YOUR COOPERATION
+ */
+
+/**
+ * @hide
+ */
+public class AudioSystem
+{
+    /* FIXME: Need to finalize this and correlate with native layer */
+    /*
+     * If these are modified, please also update Settings.System.VOLUME_SETTINGS
+     * and attrs.xml
+     */
+    /* The audio stream for phone calls */
+    public static final int STREAM_VOICE_CALL = 0;
+    /* The audio stream for system sounds */
+    public static final int STREAM_SYSTEM = 1;
+    /* The audio stream for the phone ring and message alerts */
+    public static final int STREAM_RING = 2;
+    /* The audio stream for music playback */
+    public static final int STREAM_MUSIC = 3;
+    /* The audio stream for alarms */
+    public static final int STREAM_ALARM = 4;
+    /* The audio stream for notifications */
+    public static final int STREAM_NOTIFICATION = 5;
+    /* @hide The audio stream for phone calls when connected on bluetooth */
+    public static final int STREAM_BLUETOOTH_SCO = 6;
+    /**
+     * @deprecated Use {@link #numStreamTypes() instead}
+     */
+    public static final int NUM_STREAMS = 5;
+
+    // Expose only the getter method publicly so we can change it in the future
+    private static final int NUM_STREAM_TYPES = 7;
+    public static final int getNumStreamTypes() { return NUM_STREAM_TYPES; }
+
+    /* max and min volume levels */
+    /* Maximum volume setting, for use with setVolume(int,int) */
+    public static final int MAX_VOLUME = 100;
+    /* Minimum volume setting, for use with setVolume(int,int) */
+    public static final int MIN_VOLUME = 0;
+
+    /*
+     * Sets the volume of a specified audio stream.
+     *
+     * param type   the stream type to set the volume of (e.g. STREAM_MUSIC)
+     * param volume the volume level to set (0-100)
+     * return command completion status see AUDIO_STATUS_OK, see AUDIO_STATUS_ERROR
+     */
+    public static native int setVolume(int type, int volume);
+
+    /*
+     * Returns the volume of a specified audio stream.
+     *
+     * param type the stream type to get the volume of (e.g. STREAM_MUSIC)
+     * return the current volume (0-100)
+     */
+    public static native int getVolume(int type);
+
+    /*
+     * Sets the microphone mute on or off.
+     *
+     * param on set <var>true</var> to mute the microphone;
+     *           <var>false</var> to turn mute off
+     * return command completion status see AUDIO_STATUS_OK, see AUDIO_STATUS_ERROR
+     */
+    public static native int muteMicrophone(boolean on);
+
+    /*
+     * Checks whether the microphone mute is on or off.
+     *
+     * return true if microphone is muted, false if it's not
+     */
+    public static native boolean isMicrophoneMuted();
+
+    /*
+     * Sets the audio mode.
+     *
+     * param mode  the requested audio mode (NORMAL, RINGTONE, or IN_CALL).
+     *              Informs the HAL about the current audio state so that
+     *              it can route the audio appropriately.
+     * return command completion status see AUDIO_STATUS_OK, see AUDIO_STATUS_ERROR
+     */
+    public static native int setMode(int mode);
+
+    /*
+     * Returns the current audio mode.
+     *
+     * return      the current audio mode (NORMAL, RINGTONE, or IN_CALL).
+     *              Returns the current current audio state from the HAL.
+     */
+    public static native int getMode();
+
+    /* modes for setMode/getMode/setRoute/getRoute */
+    public static final int MODE_INVALID            = -2;
+    public static final int MODE_CURRENT            = -1;
+    public static final int MODE_NORMAL             = 0;
+    public static final int MODE_RINGTONE           = 1;
+    public static final int MODE_IN_CALL            = 2;
+    public static final int NUM_MODES               = 3;
+
+
+    /* Routing bits for setRouting/getRouting API */
+    public static final int ROUTE_EARPIECE          = (1 << 0);
+    public static final int ROUTE_SPEAKER           = (1 << 1);
+
+    /** @deprecated use {@link #ROUTE_BLUETOOTH_SCO} */
+    @Deprecated public static final int ROUTE_BLUETOOTH = (1 << 2);
+    public static final int ROUTE_BLUETOOTH_SCO     = (1 << 2);
+    public static final int ROUTE_HEADSET           = (1 << 3);
+    public static final int ROUTE_BLUETOOTH_A2DP    = (1 << 4);
+    public static final int ROUTE_ALL               = 0xFFFFFFFF;
+
+    /*
+     * Sets the audio routing for a specified mode
+     *
+     * param mode   audio mode to change route. E.g., MODE_RINGTONE.
+     * param routes bit vector of routes requested, created from one or
+     *               more of ROUTE_xxx types. Set bits indicate that route should be on
+     * param mask   bit vector of routes to change, created from one or more of
+     * ROUTE_xxx types. Unset bits indicate the route should be left unchanged
+     * return command completion status see AUDIO_STATUS_OK, see AUDIO_STATUS_ERROR
+     */
+    public static native int setRouting(int mode, int routes, int mask);
+
+    /*
+     * Returns the current audio routing bit vector for a specified mode.
+     *
+     * param mode audio mode to change route (e.g., MODE_RINGTONE)
+     * return an audio route bit vector that can be compared with ROUTE_xxx
+     * bits
+     */
+    public static native int getRouting(int mode);
+
+    /*
+     * Checks whether any music is active.
+     *
+     * return true if any music tracks are active.
+     */
+    public static native boolean isMusicActive();
+
+    /*
+     * Sets a generic audio configuration parameter. The use of these parameters
+     * are platform dependant, see libaudio
+     *
+     * ** Temporary interface - DO NOT USE
+     *
+     * TODO: Replace with a more generic key:value get/set mechanism
+     *
+     * param key   name of parameter to set. Must not be null.
+     * param value value of parameter. Must not be null.
+     */
+    public static native void setParameter(String key, String value);
+
+    /*
+    private final static String TAG = "audio";
+
+    private void log(String msg) {
+        Log.d(TAG, "[AudioSystem] " + msg);
+    }
+    */
+
+    // These match the enum in libs/android_runtime/android_media_AudioSystem.cpp
+    /* Command sucessful or Media server restarted. see ErrorCallback */
+    public static final int AUDIO_STATUS_OK = 0;
+    /* Command failed or unspecified audio error.  see ErrorCallback */
+    public static final int AUDIO_STATUS_ERROR = 1;
+    /* Media server died. see ErrorCallback */
+    public static final int AUDIO_STATUS_SERVER_DIED = 100;
+
+    private static ErrorCallback mErrorCallback;
+
+    /*
+     * Handles the audio error callback.
+     */
+    public interface ErrorCallback
+    {
+        /*
+         * Callback for audio server errors.
+         * param error   error code:
+         * - AUDIO_STATUS_OK
+         * - AUDIO_STATUS_SERVER_DIED
+         * - UDIO_STATUS_ERROR
+         */
+        void onError(int error);
+    };
+
+    /*
+     * Registers a callback to be invoked when an error occurs.
+     * param cb the callback to run
+     */
+    public static void setErrorCallback(ErrorCallback cb)
+    {
+        mErrorCallback = cb;
+    }
+
+    private static void errorCallbackFromNative(int error)
+    {
+        if (mErrorCallback != null) {
+            mErrorCallback.onError(error);
+        }
+    }
+}
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
new file mode 100644
index 0000000..997cd44
--- /dev/null
+++ b/media/java/android/media/AudioTrack.java
@@ -0,0 +1,1050 @@
+/*
+ * Copyright (C) 2008 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.media;
+
+import java.lang.ref.WeakReference;
+import java.lang.IllegalArgumentException;
+import java.lang.IllegalStateException;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.media.AudioManager;
+import android.util.Log;
+
+
+/**
+ * The AudioTrack class manages and plays a single audio resource for Java applications.
+ * It allows to stream PCM audio buffers to the audio hardware for playback. This is
+ * achieved by "pushing" the data to the AudioTrack object using one of the
+ *  {@link #write(byte[], int, int)} and {@link #write(short[], int, int)} methods.
+ * <p>An AudioTrack instance can operate under two modes: static of streaming.<br>
+ * The Streaming mode consists in continuously writing data to the AudioTrack, using one
+ * of the write() methods. These are blocking and return when the data has been transferred
+ * from the Java layer to the native layer, and is queued for playback. The streaming mode
+ *  is most useful when playing blocks of audio data that for instance are:
+ * <ul>
+ *   <li>too big to fit in memory because of the duration of the sound to play,</li>
+ *   <li>too big to fit in memory because of the characteristics of the audio data
+ *         (high sampling rate, bits per sample ...)</li>
+ *   <li>chosen, received or generated as the audio keeps playing.</li>
+ * </ul>
+ * The static mode is to be chosen when dealing with short sounds that fit in memory and
+ * that need to be played with the smallest latency possible. Static mode AudioTrack instances can
+ * play the sound without the need to transfer the audio data from Java to the audio hardware
+ * each time the sound is to be played. The static mode will therefore be preferred for UI and
+ * game sounds that are played often, and with the smallest overhead possible.
+ * <p>Upon creation, an AudioTrack object initializes its associated audio buffer.
+ * The size of this buffer, specified during the construction, determines how long an AudioTrack
+ * can play before running out of data.<br>
+ * For an AudioTrack using the static mode, this size is the maximum size of the sound that can
+ * be played from it.<br>
+ * For the streaming mode, data will be written to the hardware in chunks of
+ * sizes inferior to the total buffer size.
+ */
+public class AudioTrack
+{
+    //---------------------------------------------------------
+    // Constants
+    //--------------------
+    /** Minimum value for a channel volume */
+    private static final float VOLUME_MIN = 0.0f;
+    /** Maximum value for a channel volume */
+    private static final float VOLUME_MAX = 1.0f;
+
+    /** state of an AudioTrack this is stopped */
+    public static final int PLAYSTATE_STOPPED = 1;  // matches SL_PLAYSTATE_STOPPED
+    /** state of an AudioTrack this is paused */
+    public static final int PLAYSTATE_PAUSED  = 2;  // matches SL_PLAYSTATE_PAUSED
+    /** state of an AudioTrack this is playing */
+    public static final int PLAYSTATE_PLAYING = 3;  // matches SL_PLAYSTATE_PLAYING
+
+    /**
+     * Creation mode where audio data is transferred from Java to the native layer
+     * only once before the audio starts playing.
+     */
+    public static final int MODE_STATIC = 0;
+    /**
+     * Creation mode where audio data is streamed from Java to the native layer
+     * as the audio is playing.
+     */
+    public static final int MODE_STREAM = 1;
+
+    /**
+     * State of an AudioTrack that was not successfully initialized upon creation
+     */
+    public static final int STATE_UNINITIALIZED = 0;
+    /**
+     * State of an AudioTrack that is ready to be used.
+     */
+    public static final int STATE_INITIALIZED   = 1;
+    /**
+     * State of a successfully initialized AudioTrack that uses static data,
+     * but that hasn't received that data yet.
+     */
+    public static final int STATE_NO_STATIC_DATA = 2;
+
+    // Error codes:
+    // to keep in sync with frameworks/base/core/jni/android_media_AudioTrack.cpp
+    /**
+     * Denotes a successful operation.
+     */
+    public  static final int SUCCESS                               = 0;
+    /**
+     * Denotes a generic operation failure.
+     */
+    public  static final int ERROR                                 = -1;
+    /**
+     * Denotes a failure due to the use of an invalid value.
+     */
+    public  static final int ERROR_BAD_VALUE                       = -2;
+    /**
+     * Denotes a failure due to the improper use of a method.
+     */
+    public  static final int ERROR_INVALID_OPERATION               = -3;
+
+    private static final int ERROR_NATIVESETUP_AUDIOSYSTEM         = -16;
+    private static final int ERROR_NATIVESETUP_INVALIDCHANNELCOUNT = -17;
+    private static final int ERROR_NATIVESETUP_INVALIDFORMAT       = -18;
+    private static final int ERROR_NATIVESETUP_INVALIDSTREAMTYPE   = -19;
+    private static final int ERROR_NATIVESETUP_NATIVEINITFAILED    = -20;
+
+    // Events:
+    // to keep in sync with frameworks/base/include/media/AudioTrack.h
+    /**
+     * Event id for when the playback head has reached a previously set marker.
+     */
+    private static final int NATIVE_EVENT_MARKER  = 3;
+    /**
+     * Event id for when the previously set update period has passed during playback.
+     */
+    private static final int NATIVE_EVENT_NEW_POS = 4;
+
+    private final static String TAG = "AudioTrack-Java";
+
+
+    //--------------------------------------------------------------------------
+    // Member variables
+    //--------------------
+    /**
+     * Indicates the state of the AudioTrack instance
+     */
+    private int mState = STATE_UNINITIALIZED;
+    /**
+     * Indicates the play state of the AudioTrack instance
+     */
+    private int mPlayState = PLAYSTATE_STOPPED;
+    /**
+     * Lock to make sure mPlayState updates are reflecting the actual state of the object.
+     */
+    private final Object mPlayStateLock = new Object();
+    /**
+     * The listener the AudioTrack notifies previously set marker is reached.
+     *  @see #setMarkerReachedListener(OnMarkerReachedListener)
+     */
+    private OnMarkerReachedListener mMarkerListener = null;
+    /**
+     * Lock to protect marker listener updates against event notifications
+     */
+    private final Object mMarkerListenerLock = new Object();
+    /**
+     * The listener the AudioTrack notifies periodically during playback.
+     *  @see #setPeriodicNotificationListener(OnPeriodicNotificationListener)
+     */
+    private OnPeriodicNotificationListener mPeriodicListener = null;
+    /**
+     * Lock to protect periodic listener updates against event notifications
+     */
+    private final Object mPeriodicListenerLock = new Object();
+    /**
+     * Size of the native audio buffer.
+     */
+    private int mNativeBufferSizeInBytes = 0;
+    /**
+     * Handler for events coming from the native code
+     */
+    private NativeEventHandler mNativeEventHandler = null;
+    /**
+     * The audio data sampling rate in Hz.
+     */
+    private int mSampleRate = 22050;
+    /**
+     * The number of input audio channels (1 is mono, 2 is stereo)
+     */
+    private int mChannelCount = 1;
+    /**
+     * The type of the audio stream to play. See
+     *   {@link AudioManager#STREAM_VOICE_CALL}, {@link AudioManager#STREAM_SYSTEM},
+     *   {@link AudioManager#STREAM_RING}, {@link AudioManager#STREAM_MUSIC} and
+     *   {@link AudioManager#STREAM_ALARM}
+     */
+    private int mStreamType = AudioManager.STREAM_MUSIC;
+    /**
+     * The way audio is consumed by the hardware, streaming or static.
+     */
+    private int mDataLoadMode = MODE_STREAM;
+    /**
+     * The current audio channel configuration
+     */
+    private int mChannelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_MONO;
+    /**
+     * The encoding of the audio samples.
+     * @see AudioFormat#ENCODING_PCM_8BIT
+     * @see AudioFormat#ENCODING_PCM_16BIT
+     */
+    private int mAudioFormat = AudioFormat.ENCODING_PCM_16BIT;
+
+
+    //--------------------------------
+    // Used exclusively by native code
+    //--------------------
+    /**
+     * Accessed by native methods: provides access to C++ AudioTrack object
+     */
+    @SuppressWarnings("unused")
+    private int mNativeTrackInJavaObj;
+    /**
+     * Accessed by native methods: provides access to the JNI data (i.e. resources used by
+     * the native AudioTrack object, but not stored in it).
+     */
+    @SuppressWarnings("unused")
+    private int mJniData;
+
+
+    //--------------------------------------------------------------------------
+    // Constructor, Finalize
+    //--------------------
+    /**
+     * Class constructor.
+     * @param streamType the type of the audio stream. See
+
+     *   {@link AudioManager#STREAM_VOICE_CALL}, {@link AudioManager#STREAM_SYSTEM},
+     *   {@link AudioManager#STREAM_RING}, {@link AudioManager#STREAM_MUSIC} and
+     *   {@link AudioManager#STREAM_ALARM}
+     * @param sampleRateInHz the sample rate expressed in Hertz. Examples of rates are (but
+     *   not limited to) 44100, 22050 and 11025.
+     * @param channelConfig describes the configuration of the audio channels.
+
+     *   See {@link AudioFormat#CHANNEL_CONFIGURATION_MONO} and
+     *   {@link AudioFormat#CHANNEL_CONFIGURATION_STEREO}
+
+     * @param audioFormat the format in which the audio data is represented.
+     *   See {@link AudioFormat#ENCODING_PCM_16BIT} and
+     *   {@link AudioFormat#ENCODING_PCM_8BIT}
+     * @param bufferSizeInBytes the total size (in bytes) of the buffer where audio data is read
+     *   from for playback. If using the AudioTrack in streaming mode, you can write data into
+     *   this buffer in smaller chunks than this size. If using the AudioTrack in static mode,
+     *   this is the maximum size of the sound that will be played for this instance.
+     * @param mode streaming or static buffer. See {@link #MODE_STATIC} and {@link #MODE_STREAM}
+     * @throws java.lang.IllegalArgumentException
+     */
+    public AudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat,
+            int bufferSizeInBytes, int mode)
+    throws IllegalArgumentException {
+        mState = STATE_UNINITIALIZED;
+
+        audioParamCheck(streamType, sampleRateInHz, channelConfig, audioFormat, mode);
+
+        audioBuffSizeCheck(bufferSizeInBytes);
+
+        // native initialization
+        int initResult = native_setup(new WeakReference<AudioTrack>(this),
+                mStreamType, mSampleRate, mChannelCount, mAudioFormat,
+                mNativeBufferSizeInBytes, mDataLoadMode);
+        if (initResult != SUCCESS) {
+            loge("Error code "+initResult+" when initializing AudioTrack.");
+            return; // with mState == STATE_UNINITIALIZED
+        }
+
+        if (mDataLoadMode == MODE_STATIC) {
+            mState = STATE_NO_STATIC_DATA;
+        } else {
+            mState = STATE_INITIALIZED;
+        }
+    }
+
+
+    // Convenience method for the constructor's parameter checks.
+    // This is where constructor IllegalArgumentException-s are thrown
+    // postconditions:
+    //    mStreamType is valid
+    //    mChannelCount is valid
+    //    mAudioFormat is valid
+    //    mSampleRate is valid
+    //    mDataLoadMode is valid
+    private void audioParamCheck(int streamType, int sampleRateInHz,
+                                 int channelConfig, int audioFormat, int mode) {
+
+        //--------------
+        // stream type
+        if( (streamType != AudioManager.STREAM_ALARM) && (streamType != AudioManager.STREAM_MUSIC)
+           && (streamType != AudioManager.STREAM_RING) && (streamType != AudioManager.STREAM_SYSTEM)
+           && (streamType != AudioManager.STREAM_VOICE_CALL)
+           && (streamType != AudioManager.STREAM_NOTIFICATION)
+           && (streamType != AudioManager.STREAM_BLUETOOTH_SCO)) {
+            throw (new IllegalArgumentException("Invalid stream type."));
+        } else {
+            mStreamType = streamType;
+        }
+
+        //--------------
+        // sample rate
+        if ( (sampleRateInHz < 4000) || (sampleRateInHz > 48000) ) {
+            throw (new IllegalArgumentException(sampleRateInHz
+                    + "Hz is not a supported sample rate."));
+        } else {
+            mSampleRate = sampleRateInHz;
+        }
+
+        //--------------
+        // channel config
+        switch (channelConfig) {
+        case AudioFormat.CHANNEL_CONFIGURATION_DEFAULT:
+        case AudioFormat.CHANNEL_CONFIGURATION_MONO:
+            mChannelCount = 1;
+            mChannelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_MONO;
+            break;
+        case AudioFormat.CHANNEL_CONFIGURATION_STEREO:
+            mChannelCount = 2;
+            mChannelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_STEREO;
+            break;
+        default:
+            mChannelCount = 0;
+            mChannelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_INVALID;
+            throw(new IllegalArgumentException("Unsupported channel configuration."));
+        }
+
+        //--------------
+        // audio format
+        switch (audioFormat) {
+        case AudioFormat.ENCODING_DEFAULT:
+            mAudioFormat = AudioFormat.ENCODING_PCM_16BIT;
+            break;
+        case AudioFormat.ENCODING_PCM_16BIT:
+        case AudioFormat.ENCODING_PCM_8BIT:
+            mAudioFormat = audioFormat;
+            break;
+        default:
+            mAudioFormat = AudioFormat.ENCODING_INVALID;
+            throw(new IllegalArgumentException("Unsupported sample encoding."
+                + " Should be ENCODING_PCM_8BIT or ENCODING_PCM_16BIT."));
+        }
+
+        //--------------
+        // audio load mode
+        if ( (mode != MODE_STREAM) && (mode != MODE_STATIC) ) {
+            throw(new IllegalArgumentException("Invalid mode."));
+        } else {
+            mDataLoadMode = mode;
+        }
+    }
+
+
+    // Convenience method for the contructor's audio buffer size check.
+    // preconditions:
+    //    mChannelCount is valid
+    //    mAudioFormat is valid
+    // postcondition:
+    //    mNativeBufferSizeInBytes is valid (multiple of frame size, positive)
+    private void audioBuffSizeCheck(int audioBufferSize) {
+        // NB: this section is only valid with PCM data.
+        //     To update when supporting compressed formats
+        int frameSizeInBytes = mChannelCount
+                * (mAudioFormat == AudioFormat.ENCODING_PCM_8BIT ? 1 : 2);
+        if ((audioBufferSize % frameSizeInBytes != 0) || (audioBufferSize < 1)) {
+            throw (new IllegalArgumentException("Invalid audio buffer size."));
+        }
+
+        mNativeBufferSizeInBytes = audioBufferSize;
+    }
+
+
+    // Convenience method for the creation of the native event handler
+    // It is called only when a non-null event listener is set.
+    // precondition:
+    //    mNativeEventHandler is null
+    private void createNativeEventHandler() {
+        Looper looper;
+        if ((looper = Looper.myLooper()) != null) {
+            mNativeEventHandler = new NativeEventHandler(this, looper);
+        } else if ((looper = Looper.getMainLooper()) != null) {
+            mNativeEventHandler = new NativeEventHandler(this, looper);
+        } else {
+            mNativeEventHandler = null;
+        }
+    }
+
+
+    /**
+     * Releases the native AudioTrack resources.
+     */
+    public void release() {
+        // even though native_release() stops the native AudioTrack, we need to stop
+        // AudioTrack subclasses too.
+        try {
+            stop();
+        } catch(IllegalStateException ise) { 
+            // don't raise an exception, we're releasing the resources.
+        }
+        native_release();
+        mState = STATE_UNINITIALIZED;
+    }
+
+    @Override
+    protected void finalize() {
+        native_finalize();
+    }
+
+    //--------------------------------------------------------------------------
+    // Getters
+    //--------------------
+    /**
+     * Returns the minimum valid volume value. Volume values set under this one will
+     * be clamped at this value.
+     * @return the minimum volume expressed as a linear attenuation.
+     */
+    static public float getMinVolume() {
+        return AudioTrack.VOLUME_MIN;
+    }
+
+    /**
+     * Returns the maximum valid volume value. Volume values set above this one will
+     * be clamped at this value.
+     * @return the maximum volume expressed as a linear attenuation.
+     */
+    static public float getMaxVolume() {
+        return AudioTrack.VOLUME_MAX;
+    }
+
+    /**
+     * Returns the configured audio data sample rate in Hz
+     */
+    public int getSampleRate() {
+        return mSampleRate;
+    }
+    
+    /**
+     * @hide
+     * Returns the current playback rate in Hz. Note that this rate may differ from one set using
+     * {@link #setPlaybackRate(int)} as the value effectively set is implementation-dependent.
+     */
+    public int getPlaybackRate() {
+        return native_get_playback_rate();
+    }
+
+    /**
+     * Returns the configured audio data format. See {@link AudioFormat#ENCODING_PCM_16BIT}
+     * and {@link AudioFormat#ENCODING_PCM_8BIT}.
+     */
+    public int getAudioFormat() {
+        return mAudioFormat;
+    }
+
+    /**
+     * Returns the type of audio stream this AudioTrack is configured for.
+     * Compare the result against {@link AudioManager#STREAM_VOICE_CALL},
+     * {@link AudioManager#STREAM_SYSTEM}, {@link AudioManager#STREAM_RING},
+     * {@link AudioManager#STREAM_MUSIC} or {@link AudioManager#STREAM_ALARM}
+     */
+    public int getStreamType() {
+        return mStreamType;
+    }
+
+    /**
+     * Returns the configured channel configuration.
+
+     * See {@link AudioFormat#CHANNEL_CONFIGURATION_MONO}
+     * and {@link AudioFormat#CHANNEL_CONFIGURATION_STEREO}.
+     */
+    public int getChannelConfiguration() {
+        return mChannelConfiguration;
+    }
+
+    /**
+     * Returns the configured number of channels.
+     */
+    public int getChannelCount() {
+        return mChannelCount;
+    }
+
+    /**
+     * Returns the state of the AudioTrack instance. This is useful after the
+     * AudioTrack instance has been created to check if it was initialized
+     * properly. This ensures that the appropriate hardware resources have been
+     * acquired.
+     */
+    public int getState() {
+        return mState;
+    }
+
+    /**
+     * Returns the playback state of the AudioTrack instance.
+     * @see #PLAYSTATE_STOPPED
+     * @see #PLAYSTATE_PAUSED
+     * @see #PLAYSTATE_PLAYING
+     */
+    public int getPlayState() {
+        return mPlayState;
+    }
+
+    /**
+     *  Returns the native frame count used by the hardware
+     */
+    protected int getNativeFrameCount() {
+        return native_get_native_frame_count();
+    }
+
+    /**
+     * @return marker position in frames
+     */
+    public int getNotificationMarkerPosition() {
+        return native_get_marker_pos();
+    }
+
+    /**
+     * @return update period in frames
+     */
+    public int getPositionNotificationPeriod() {
+        return native_get_pos_update_period();
+    }
+
+    /**
+     * @return playback head position in frames
+     */
+    public int getPlaybackHeadPosition() {
+        return native_get_position();
+    }
+
+    /**
+     *  Returns the hardware output sample rate
+     */
+    static public int getNativeOutputSampleRate(int streamType) {
+        return native_get_output_sample_rate(streamType);
+    }
+    
+    /**
+     * {@hide}
+     * Returns the minimum buffer size required for the successful creation of an AudioTrack
+     * object to be created in the {@link #MODE_STREAM} mode.
+     * @param sampleRateInHz the sample rate expressed in Hertz.
+     * @param channelConfig describes the configuration of the audio channels. 
+     *   See {@link AudioFormat#CHANNEL_CONFIGURATION_MONO} and
+     *   {@link AudioFormat#CHANNEL_CONFIGURATION_STEREO}
+     * @param audioFormat the format in which the audio data is represented. 
+     *   See {@link AudioFormat#ENCODING_PCM_16BIT} and 
+     *   {@link AudioFormat#ENCODING_PCM_8BIT}
+     * @return {@link #ERROR_BAD_VALUE} if an invalid parameter was passed,
+     *   or {@link #ERROR} if the implementation was unable to query the hardware for its output 
+     *     properties, 
+     *   or the minimum buffer size expressed  in number of bytes.
+     */
+    static public int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat) {
+        int channelCount = 0;
+        switch(channelConfig) {
+        case AudioFormat.CHANNEL_CONFIGURATION_MONO:
+            channelCount = 1;
+            break;
+        case AudioFormat.CHANNEL_CONFIGURATION_STEREO:
+            channelCount = 2;
+            break;
+        default:
+            loge("getMinBufferSize(): Invalid channel configuration.");
+            return AudioTrack.ERROR_BAD_VALUE;
+        }
+        
+        if ((audioFormat != AudioFormat.ENCODING_PCM_16BIT) 
+            && (audioFormat != AudioFormat.ENCODING_PCM_8BIT)) {
+            loge("getMinBufferSize(): Invalid audio format.");
+            return AudioTrack.ERROR_BAD_VALUE;
+        }
+        
+        if ( (sampleRateInHz < 4000) || (sampleRateInHz > 48000) ) {
+            loge("getMinBufferSize(): " + sampleRateInHz +"Hz is not a supported sample rate.");
+            return AudioTrack.ERROR_BAD_VALUE;
+        }
+        
+        int size = native_get_min_buff_size(sampleRateInHz, channelCount, audioFormat);
+        if ((size == -1) || (size == 0)) {
+            loge("getMinBufferSize(): error querying hardware");
+            return AudioTrack.ERROR;
+        }
+        else {
+            return size;
+        }
+    }
+
+
+    //--------------------------------------------------------------------------
+    // Initialization / configuration
+    //--------------------
+    /**
+     * Sets the listener the AudioTrack notifies when a previously set marker is reached.
+     * @param listener
+     */
+    public void setMarkerReachedListener(OnMarkerReachedListener listener) {
+        synchronized (mMarkerListenerLock) {
+            mMarkerListener = listener;
+        }
+        if ((listener != null) && (mNativeEventHandler == null)) {
+            createNativeEventHandler();
+        }
+    }
+
+
+    /**
+     * Sets the listener the AudioTrack notifies periodically during playback.
+     * @param listener
+     */
+    public void setPeriodicNotificationListener(OnPeriodicNotificationListener listener) {
+        synchronized (mPeriodicListenerLock) {
+            mPeriodicListener = listener;
+        }
+        if ((listener != null) && (mNativeEventHandler == null)) {
+            createNativeEventHandler();
+        }
+    }
+
+
+     /**
+     * Sets the specified left/right output volume values on the AudioTrack. Values are clamped
+     * to the ({@link #getMinVolume()}, {@link #getMaxVolume()}) interval if outside this range.
+     * @param leftVolume output attenuation for the left channel. A value of 0.0f is silence,
+     *      a value of 1.0f is no attenuation.
+     * @param rightVolume output attenuation for the right channel
+     * @return error code or success, see {@link #SUCCESS},
+     *    {@link #ERROR_INVALID_OPERATION}
+     */
+    public int setStereoVolume(float leftVolume, float rightVolume) {
+        if (mState != STATE_INITIALIZED) {
+            return ERROR_INVALID_OPERATION;
+        }
+
+        // clamp the volumes
+        if (leftVolume < getMinVolume()) {
+            leftVolume = getMinVolume();
+        }
+        if (leftVolume > getMaxVolume()) {
+            leftVolume = getMaxVolume();
+        }
+        if (rightVolume < getMinVolume()) {
+            rightVolume = getMinVolume();
+        }
+        if (rightVolume > getMaxVolume()) {
+            rightVolume = getMaxVolume();
+        }
+
+        native_setVolume(leftVolume, rightVolume);
+
+        return SUCCESS;
+    }
+
+
+    /**
+     * Sets the playback sample rate for this track. This sets the sampling rate at which
+     * the audio data will be consumed and played back, not the original sampling rate of the
+     * content. Setting it to half the sample rate of the content will cause the playback to
+     * last twice as long, but will also result result in a negative pitch shift.
+     * The current implementation supports a maximum sample rate of twice the hardware output
+     * sample rate (see {@link #getNativeOutputSampleRate(int)}). Use {@link #getSampleRate()} to
+     * check the rate actually used in hardware after potential clamping.
+     * @param sampleRateInHz
+     * @return error code or success, see {@link #SUCCESS}, {@link #ERROR_BAD_VALUE},
+     *    {@link #ERROR_INVALID_OPERATION}
+     */
+    public int setPlaybackRate(int sampleRateInHz) {
+        if (mState != STATE_INITIALIZED) {
+            return ERROR_INVALID_OPERATION;
+        }
+        if (sampleRateInHz <= 0) {
+            return ERROR_BAD_VALUE;
+        }
+        native_set_playback_rate(sampleRateInHz);
+        return SUCCESS;
+    }
+
+
+    /**
+     *
+     * @param markerInFrames marker in frames
+     * @return error code or success, see {@link #SUCCESS}, {@link #ERROR_BAD_VALUE},
+     *  {@link #ERROR_INVALID_OPERATION}
+     */
+    public int setNotificationMarkerPosition(int markerInFrames) {
+        if (mState != STATE_INITIALIZED) {
+            return ERROR_INVALID_OPERATION;
+        }
+        return native_set_marker_pos(markerInFrames);
+    }
+
+
+    /**
+     * @param periodInFrames update period in frames
+     * @return error code or success, see {@link #SUCCESS}, {@link #ERROR_INVALID_OPERATION}
+     */
+    public int setPositionNotificationPeriod(int periodInFrames) {
+        if (mState != STATE_INITIALIZED) {
+            return ERROR_INVALID_OPERATION;
+        }
+        return native_set_pos_update_period(periodInFrames);
+    }
+
+
+    /**
+     * Sets the playback head position. The track must be stopped for the position to be changed.
+     * @param positionInFrames playback head position in frames
+     * @return error code or success, see {@link #SUCCESS}, {@link #ERROR_BAD_VALUE},
+     *    {@link #ERROR_INVALID_OPERATION}
+     */
+    public int setPlaybackHeadPosition(int positionInFrames) {
+        synchronized(mPlayStateLock) {
+            if ((mPlayState == PLAYSTATE_STOPPED) || (mPlayState == PLAYSTATE_PAUSED)) {
+                return native_set_position(positionInFrames);
+            } else {
+                return ERROR_INVALID_OPERATION;
+            }
+        }
+    }
+
+    /**
+     * Sets the loop points and the loop count. The loop can be infinite.
+     * @param startInFrames loop start marker in frames
+     * @param endInFrames loop end marker in frames
+     * @param loopCount the number of times the loop is looped.
+     *    A value of -1 means infinite looping.
+     * @return error code or success, see {@link #SUCCESS}, {@link #ERROR_BAD_VALUE},
+     *    {@link #ERROR_INVALID_OPERATION}
+     */
+    public int setLoopPoints(int startInFrames, int endInFrames, int loopCount) {
+        if (mDataLoadMode == MODE_STREAM) {
+            return ERROR_INVALID_OPERATION;
+        }
+        return native_set_loop(startInFrames, endInFrames, loopCount);
+    }
+
+    /**
+     * Sets the initialization state of the instance. To be used in an AudioTrack subclass
+     * constructor to set a subclass-specific post-initialization state.
+     * @param state the state of the AudioTrack instance
+     */
+    protected void setState(int state) {
+        mState = state;
+    }
+
+
+    //---------------------------------------------------------
+    // Transport control methods
+    //--------------------
+    /**
+     * Starts playing an AudioTrack.
+     * @throws IllegalStateException
+     */
+    public void play()
+    throws IllegalStateException {
+        if (mState != STATE_INITIALIZED) {
+            throw(new IllegalStateException("play() called on uninitialized AudioTrack."));
+        }
+
+        synchronized(mPlayStateLock) {
+            native_start();
+            mPlayState = PLAYSTATE_PLAYING;
+        }
+    }
+
+    /**
+     * Stops playing the audio data.
+     * @throws IllegalStateException
+     */
+    public void stop()
+    throws IllegalStateException {
+        if (mState != STATE_INITIALIZED) {
+            throw(new IllegalStateException("stop() called on uninitialized AudioTrack."));
+        }
+
+        // stop playing
+        synchronized(mPlayStateLock) {
+            native_stop();
+            mPlayState = PLAYSTATE_STOPPED;
+        }
+    }
+
+    /**
+     * Pauses the playback of the audio data.
+     * @throws IllegalStateException
+     */
+    public void pause()
+    throws IllegalStateException {
+        if (mState != STATE_INITIALIZED) {
+            throw(new IllegalStateException("pause() called on uninitialized AudioTrack."));
+        }
+        //logd("pause()");
+
+        // pause playback
+        synchronized(mPlayStateLock) {
+            native_pause();
+            mPlayState = PLAYSTATE_PAUSED;
+        }
+    }
+
+
+    //---------------------------------------------------------
+    // Audio data supply
+    //--------------------
+
+    /**
+     * Flushes the audio data currently queued for playback.
+     */
+
+    public void flush() {
+        if (mState == STATE_INITIALIZED) {
+            // flush the data in native layer
+            native_flush();
+        }
+
+    }
+
+    /**
+     * Writes the audio data to the audio hardware for playback.
+     * @param audioData the array that holds the data to play.
+     * @param offsetInBytes the offset in audioData where the data to play starts.
+     * @param sizeInBytes the number of bytes to read in audioData after the offset.
+     * @return the number of bytes that were written or {@link #ERROR_INVALID_OPERATION}
+     *    if the object wasn't properly initialized, or {@link #ERROR_BAD_VALUE} if
+     *    the parameters don't resolve to valid data and indexes.
+     */
+
+    public int write(byte[] audioData,int offsetInBytes, int sizeInBytes) {
+        if ((mDataLoadMode == MODE_STATIC)
+                && (mState == STATE_NO_STATIC_DATA)
+                && (sizeInBytes > 0)) {
+            mState = STATE_INITIALIZED;
+        }
+
+        if (mState != STATE_INITIALIZED) {
+            return ERROR_INVALID_OPERATION;
+        }
+
+        if ( (audioData == null) || (offsetInBytes < 0 ) || (sizeInBytes < 0) 
+                || (offsetInBytes + sizeInBytes > audioData.length)) {
+            return ERROR_BAD_VALUE;
+        }
+
+        return native_write_byte(audioData, offsetInBytes, sizeInBytes, mAudioFormat);
+    }
+
+
+    /**
+     * Writes the audio data to the audio hardware for playback.
+     * @param audioData the array that holds the data to play.
+     * @param offsetInShorts the offset in audioData where the data to play starts.
+     * @param sizeInShorts the number of bytes to read in audioData after the offset.
+     * @return the number of shorts that were written or {@link #ERROR_INVALID_OPERATION}
+      *    if the object wasn't properly initialized, or {@link #ERROR_BAD_VALUE} if
+      *    the parameters don't resolve to valid data and indexes.
+     */
+
+    public int write(short[] audioData, int offsetInShorts, int sizeInShorts) {
+        if ((mDataLoadMode == MODE_STATIC)
+                && (mState == STATE_NO_STATIC_DATA)
+                && (sizeInShorts > 0)) {
+            mState = STATE_INITIALIZED;
+        }
+        
+        if (mState != STATE_INITIALIZED) {
+            return ERROR_INVALID_OPERATION;
+        }
+
+        if ( (audioData == null) || (offsetInShorts < 0 ) || (sizeInShorts < 0) 
+                || (offsetInShorts + sizeInShorts > audioData.length)) {
+            return ERROR_BAD_VALUE;
+        }
+
+        return native_write_short(audioData, offsetInShorts, sizeInShorts, mAudioFormat);
+    }
+
+
+    /**
+     * Notifies the native resource to reuse the audio data already loaded in the native
+     * layer. This call is only valid with AudioTrack instances that don't use the streaming
+     * model.
+     * @return error code or success, see {@link #SUCCESS}, {@link #ERROR_BAD_VALUE},
+     *  {@link #ERROR_INVALID_OPERATION}
+     */
+    public int reloadStaticData() {
+        if (mDataLoadMode == MODE_STREAM) {
+            return ERROR_INVALID_OPERATION;
+        }
+        return native_reload_static();
+    }
+
+
+    //---------------------------------------------------------
+    // Interface definitions
+    //--------------------
+    /**
+     * Interface definition for a callback to be invoked when an AudioTrack has
+     * reached a notification marker set by setNotificationMarkerPosition().
+     */
+    public interface OnMarkerReachedListener  {
+        /**
+         * Called on the listener to notify it that the previously set marker has been reached
+         * by the playback head.
+         */
+        void onMarkerReached(AudioTrack track);
+    }
+
+
+    /**
+     * Interface definition for a callback to be invoked for each periodic AudioTrack
+     * update during playback. The update interval is set by setPositionNotificationPeriod().
+     */
+    public interface OnPeriodicNotificationListener  {
+        /**
+         * Called on the listener to periodically notify it that the playback head has reached
+         * a multiple of the notification period.
+         */
+        void onPeriodicNotification(AudioTrack track);
+    }
+
+
+    //---------------------------------------------------------
+    // Inner classes
+    //--------------------
+    /**
+     * Helper class to handle the forwarding of native events to the appropriate listeners
+     */
+    private class NativeEventHandler extends Handler
+    {
+        private AudioTrack mAudioTrack;
+
+        public NativeEventHandler(AudioTrack mp, Looper looper) {
+            super(looper);
+            mAudioTrack = mp;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (mAudioTrack == null) {
+                return;
+            }
+            switch(msg.what) {
+            case NATIVE_EVENT_MARKER:
+                synchronized (mMarkerListenerLock) {
+                    if (mAudioTrack.mMarkerListener != null) {
+                        mAudioTrack.mMarkerListener.onMarkerReached(mAudioTrack);
+                    }
+                }
+                break;
+            case NATIVE_EVENT_NEW_POS:
+                synchronized (mPeriodicListenerLock) {
+                    if (mAudioTrack.mPeriodicListener != null) {
+                        mAudioTrack.mPeriodicListener.onPeriodicNotification(mAudioTrack);
+                    }
+                }
+                break;
+            default:
+                Log.e(TAG, "[ android.media.AudioTrack.NativeEventHandler ] " +
+                        "Unknown event type: " + msg.what);
+                break;
+            }
+        }
+    }
+
+
+    //---------------------------------------------------------
+    // Java methods called from the native side
+    //--------------------
+    @SuppressWarnings("unused")
+    private static void postEventFromNative(Object audiotrack_ref,
+            int what, int arg1, int arg2, Object obj) {
+        //logd("Event posted from the native side: event="+ what + " args="+ arg1+" "+arg2);
+        AudioTrack track = (AudioTrack)((WeakReference)audiotrack_ref).get();
+        if (track == null) {
+            return;
+        }
+
+        if (track.mNativeEventHandler != null) {
+            Message m = track.mNativeEventHandler.obtainMessage(what, arg1, arg2, obj);
+            track.mNativeEventHandler.sendMessage(m);
+        }
+
+    }
+
+
+    //---------------------------------------------------------
+    // Native methods called from the Java side
+    //--------------------
+
+    private native final int native_setup(Object audiotrack_this,
+            int streamType, int sampleRate, int nbChannels, int audioFormat,
+            int buffSizeInBytes, int mode);
+
+    private native final void native_finalize();
+
+    private native final void native_release();
+
+    private native final void native_start();
+
+    private native final void native_stop();
+
+    private native final void native_pause();
+
+    private native final void native_flush();
+
+    private native final int native_write_byte(byte[] audioData,
+                                               int offsetInBytes, int sizeInBytes, int format);
+
+    private native final int native_write_short(short[] audioData,
+                                                int offsetInShorts, int sizeInShorts, int format);
+
+    private native final int native_reload_static();
+
+    private native final int native_get_native_frame_count();
+
+    private native final void native_setVolume(float leftVolume, float rightVolume);
+
+    private native final void native_set_playback_rate(int sampleRateInHz);
+    private native final int  native_get_playback_rate();
+
+    private native final int native_set_marker_pos(int marker);
+    private native final int native_get_marker_pos();
+
+    private native final int native_set_pos_update_period(int updatePeriod);
+    private native final int native_get_pos_update_period();
+
+    private native final int native_set_position(int position);
+    private native final int native_get_position();
+
+    private native final int native_set_loop(int start, int end, int loopCount);
+
+    static private native final int native_get_output_sample_rate(int streamType);
+    static private native final int native_get_min_buff_size(
+            int sampleRateInHz, int channelConfig, int audioFormat);
+
+
+    //---------------------------------------------------------
+    // Utility methods
+    //------------------
+
+    private static void logd(String msg) {
+        Log.d(TAG, "[ android.media.AudioTrack ] " + msg);
+    }
+
+    private static void loge(String msg) {
+        Log.e(TAG, "[ android.media.AudioTrack ] " + msg);
+    }
+
+}
diff --git a/media/java/android/media/FaceDetector.java b/media/java/android/media/FaceDetector.java
new file mode 100644
index 0000000..3b41cf8
--- /dev/null
+++ b/media/java/android/media/FaceDetector.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2006 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.media;
+
+import android.graphics.Bitmap;
+import android.graphics.PointF;
+import android.util.Log;
+
+import java.lang.IllegalArgumentException;
+
+/**
+ * Identifies the faces of people in a 
+ * {@link android.graphics.Bitmap} graphic object.
+ */
+public class FaceDetector {
+
+    /**
+     * A Face contains all the information identifying the location
+     * of a face in a bitmap.
+     */
+    public class Face {
+        /** The minimum confidence factor of good face recognition */
+        public static final float CONFIDENCE_THRESHOLD = 0.4f;
+        /** The x-axis Euler angle of a face. */
+        public static final int EULER_X = 0;
+        /** The y-axis Euler angle of a face. */
+        public static final int EULER_Y = 1;
+        /** The z-axis Euler angle of a face. */
+        public static final int EULER_Z = 2;
+
+        /** 
+         * Returns a confidence factor between 0 and 1. This indicates how
+         * certain what has been found is actually a face. A confidence
+         * factor above 0.3 is usually good enough.
+         */
+        public float confidence() {
+            return mConfidence;
+        }
+        /**
+         * Sets the position of the mid-point between the eyes.
+         * @param point the PointF coordinates (float values) of the 
+         *              face's mid-point
+         */
+        public void getMidPoint(PointF point) {
+            // don't return a PointF to avoid allocations
+            point.set(mMidPointX, mMidPointY);
+        }
+        /**
+         * Returns the distance between the eyes.
+         */
+        public float eyesDistance() {
+            return mEyesDist;
+        }
+        /**
+         * Returns the face's pose. That is, the rotations around either 
+         * the X, Y or Z axis (the positions in 3-dimensional Euclidean space).
+         * 
+         * @param euler the Euler axis to retrieve an angle from 
+         *              (<var>EULER_X</var>, <var>EULER_Y</var> or 
+         *              <var>EULER_Z</var>)
+         * @return the Euler angle of the of the face, for the given axis
+         */
+        public float pose(int euler) {
+            // don't use an array to avoid allocations
+            if (euler == EULER_X)
+                return mPoseEulerX;
+            else if (euler == EULER_Y)
+                return mPoseEulerY;
+            else if (euler == EULER_Z)
+                return mPoseEulerZ;
+           throw new IllegalArgumentException();
+        }
+
+        // private ctor, user not supposed to build this object
+        private Face() {
+        }
+        private float   mConfidence;
+        private float   mMidPointX;
+        private float   mMidPointY;
+        private float   mEyesDist;
+        private float   mPoseEulerX;
+        private float   mPoseEulerY;
+        private float   mPoseEulerZ;
+    }
+
+
+    /**
+     * Creates a FaceDetector, configured with the size of the images to
+     * be analysed and the maximum number of faces that can be detected.
+     * These parameters cannot be changed once the object is constructed.
+     * 
+     * @param width  the width of the image
+     * @param height the height of the image
+     * @param maxFaces the maximum number of faces to identify
+     *
+     */
+    public FaceDetector(int width, int height, int maxFaces)
+    {
+        if (!sInitialized) {
+            return;
+        }
+        fft_initialize(width, height, maxFaces);
+        mWidth = width;
+        mHeight = height;
+        mMaxFaces = maxFaces;
+        mBWBuffer = new byte[width * height];
+    }
+
+    /**
+     * Finds all the faces found in a given {@link android.graphics.Bitmap}. 
+     * The supplied array is populated with {@link FaceDetector.Face}s for each
+     * face found. The bitmap must be in 565 format (for now).
+     * 
+     * @param bitmap the {@link android.graphics.Bitmap} graphic to be analyzed
+     * @param faces  an array in which to place all found 
+     *               {@link FaceDetector.Face}s. The array must be sized equal
+     *               to the <var>maxFaces</var> value set at initialization
+     * @return the number of faces found
+     * @throws IllegalArgumentException if the Bitmap dimensions don't match
+     *               the dimensions defined at initialization or the given array 
+     *               is not sized equal to the <var>maxFaces</var> value defined
+     *               at initialization
+     */
+    public int findFaces(Bitmap bitmap, Face[] faces)
+    {
+        if (!sInitialized) {
+            return 0;
+        }
+        if (bitmap.getWidth() != mWidth || bitmap.getHeight() != mHeight) {
+            throw new IllegalArgumentException(
+                    "bitmap size doesn't match initialization");
+        }
+        if (faces.length < mMaxFaces) {
+            throw new IllegalArgumentException(
+                    "faces[] smaller than maxFaces");
+        }
+        
+        int numFaces = fft_detect(bitmap);
+        if (numFaces >= mMaxFaces)
+            numFaces = mMaxFaces;
+        for (int i=0 ; i<numFaces ; i++) {
+            if (faces[i] == null)
+                faces[i] = new Face();
+            fft_get_face(faces[i], i);
+        }
+        return numFaces;
+    }
+
+
+    /* no user serviceable parts here ... */
+    @Override
+    protected void finalize() throws Throwable {
+        fft_destroy();
+    }
+
+    /*
+     * We use a class initializer to allow the native code to cache some
+     * field offsets.
+     */
+    private static boolean sInitialized;
+    native private static void nativeClassInit();
+
+    static {
+        sInitialized = false;
+        try {
+            System.loadLibrary("FFTEm");
+            nativeClassInit();
+            sInitialized = true;
+        } catch (UnsatisfiedLinkError e) {
+            Log.d("FFTEm", "face detection library not found!");
+        }
+    }
+
+    native private int  fft_initialize(int width, int height, int maxFaces);
+    native private int  fft_detect(Bitmap bitmap);
+    native private void fft_get_face(Face face, int i);
+    native private void fft_destroy();
+
+    private int     mFD;
+    private int     mSDK;
+    private int     mDCR;
+    private int     mWidth;
+    private int     mHeight;
+    private int     mMaxFaces;    
+    private byte    mBWBuffer[];
+}
+
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
new file mode 100644
index 0000000..f5e242d
--- /dev/null
+++ b/media/java/android/media/IAudioService.aidl
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2007 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.media;
+
+/**
+ * {@hide}
+ */
+interface IAudioService {
+    
+    void adjustVolume(int direction, int flags);
+
+    void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags);
+    
+    void adjustStreamVolume(int streamType, int direction, int flags);
+    
+    void setStreamVolume(int streamType, int index, int flags);
+    
+   	void setStreamSolo(int streamType, boolean state, IBinder cb);
+   	
+   	void setStreamMute(int streamType, boolean state, IBinder cb);
+    
+    int getStreamVolume(int streamType);
+    
+    int getStreamMaxVolume(int streamType);
+    
+    void setRingerMode(int ringerMode);
+    
+    int getRingerMode();
+
+    void setVibrateSetting(int vibrateType, int vibrateSetting);
+    
+    int getVibrateSetting(int vibrateType);
+    
+    boolean shouldVibrate(int vibrateType);
+    
+    void setMicrophoneMute(boolean on);
+
+    boolean isMicrophoneMute();
+
+    void setMode(int mode);
+
+    int getMode();
+
+    void setRouting(int mode, int routes, int mask);
+
+    int getRouting(int mode);
+
+    boolean isMusicActive();
+
+    void setParameter(String key, String value);
+
+    oneway void playSoundEffect(int effectType);
+  
+    oneway void playSoundEffectVolume(int effectType, float volume);
+
+    boolean loadSoundEffects();
+  
+    oneway void unloadSoundEffects();
+
+}
diff --git a/media/java/android/media/IMediaScannerListener.aidl b/media/java/android/media/IMediaScannerListener.aidl
new file mode 100644
index 0000000..4e85563
--- /dev/null
+++ b/media/java/android/media/IMediaScannerListener.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2008 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.media;
+
+import android.net.Uri;
+
+/**
+ * {@hide}
+ */
+oneway interface IMediaScannerListener
+{
+    /**
+     * Called when a IMediaScannerService.scanFile() call has completed.
+     * @param path the path to the file that has been scanned.
+     * @param uri the Uri for the file if the scanning operation succeeded 
+     * and the file was added to the media database, or null if scanning failed. 
+     */
+    void scanCompleted(String path, in Uri uri);
+}
diff --git a/media/java/android/media/IMediaScannerService.aidl b/media/java/android/media/IMediaScannerService.aidl
new file mode 100644
index 0000000..c531646
--- /dev/null
+++ b/media/java/android/media/IMediaScannerService.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2007 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.media;
+
+import android.media.IMediaScannerListener;
+
+/**
+ * {@hide}
+ */
+interface IMediaScannerService
+{
+    /**
+     * Requests the media scanner to scan a file.
+     * @param path the path to the file to be scanned.
+     * @param mimeType  an optional mimeType for the file.
+     * If mimeType is null, then the mimeType will be inferred from the file extension.
+     * @param listener an optional IMediaScannerListener. 
+     * If specified, the caller will be notified when scanning is complete via the listener.
+     */
+    void requestScanFile(String path, String mimeType, in IMediaScannerListener listener);
+
+    /**
+     * Older API, left in for backward compatibility.
+     * Requests the media scanner to scan a file.
+     * @param path the path to the file to be scanned.
+     * @param mimeType  an optional mimeType for the file.
+     * If mimeType is null, then the mimeType will be inferred from the file extension.
+     */
+    void scanFile(String path, String mimeType);
+}
diff --git a/media/java/android/media/JetPlayer.java b/media/java/android/media/JetPlayer.java
new file mode 100644
index 0000000..9de0eec
--- /dev/null
+++ b/media/java/android/media/JetPlayer.java
@@ -0,0 +1,423 @@
+/*
+ * Copyright (C) 2008 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.media;
+
+
+import java.io.FileDescriptor;
+import java.lang.ref.WeakReference;
+import java.lang.CloneNotSupportedException;
+
+import android.content.res.AssetFileDescriptor;
+import android.os.Looper;
+import android.os.Handler;
+import android.os.Message;
+import android.util.AndroidRuntimeException;
+import android.util.Log;
+
+/**
+ * JetPlayer provides access to JET content playback and control.
+ * <p>
+ * Use <code>JetPlayer.getJetPlayer()</code> to get an instance of this class.
+ * 
+ */
+public class JetPlayer
+{    
+    //--------------------------------------------
+    // Constants
+    //------------------------
+    /**
+     * The maximum number of simultaneous tracks. Use __link #getMaxTracks()} to
+     * access this value.
+     */
+    private static int MAXTRACKS = 32;
+        
+    // to keep in sync with the JetPlayer class constants
+    // defined in frameworks/base/include/media/JetPlayer.h
+    private static final int JET_EVENT                   = 1;
+    private static final int JET_USERID_UPDATE           = 2;
+    private static final int JET_NUMQUEUEDSEGMENT_UPDATE = 3;
+    private static final int JET_PAUSE_UPDATE            = 4;
+    
+    // to keep in sync with external/sonivox/arm-wt-22k/lib_src/jet_data.h
+    // Encoding of event information on 32 bits
+    private static final int JET_EVENT_VAL_MASK    = 0x0000007f; // mask for value
+    private static final int JET_EVENT_CTRL_MASK   = 0x00003f80; // mask for controller
+    private static final int JET_EVENT_CHAN_MASK   = 0x0003c000; // mask for channel
+    private static final int JET_EVENT_TRACK_MASK  = 0x00fc0000; // mask for track number
+    private static final int JET_EVENT_SEG_MASK    = 0xff000000; // mask for segment ID
+    private static final int JET_EVENT_CTRL_SHIFT  = 7;  // shift to get controller number to bit 0
+    private static final int JET_EVENT_CHAN_SHIFT  = 14; // shift to get MIDI channel to bit 0
+    private static final int JET_EVENT_TRACK_SHIFT = 18; // shift to get track ID to bit 0
+    private static final int JET_EVENT_SEG_SHIFT   = 24; // shift to get segment ID to bit 0
+
+    
+    //--------------------------------------------
+    // Member variables
+    //------------------------
+    private EventHandler            mNativeEventHandler = null;
+    
+    /**
+     * Lock to protect status listener updates against status change notifications
+     */
+    private final Object mStatusListenerLock = new Object();
+    
+    /**
+     * Lock to protect the event listener updates against event notifications
+     */
+    private final Object mEventListenerLock = new Object();
+    
+    private JetStatusUpdateListener mJetStatusUpdateListener = null;
+    
+    private JetEventListener mJetEventListener = null;
+    
+    private static JetPlayer singletonRef;
+    
+    
+    //--------------------------------
+    // Used exclusively by native code
+    //--------------------
+    /** 
+     * Accessed by native methods: provides access to C++ JetPlayer object 
+     */
+    @SuppressWarnings("unused")
+    private int mNativePlayerInJavaObj;
+
+    
+    //--------------------------------------------
+    // Constructor, finalize
+    //------------------------
+    public static JetPlayer getJetPlayer() {
+        if (singletonRef == null)
+            singletonRef = new JetPlayer();
+        return singletonRef;
+    }
+    
+    
+    public Object clone() throws CloneNotSupportedException {
+        // JetPlayer is a singleton class,
+        // so you can't clone a JetPlayer instance
+        throw new CloneNotSupportedException();    
+    }
+    
+
+    private JetPlayer() {
+                
+        native_setup(new WeakReference<JetPlayer>(this),
+                JetPlayer.getMaxTracks(), 
+                1200); //TODO parametrize this (?)
+    }
+    
+    
+    protected void finalize() { 
+        native_finalize(); 
+    }
+    
+    
+    public void release() {
+        native_release();
+    }
+    
+    
+    private void createNativeEventHandler() {
+        Looper looper;
+        if ((looper = Looper.myLooper()) != null) {
+            mNativeEventHandler = new EventHandler(this, looper);
+        } else if ((looper = Looper.getMainLooper()) != null) {
+            mNativeEventHandler = new EventHandler(this, looper);
+        } else {
+            mNativeEventHandler = null;
+        }
+    }
+    
+    
+    //--------------------------------------------
+    // Getters
+    //------------------------
+    /**
+     * Returns the maximum number of simultaneous MIDI tracks supported by the Jet player
+     */
+    public static int getMaxTracks() {
+        return JetPlayer.MAXTRACKS;
+    }
+    
+    
+    //--------------------------------------------
+    // Jet functionality
+    //------------------------
+    public boolean loadJetFile(String path) {
+        return native_loadJetFromFile(path);
+    }
+    
+    
+    public boolean loadJetFile(AssetFileDescriptor afd) {
+        long len = afd.getLength();
+        if (len < 0) {
+            throw new AndroidRuntimeException("no length for fd");
+        }
+        return native_loadJetFromFileD(
+                afd.getFileDescriptor(), afd.getStartOffset(), len);
+    }
+    
+    
+    public boolean closeJetFile() {
+        return native_closeJetFile();
+    }
+    
+    
+    public boolean play() {
+        return native_playJet();
+    }
+    
+    
+    public boolean pause() {
+        return native_pauseJet();
+    }
+    
+    
+    public boolean queueJetSegment(int segmentNum, int libNum, int repeatCount,
+        int transpose, int muteFlags, byte userID) {
+        return native_queueJetSegment(segmentNum, libNum, repeatCount, 
+                transpose, muteFlags, userID);
+    }
+    
+    
+    public boolean queueJetSegmentMuteArray(int segmentNum, int libNum, int repeatCount,
+            int transpose, boolean[] muteArray, byte userID) {
+        if (muteArray.length != JetPlayer.getMaxTracks()) {
+            return false;
+        }
+        return native_queueJetSegmentMuteArray(segmentNum, libNum, repeatCount,
+                transpose, muteArray, userID);
+    }
+    
+    
+    public boolean setMuteFlags(int muteFlags, boolean sync) {
+        return native_setMuteFlags(muteFlags, sync);
+    }
+    
+    
+    public boolean setMuteArray(boolean[] muteArray, boolean sync) {
+        if(muteArray.length != JetPlayer.getMaxTracks())
+            return false;
+        return native_setMuteArray(muteArray, sync);
+    }
+    
+    
+    public boolean setMuteFlag(int trackId, boolean muteFlag, boolean sync) {
+        return native_setMuteFlag(trackId, muteFlag, sync);
+    }
+    
+    
+    public boolean triggerClip(int clipId) {
+        return native_triggerClip(clipId);
+    }
+    
+    
+    public boolean clearQueue() {
+        return native_clearQueue();
+    }
+    
+     
+    //---------------------------------------------------------
+    // Internal class to handle events posted from native code
+    //------------------------
+    private class EventHandler extends Handler
+    {
+        private JetPlayer mJet;
+
+        public EventHandler(JetPlayer jet, Looper looper) {
+            super(looper);
+            mJet = jet;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch(msg.what) {
+            case JET_EVENT:
+                synchronized (mEventListenerLock) {
+                    if (mJetEventListener != null) {
+                        // call the appropriate listener after decoding the event parameters
+                        // encoded in msg.arg1
+                        mJetEventListener.onJetEvent(
+                            mJet,
+                            (short)((msg.arg1 & JET_EVENT_SEG_MASK)   >> JET_EVENT_SEG_SHIFT),
+                            (byte) ((msg.arg1 & JET_EVENT_TRACK_MASK) >> JET_EVENT_TRACK_SHIFT),
+                            // JETCreator channel numbers start at 1, but the index starts at 0
+                            // in the .jet files
+                            (byte)(((msg.arg1 & JET_EVENT_CHAN_MASK)  >> JET_EVENT_CHAN_SHIFT) + 1),
+                            (byte) ((msg.arg1 & JET_EVENT_CTRL_MASK)  >> JET_EVENT_CTRL_SHIFT),
+                            (byte)  (msg.arg1 & JET_EVENT_VAL_MASK) );
+                    }
+                }
+                return;
+            case JET_USERID_UPDATE:
+                synchronized (mStatusListenerLock) {
+                    if (mJetStatusUpdateListener != null) {
+                        mJetStatusUpdateListener.onJetUserIdUpdate(mJet, msg.arg1, msg.arg2);
+                    }
+                }
+                return;
+            case JET_NUMQUEUEDSEGMENT_UPDATE:
+                synchronized (mStatusListenerLock) {
+                    if (mJetStatusUpdateListener != null) {
+                        mJetStatusUpdateListener.onJetNumQueuedSegmentUpdate(mJet, msg.arg1);
+                    }
+                }
+                return;
+            case JET_PAUSE_UPDATE:
+                synchronized (mStatusListenerLock) {
+                    if (mJetStatusUpdateListener != null)
+                        mJetStatusUpdateListener.onJetPauseUpdate(mJet, msg.arg1);
+                }
+                return;
+
+            default:
+                loge("Unknown message type " + msg.what);
+                return;
+            }
+        }
+    }
+    
+    
+    //--------------------------------------------
+    // Jet status update listener
+    //------------------------
+    public void setStatusUpdateListener(JetStatusUpdateListener listener) {
+        synchronized(mStatusListenerLock) {
+            mJetStatusUpdateListener = listener;
+        }
+        
+        if ((listener != null) && (mNativeEventHandler == null)) {
+            createNativeEventHandler();
+        }
+    }
+    
+    /**
+     * Handles the notification when the JET status is updated.
+     */
+    public interface JetStatusUpdateListener {
+        /**
+         * Callback for when JET's currently playing segment userID is updated.
+         * 
+         * @param player the JET player the status update is coming from
+         * @param userId the ID of the currently playing segment
+         * @param repeatCount the repetition count for the segment (0 means it plays once)
+         */
+        void onJetUserIdUpdate(JetPlayer player, int userId, int repeatCount);
+        
+        /**
+         * Callback for when JET's number of queued segments is updated.
+         * 
+         * @param player the JET player the status update is coming from
+         * @param nbSegments the number of segments in the JET queue
+         */
+        void onJetNumQueuedSegmentUpdate(JetPlayer player, int nbSegments);
+        
+        /**
+         * Callback for when JET pause state is updated.
+         * 
+         * @param player the JET player the status update is coming from
+         * @param paused indicates whether JET is paused or not
+         */
+        void onJetPauseUpdate(JetPlayer player, int paused);
+    }
+    
+    
+    //--------------------------------------------
+    // Jet event listener
+    //------------------------
+    public void setEventListener(JetEventListener listener) {
+        synchronized(mEventListenerLock) {
+            mJetEventListener = listener;
+        }
+        
+        if ((listener != null) && (mNativeEventHandler == null)) {
+            createNativeEventHandler();
+        }
+    }
+    
+    /**
+     * Handles the notification when the JET engine generates an event.
+     */
+    public interface JetEventListener {
+        /**
+         * Callback for when the JET engine generates a new event.
+         * 
+         * @param player the JET player the event is coming from
+         * @param segment 8 bit unsigned value
+         * @param track 6 bit unsigned value
+         * @param channel 4 bit unsigned value
+         * @param controller 7 bit unsigned value
+         * @param value 7 bit unsigned value
+         */
+        void onJetEvent(JetPlayer player,
+                short segment, byte track, byte channel, byte controller, byte value);
+    }
+    
+    
+    //--------------------------------------------
+    // Native methods
+    //------------------------
+    private native final boolean native_setup(Object Jet_this,
+                int maxTracks, int trackBufferSize);
+    private native final void    native_finalize();
+    private native final void    native_release();
+    private native final boolean native_loadJetFromFile(String pathToJetFile);
+    private native final boolean native_loadJetFromFileD(FileDescriptor fd, long offset, long len);
+    private native final boolean native_closeJetFile();
+    private native final boolean native_playJet();
+    private native final boolean native_pauseJet();
+    private native final boolean native_queueJetSegment(int segmentNum, int libNum,
+            int repeatCount, int transpose, int muteFlags, byte userID);
+    private native final boolean native_queueJetSegmentMuteArray(int segmentNum, int libNum, 
+            int repeatCount, int transpose, boolean[] muteArray, byte userID);
+    private native final boolean native_setMuteFlags(int muteFlags, boolean sync);
+    private native final boolean native_setMuteArray(boolean[]muteArray, boolean sync);
+    private native final boolean native_setMuteFlag(int trackId, boolean muteFlag, boolean sync);
+    private native final boolean native_triggerClip(int clipId);
+    private native final boolean native_clearQueue();
+    
+    //---------------------------------------------------------
+    // Called exclusively by native code
+    //--------------------
+    @SuppressWarnings("unused")
+    private static void postEventFromNative(Object jetplayer_ref,
+            int what, int arg1, int arg2) {
+        
+        JetPlayer jet = (JetPlayer)((WeakReference)jetplayer_ref).get();
+
+        if( (jet!=null) && (jet.mNativeEventHandler!=null) ){
+            Message m = jet.mNativeEventHandler.obtainMessage(what, arg1, arg2, null);
+            jet.mNativeEventHandler.sendMessage(m);
+        }
+    }
+    
+ 
+    //---------------------------------------------------------
+    // Utils
+    //--------------------
+    private final static String TAG = "JetPlayer-J";
+    
+    private static void logd(String msg) {
+        Log.d(TAG, "[ android.media.JetPlayer ] " + msg);
+    }
+    
+    private static void loge(String msg) {
+        Log.e(TAG, "[ android.media.JetPlayer ] " + msg);
+    }
+ 
+}
diff --git a/media/java/android/media/MediaFile.java b/media/java/android/media/MediaFile.java
new file mode 100644
index 0000000..f05842d
--- /dev/null
+++ b/media/java/android/media/MediaFile.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2007 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.media;
+
+import android.content.ContentValues;
+import android.provider.MediaStore.Audio;
+import android.provider.MediaStore.Images;
+import android.provider.MediaStore.Video;
+
+import java.util.HashMap;
+import java.util.Iterator;
+
+/**
+ * MediaScanner helper class.
+ *
+ * {@hide}
+ */
+public class MediaFile {
+    // comma separated list of all file extensions supported by the media scanner
+    public static String sFileExtensions;
+
+    // Audio file types
+    public static final int FILE_TYPE_MP3     = 1;
+    public static final int FILE_TYPE_M4A     = 2;
+    public static final int FILE_TYPE_WAV     = 3;
+    public static final int FILE_TYPE_AMR     = 4;
+    public static final int FILE_TYPE_AWB     = 5;
+    public static final int FILE_TYPE_WMA     = 6;
+    public static final int FILE_TYPE_OGG     = 7;
+    private static final int FIRST_AUDIO_FILE_TYPE = FILE_TYPE_MP3;
+    private static final int LAST_AUDIO_FILE_TYPE = FILE_TYPE_OGG;
+
+    // MIDI file types
+    public static final int FILE_TYPE_MID     = 11;
+    public static final int FILE_TYPE_SMF     = 12;
+    public static final int FILE_TYPE_IMY     = 13;
+    private static final int FIRST_MIDI_FILE_TYPE = FILE_TYPE_MID;
+    private static final int LAST_MIDI_FILE_TYPE = FILE_TYPE_IMY;
+   
+    // Video file types
+    public static final int FILE_TYPE_MP4     = 21;
+    public static final int FILE_TYPE_M4V     = 22;
+    public static final int FILE_TYPE_3GPP    = 23;
+    public static final int FILE_TYPE_3GPP2   = 24;
+    public static final int FILE_TYPE_WMV     = 25;
+    private static final int FIRST_VIDEO_FILE_TYPE = FILE_TYPE_MP4;
+    private static final int LAST_VIDEO_FILE_TYPE = FILE_TYPE_WMV;
+    
+    // Image file types
+    public static final int FILE_TYPE_JPEG    = 31;
+    public static final int FILE_TYPE_GIF     = 32;
+    public static final int FILE_TYPE_PNG     = 33;
+    public static final int FILE_TYPE_BMP     = 34;
+    public static final int FILE_TYPE_WBMP    = 35;
+    private static final int FIRST_IMAGE_FILE_TYPE = FILE_TYPE_JPEG;
+    private static final int LAST_IMAGE_FILE_TYPE = FILE_TYPE_WBMP;
+   
+    // Playlist file types
+    public static final int FILE_TYPE_M3U     = 41;
+    public static final int FILE_TYPE_PLS     = 42;
+    public static final int FILE_TYPE_WPL     = 43;
+    private static final int FIRST_PLAYLIST_FILE_TYPE = FILE_TYPE_M3U;
+    private static final int LAST_PLAYLIST_FILE_TYPE = FILE_TYPE_WPL;
+    
+    static class MediaFileType {
+    
+        int fileType;
+        String mimeType;
+        
+        MediaFileType(int fileType, String mimeType) {
+            this.fileType = fileType;
+            this.mimeType = mimeType;
+        }
+    }
+    
+    private static HashMap<String, MediaFileType> sFileTypeMap 
+            = new HashMap<String, MediaFileType>();
+    private static HashMap<String, Integer> sMimeTypeMap 
+            = new HashMap<String, Integer>();            
+    static void addFileType(String extension, int fileType, String mimeType) {
+        sFileTypeMap.put(extension, new MediaFileType(fileType, mimeType));
+        sMimeTypeMap.put(mimeType, new Integer(fileType));
+    }
+    static {
+        addFileType("MP3", FILE_TYPE_MP3, "audio/mpeg");
+        addFileType("M4A", FILE_TYPE_M4A, "audio/mp4");
+        addFileType("WAV", FILE_TYPE_WAV, "audio/x-wav");
+        addFileType("AMR", FILE_TYPE_AMR, "audio/amr");
+        addFileType("AWB", FILE_TYPE_AWB, "audio/amr-wb");
+        addFileType("WMA", FILE_TYPE_WMA, "audio/x-ms-wma");    
+        addFileType("OGG", FILE_TYPE_OGG, "application/ogg");
+        addFileType("OGA", FILE_TYPE_OGG, "application/ogg");
+ 
+        addFileType("MID", FILE_TYPE_MID, "audio/midi");
+        addFileType("MIDI", FILE_TYPE_MID, "audio/midi");
+        addFileType("XMF", FILE_TYPE_MID, "audio/midi");
+        addFileType("RTTTL", FILE_TYPE_MID, "audio/midi");
+        addFileType("SMF", FILE_TYPE_SMF, "audio/sp-midi");
+        addFileType("IMY", FILE_TYPE_IMY, "audio/imelody");
+        addFileType("RTX", FILE_TYPE_MID, "audio/midi");
+        addFileType("OTA", FILE_TYPE_MID, "audio/midi");
+        
+        addFileType("MP4", FILE_TYPE_MP4, "video/mp4");
+        addFileType("M4V", FILE_TYPE_M4V, "video/mp4");
+        addFileType("3GP", FILE_TYPE_3GPP, "video/3gpp");
+        addFileType("3GPP", FILE_TYPE_3GPP, "video/3gpp");
+        addFileType("3G2", FILE_TYPE_3GPP2, "video/3gpp2");
+        addFileType("3GPP2", FILE_TYPE_3GPP2, "video/3gpp2");
+        addFileType("WMV", FILE_TYPE_WMV, "video/x-ms-wmv");
+
+        addFileType("JPG", FILE_TYPE_JPEG, "image/jpeg");
+        addFileType("JPEG", FILE_TYPE_JPEG, "image/jpeg");
+        addFileType("GIF", FILE_TYPE_GIF, "image/gif");
+        addFileType("PNG", FILE_TYPE_PNG, "image/png");
+        addFileType("BMP", FILE_TYPE_BMP, "image/x-ms-bmp");
+        addFileType("WBMP", FILE_TYPE_WBMP, "image/vnd.wap.wbmp");
+ 
+        addFileType("M3U", FILE_TYPE_M3U, "audio/x-mpegurl");
+        addFileType("PLS", FILE_TYPE_PLS, "audio/x-scpls");
+        addFileType("WPL", FILE_TYPE_WPL, "application/vnd.ms-wpl");
+
+        // compute file extensions list for native Media Scanner
+        StringBuilder builder = new StringBuilder();
+        Iterator<String> iterator = sFileTypeMap.keySet().iterator();
+        
+        while (iterator.hasNext()) {
+            if (builder.length() > 0) {
+                builder.append(',');
+            }
+            builder.append(iterator.next());
+        } 
+        sFileExtensions = builder.toString();
+    }
+    
+    public static final String UNKNOWN_STRING = "<unknown>";
+    
+    public static boolean isAudioFileType(int fileType) {
+        return ((fileType >= FIRST_AUDIO_FILE_TYPE &&
+                fileType <= LAST_AUDIO_FILE_TYPE) ||
+                (fileType >= FIRST_MIDI_FILE_TYPE &&
+                fileType <= LAST_MIDI_FILE_TYPE));
+    }
+    
+    public static boolean isVideoFileType(int fileType) {
+        return (fileType >= FIRST_VIDEO_FILE_TYPE &&
+                fileType <= LAST_VIDEO_FILE_TYPE);
+    }
+    
+    public static boolean isImageFileType(int fileType) {
+        return (fileType >= FIRST_IMAGE_FILE_TYPE &&
+                fileType <= LAST_IMAGE_FILE_TYPE);
+    }
+    
+    public static boolean isPlayListFileType(int fileType) {
+        return (fileType >= FIRST_PLAYLIST_FILE_TYPE &&
+                fileType <= LAST_PLAYLIST_FILE_TYPE);
+    }
+    
+    public static MediaFileType getFileType(String path) {
+        int lastDot = path.lastIndexOf(".");
+        if (lastDot < 0)
+            return null;
+        return sFileTypeMap.get(path.substring(lastDot + 1).toUpperCase());
+    }
+    
+    public static int getFileTypeForMimeType(String mimeType) {
+        Integer value = sMimeTypeMap.get(mimeType);
+        return (value == null ? 0 : value.intValue());
+    }
+
+}
diff --git a/media/java/android/media/MediaMetadataRetriever.java b/media/java/android/media/MediaMetadataRetriever.java
new file mode 100644
index 0000000..3a49a5f
--- /dev/null
+++ b/media/java/android/media/MediaMetadataRetriever.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2008 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.media;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.FileNotFoundException;
+
+/**
+ * MediaMetadataRetriever class provides a unified interface for retrieving
+ * frame and meta data from an input media file.
+ * {@hide}
+ */
+public class MediaMetadataRetriever
+{
+    static {
+        System.loadLibrary("media_jni");
+    }
+
+    // The field below is accessed by native methods
+    @SuppressWarnings("unused")
+    private int mNativeContext;
+ 
+    public MediaMetadataRetriever() {
+        native_setup();
+    }
+
+    /**
+     * Call this method before setDataSource() so that the mode becomes
+     * effective for subsequent operations. This method can be called only once
+     * at the beginning if the intended mode of operation for a
+     * MediaMetadataRetriever object remains the same for its whole lifetime,
+     * and thus it is unnecessary to call this method each time setDataSource()
+     * is called. If this is not never called (which is allowed), by default the
+     * intended mode of operation is to both capture frame and retrieve meta
+     * data (i.e., MODE_GET_METADATA_ONLY | MODE_CAPTURE_FRAME_ONLY).
+     * Often, this may not be what one wants, since doing this has negative
+     * performance impact on execution time of a call to setDataSource(), since
+     * both types of operations may be time consuming.
+     * 
+     * @param mode The intended mode of operation. Can be any combination of 
+     * MODE_GET_METADATA_ONLY and MODE_CAPTURE_FRAME_ONLY:
+     * 1. MODE_GET_METADATA_ONLY & MODE_CAPTURE_FRAME_ONLY: 
+     *    For neither frame capture nor meta data retrieval
+     * 2. MODE_GET_METADATA_ONLY: For meta data retrieval only
+     * 3. MODE_CAPTURE_FRAME_ONLY: For frame capture only
+     * 4. MODE_GET_METADATA_ONLY | MODE_CAPTURE_FRAME_ONLY: 
+     *    For both frame capture and meta data retrieval
+     */
+    public native void setMode(int mode);
+    
+    /**
+     * @return the current mode of operation. A negative return value indicates
+     * some runtime error has occurred.
+     */
+    public native int getMode();
+
+    /**
+     * Sets the data source (file pathname) to use. Call this
+     * method before the rest of the methods in this class. This method may be
+     * time-consuming.
+     * 
+     * @param path The path of the input media file.
+     * @throws IllegalArgumentException If the path is invalid.
+     */
+    public native void setDataSource(String path) throws IllegalArgumentException;
+    
+    /**
+     * Sets the data source (FileDescriptor) to use.  It is the caller's
+     * responsibility to close the file descriptor. It is safe to do so as soon
+     * as this call returns. Call this method before the rest of the methods in
+     * this class. This method may be time-consuming.
+     * 
+     * @param fd the FileDescriptor for the file you want to play
+     * @param offset the offset into the file where the data to be played starts,
+     * in bytes. It must be non-negative
+     * @param length the length in bytes of the data to be played. It must be
+     * non-negative.
+     * @throws IllegalArgumentException if the arguments are invalid
+     */
+    public native void setDataSource(FileDescriptor fd, long offset, long length)
+            throws IllegalArgumentException;
+    
+    /**
+     * Sets the data source (FileDescriptor) to use. It is the caller's
+     * responsibility to close the file descriptor. It is safe to do so as soon
+     * as this call returns. Call this method before the rest of the methods in
+     * this class. This method may be time-consuming.
+     * 
+     * @param fd the FileDescriptor for the file you want to play
+     * @throws IllegalArgumentException if the FileDescriptor is invalid
+     */
+    public void setDataSource(FileDescriptor fd)
+            throws IllegalArgumentException {
+        // intentionally less than LONG_MAX
+        setDataSource(fd, 0, 0x7ffffffffffffffL);
+    }
+    
+    /**
+     * Sets the data source as a content Uri. Call this method before 
+     * the rest of the methods in this class. This method may be time-consuming.
+     * 
+     * @param context the Context to use when resolving the Uri
+     * @param uri the Content URI of the data you want to play
+     * @throws IllegalArgumentException if the Uri is invalid
+     * @throws SecurityException if the Uri cannot be used due to lack of
+     * permission.
+     */
+    public void setDataSource(Context context, Uri uri)
+        throws IllegalArgumentException, SecurityException {
+        if (uri == null) {
+            throw new IllegalArgumentException();
+        }
+        
+        String scheme = uri.getScheme();
+        if(scheme == null || scheme.equals("file")) {
+            setDataSource(uri.getPath());
+            return;
+        }
+
+        AssetFileDescriptor fd = null;
+        try {
+            ContentResolver resolver = context.getContentResolver();
+            try {
+                fd = resolver.openAssetFileDescriptor(uri, "r");
+            } catch(FileNotFoundException e) {
+                throw new IllegalArgumentException();
+            }
+            if (fd == null) {
+                throw new IllegalArgumentException();
+            }
+            FileDescriptor descriptor = fd.getFileDescriptor();
+            if (!descriptor.valid()) {
+                throw new IllegalArgumentException();
+            }
+            // Note: using getDeclaredLength so that our behavior is the same
+            // as previous versions when the content provider is returning
+            // a full file.
+            if (fd.getDeclaredLength() < 0) {
+                setDataSource(descriptor);
+            } else {
+                setDataSource(descriptor, fd.getStartOffset(), fd.getDeclaredLength());
+            }
+            return;
+        } catch (SecurityException ex) {
+        } finally {
+            try {
+                if (fd != null) {
+                    fd.close();
+                }
+            } catch(IOException ioEx) {
+            }
+        }
+        setDataSource(uri.toString());
+    }
+
+    /**
+     * Call this method after setDataSource(). This method retrieves the 
+     * meta data value associated with the keyCode.
+     * 
+     * The keyCode currently supported is listed below as METADATA_XXX
+     * constants. With any other value, it returns a null pointer.
+     * 
+     * @param keyCode One of the constants listed below at the end of the class.
+     * @return The meta data value associate with the given keyCode on success; 
+     * null on failure.
+     */
+    public native String extractMetadata(int keyCode);
+
+    /**
+     * Call this method after setDataSource(). This method finds a
+     * representative frame if successful and returns it as a bitmap. This is
+     * useful for generating a thumbnail for an input media source.
+     * 
+     * @return A Bitmap containing a representative video frame, which 
+     *         can be null, if such a frame cannot be retrieved.
+     */
+    public native Bitmap captureFrame();
+    
+    /**
+     * Call this method after setDataSource(). This method finds the optional
+     * graphic or album art associated (embedded or external url linked) the 
+     * related data source.
+     * 
+     * @return null if no such graphic is found.
+     */
+    public native byte[] extractAlbumArt();
+
+    /**
+     * Call it when one is done with the object. This method releases the memory
+     * allocated internally.
+     */
+    public native void release();
+    private native void native_setup(); 
+
+    private native final void native_finalize();
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            native_finalize();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    public static final int MODE_GET_METADATA_ONLY  = 0x01;
+    public static final int MODE_CAPTURE_FRAME_ONLY = 0x02;
+
+    /*
+     * Do not change these values without updating their counterparts
+     * in include/media/mediametadataretriever.h!
+     */
+    public static final int METADATA_KEY_CD_TRACK_NUMBER = 0;
+    public static final int METADATA_KEY_ALBUM           = 1;
+    public static final int METADATA_KEY_ARTIST          = 2;
+    public static final int METADATA_KEY_AUTHOR          = 3;
+    public static final int METADATA_KEY_COMPOSER        = 4;
+    public static final int METADATA_KEY_DATE            = 5;
+    public static final int METADATA_KEY_GENRE           = 6;
+    public static final int METADATA_KEY_TITLE           = 7;
+    public static final int METADATA_KEY_YEAR            = 8;
+    public static final int METADATA_KEY_DURATION        = 9;
+    public static final int METADATA_KEY_NUM_TRACKS      = 10;
+    public static final int METADATA_KEY_IS_DRM_CRIPPLED = 11;
+    public static final int METADATA_KEY_CODEC           = 12;
+    public static final int METADATA_KEY_RATING          = 13;
+    public static final int METADATA_KEY_COMMENT         = 14;
+    public static final int METADATA_KEY_COPYRIGHT       = 15;
+    public static final int METADATA_KEY_BIT_RATE        = 16;
+    public static final int METADATA_KEY_FRAME_RATE      = 17;
+    public static final int METADATA_KEY_VIDEO_FORMAT    = 18;
+    public static final int METADATA_KEY_VIDEO_HEIGHT    = 19;
+    public static final int METADATA_KEY_VIDEO_WIDTH     = 20;
+    // Add more here...
+}
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
new file mode 100644
index 0000000..369af3b
--- /dev/null
+++ b/media/java/android/media/MediaPlayer.java
@@ -0,0 +1,1211 @@
+/*
+ * Copyright (C) 2006 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.media;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.PowerManager;
+import android.util.Log;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.graphics.Bitmap;
+import android.media.AudioManager;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * MediaPlayer class can be used to control playback
+ * of audio/video files and streams. An example on how to use the methods in
+ * this class can be found in {@link android.widget.VideoView}.
+ * Please see <a href="{@docRoot}guide/topics/media/index.html">Audio and Video</a>
+ * for additional help using MediaPlayer.
+ *
+ * <p>Topics covered here are:
+ * <ol>
+ * <li><a href="#StateDiagram">State Diagram</a>
+ * <li><a href="#Valid_and_Invalid_States">Valid and Invalid States</a>
+ * <li><a href="#Permissions">Permissions</a>
+ * </ol>
+ *
+ * <a name="StateDiagram"></a>
+ * <h3>State Diagram</h3>
+ *
+ * <p>Playback control of audio/video files and streams is managed as a state
+ * machine. The following diagram shows the life cycle and the states of a
+ * MediaPlayer object driven by the supported playback control operations.
+ * The ovals represent the states a MediaPlayer object may reside
+ * in. The arcs represent the playback control operations that drive the object
+ * state transition. There are two types of arcs. The arcs with a single arrow 
+ * head represent synchronous method calls, while those with
+ * a double arrow head represent asynchronous method calls.</p>
+ *
+ * <p><img src="../../../images/mediaplayer_state_diagram.gif"
+ *         alt="MediaPlayer State diagram"
+ *         border="0" /></p>
+ *
+ * <p>From this state diagram, one can see that a MediaPlayer object has the
+ *    following states:</p>
+ * <ul>
+ *     <li>When a MediaPlayer object is just created using <code>new</code> or 
+ *         after {@link #reset()} is called, it is in the <em>Idle</em> state; and after 
+ *         {@link #release()} is called, it is in the <em>End</em> state. Between these 
+ *         two states is the life cycle of the MediaPlayer object. 
+ *         <ul>
+ *         <li>There is a subtle but important difference between a newly constructed 
+ *         MediaPlayer object and the MediaPlayer object after {@link #reset()} 
+ *         is called. It is a programming error to invoke methods such 
+ *         as {@link #getCurrentPosition()}, 
+ *         {@link #getDuration()}, {@link #getVideoHeight()}, 
+ *         {@link #getVideoWidth()}, {@link #setAudioStreamType(int)},
+ *         {@link #setLooping(boolean)},
+ *         {@link #setVolume(float, float)}, {@link #pause()}, {@link #start()}, 
+ *         {@link #stop()}, {@link #seekTo(int)}, {@link #prepare()} or 
+ *         {@link #prepareAsync()} in the <em>Idle</em> state for both cases. If any of these 
+ *         methods is called right after a MediaPlayer object is constructed, 
+ *         the user supplied callback method OnErrorListener.onError() won't be 
+ *         called by the internal player engine and the object state remains
+ *         unchanged; but if these methods are called right after {@link #reset()}, 
+ *         the user supplied callback method OnErrorListener.onError() will be
+ *         invoked by the internal player engine and the object will be 
+ *         transfered to the <em>Error</em> state. </li>
+ *         <li>It is also recommended that once 
+ *         a MediaPlayer object is no longer being used, call {@link #release()} immediately 
+ *         so that resources used by the internal player engine associated with the 
+ *         MediaPlayer object can be released immediately. Resource may include
+ *         singleton resources such as hardware acceleration components and 
+ *         failure to call {@link #release()} may cause subsequent instances of
+ *         MediaPlayer objects to fallback to software implementations or fail
+ *         altogether. Once the MediaPlayer
+ *         object is in the <em>End</em> state, it can no longer be used and 
+ *         there is no way to bring it back to any other state. </li>
+ *         <li>Furthermore, 
+ *         the MediaPlayer objects created using <code>new</code> is in the 
+ *         <em>Idle</em> state, while those created with one 
+ *         of the overloaded convenient <code>create</code> methods are <em>NOT</em> 
+ *         in the <em>Idle</em> state. In fact, the objects are in the <em>Prepared</em>
+ *         state if the creation using <code>create</code> method is successful.
+ *         </li>
+ *         </ul>
+ *         </li>
+ *     <li>In general, some playback control operation may fail due to various
+ *         reasons, such as unsupported audio/video format, poorly interleaved
+ *         audio/video, resolution too high, streaming timeout, and the like.
+ *         Thus, error reporting and recovery is an important concern under
+ *         these circumstances. Sometimes, due to programming errors, invoking a playback 
+ *         control operation in an invalid state may also occur. Under all these
+ *         error conditions, the internal player engine invokes a user supplied
+ *         OnErrorListener.onError() method if an OnErrorListener has been
+ *         registered beforehand via
+ *         {@link #setOnErrorListener(android.media.MediaPlayer.OnErrorListener)}.
+ *         <ul>
+ *         <li>It is important to note that once an error occurs, the 
+ *         MediaPlayer object enters the <em>Error</em> state (except as noted 
+ *         above), even if an error listener has not been registered by the application.</li>
+ *         <li>In order to reuse a MediaPlayer object that is in the <em>
+ *         Error</em> state and recover from the error, 
+ *         {@link #reset()} can be called to restore the object to its <em>Idle</em>
+ *         state.</li>
+ *         <li>It is good programming practice to have your application 
+ *         register a OnErrorListener to look out for error notifications from 
+ *         the internal player engine.</li> 
+ *         <li>IlleglStateException is
+ *         thrown to prevent programming errors such as calling {@link #prepare()},
+ *         {@link #prepareAsync()}, or one of the overloaded <code>setDataSource
+ *         </code> methods in an invalid state. </li>
+ *         </ul>
+ *         </li>
+ *     <li>Calling
+ *         {@link #setDataSource(FileDescriptor)}, or
+ *         {@link #setDataSource(String)}, or
+ *         {@link #setDataSource(Context, Uri)}, or
+ *         {@link #setDataSource(FileDescriptor, long, long)} transfers a 
+ *         MediaPlayer object in the <em>Idle</em> state to the
+ *         <em>Initialized</em> state.
+ *         <ul>
+ *         <li>An IllegalStateException is thrown if 
+ *         setDataSource() is called in any other state.</li>
+ *         <li>It is good programming 
+ *         practice to always look out for <code>IllegalArgumentException</code> 
+ *         and <code>IOException</code> that may be thrown from the overloaded
+ *         <code>setDataSource</code> methods.</li>
+ *         </ul>
+ *         </li>
+ *     <li>A MediaPlayer object must first enter the <em>Prepared</em> state
+ *         before playback can be started. 
+ *         <ul>
+ *         <li>There are two ways (synchronous vs.
+ *         asynchronous) that the <em>Prepared</em> state can be reached:
+ *         either a call to {@link #prepare()} (synchronous) which
+ *         transfers the object to the <em>Prepared</em> state once the method call
+ *         returns, or a call to {@link #prepareAsync()} (asynchronous) which
+ *         first transfers the object to the <em>Preparing</em> state after the 
+ *         call returns (which occurs almost right way) while the internal 
+ *         player engine continues working on the rest of preparation work
+ *         until the preparation work completes. When the preparation completes or when {@link #prepare()} call returns,
+ *         the internal player engine then calls a user supplied callback method,
+ *         onPrepared() of the OnPreparedListener interface, if an
+ *         OnPreparedListener is registered beforehand via {@link
+ *         #setOnPreparedListener(android.media.MediaPlayer.OnPreparedListener)}.</li>
+ *         <li>It is important to note that
+ *         the <em>Preparing</em> state is a transient state, and the behavior
+ *         of calling any method with side effect while a MediaPlayer object is
+ *         in the <em>Preparing</em> state is undefined.</li>
+ *         <li>An IllegalStateException is 
+ *         thrown if {@link #prepare()} or {@link #prepareAsync()} is called in 
+ *         any other state.</li> 
+ *         <li>While in the <em>Prepared</em> state, properties 
+ *         such as audio/sound volume, screenOnWhilePlaying, looping can be 
+ *         adjusted by invoking the corresponding set methods.</li>
+ *         </ul>
+ *         </li>
+ *     <li>To start the playback, {@link #start()} must be called. After 
+ *         {@link #start()} returns successfully, the MediaPlayer object is in the
+ *         <em>Started</em> state. {@link #isPlaying()} can be called to test 
+ *         whether the MediaPlayer object is in the <em>Started</em> state.
+ *         <ul>
+ *         <li>While in the <em>Started</em> state, the internal player engine calls
+ *         a user supplied OnBufferingUpdateListener.onBufferingUpdate() callback
+ *         method if a OnBufferingUpdateListener has been registered beforehand 
+ *         via {@link #setOnBufferingUpdateListener(OnBufferingUpdateListener)}.
+ *         This callback allows applications to keep track of the buffering status
+ *         while streaming audio/video.</li>
+ *         <li>Calling {@link #start()} has not effect
+ *         on a MediaPlayer object that is already in the <em>Started</em> state.</li>
+ *         </ul>
+ *         </li>
+ *     <li>Playback can be paused and stopped, and the current playback position
+ *         can be adjusted. Playback can be paused via {@link #pause()}. When the call to 
+ *         {@link #pause()} returns, the MediaPlayer object enters the
+ *         <em>Paused</em> state. Note that the transition from the <em>Started</em> 
+ *         state to the <em>Paused</em> state and vice versa happens 
+ *         asynchronously in the player engine. It may take some time before 
+ *         the state is updated in calls to {@link #isPlaying()}, and it can be 
+ *         a number of seconds in the case of streamed content.
+ *         <ul>
+ *         <li>Calling {@link #start()} to resume playback for a paused 
+ *         MediaPlayer object, and the resumed playback
+ *         position is the same as where it was paused. When the call to 
+ *         {@link #start()} returns, the paused MediaPlayer object goes back to
+ *         the <em>Started</em> state.</li>
+ *         <li>Calling {@link #pause()} has no effect on
+ *         a MediaPlayer object that is already in the <em>Paused</em> state.</li>
+ *         </ul>
+ *         </li>
+ *     <li>Calling  {@link #stop()} stops playback and causes a 
+ *         MediaPlayer in the <em>Started</em>, <em>Paused</em>, <em>Prepared
+ *         </em> or <em>PlaybackCompleted</em> state to enter the 
+ *         <em>Stopped</em> state.
+ *         <ul>
+ *         <li>Once in the <em>Stopped</em> state, playback cannot be started 
+ *         until {@link #prepare()} or {@link #prepareAsync()} are called to set
+ *         the MediaPlayer object to the <em>Prepared</em> state again.</li>
+ *         <li>Calling {@link #stop()} has no effect on a MediaPlayer 
+ *         object that is already in the <em>Stopped</em> state.</li>
+ *         </ul>
+ *         </li>
+ *     <li>The playback position can be adjusted with a call to 
+ *         {@link #seekTo(int)}. 
+ *         <ul>
+ *         <li>Although the asynchronuous {@link #seekTo(int)}
+ *         call returns right way, the actual seek operation may take a while to
+ *         finish, especially for audio/video being streamed. When the actual 
+ *         seek operation completes, the internal player engine calls a user 
+ *         supplied OnSeekComplete.onSeekComplete() if an OnSeekCompleteListener
+ *         has been registered beforehand via 
+ *         {@link #setOnSeekCompleteListener(OnSeekCompleteListener)}.</li>
+ *         <li>Please
+ *         note that {@link #seekTo(int)} can also be called in the other states,
+ *         such as <em>Prepared</em>, <em>Paused</em> and <em>PlaybackCompleted
+ *         </em> state.</li>
+ *         <li>Furthermore, the actual current playback position
+ *         can be retrieved with a call to {@link #getCurrentPosition()}, which
+ *         is helpful for applications such as a Music player that need to keep 
+ *         track of the playback progress.</li>
+ *         </ul>
+ *         </li>
+ *     <li>When the playback reaches the end of stream, the playback completes.
+ *         <ul>
+ *         <li>If the looping mode was being set to <var>true</var>with
+ *         {@link #setLooping(boolean)}, the MediaPlayer object shall remain in
+ *         the <em>Started</em> state.</li>
+ *         <li>If the looping mode was set to <var>false
+ *         </var>, the player engine calls a user supplied callback method,
+ *         OnCompletion.onCompletion(), if a OnCompletionListener is registered
+ *         beforehand via {@link #setOnCompletionListener(OnCompletionListener)}.
+ *         The invoke of the callback signals that the object is now in the <em>
+ *         PlaybackCompleted</em> state.</li>
+ *         <li>While in the <em>PlaybackCompleted</em>
+ *         state, calling {@link #start()} can restart the playback from the
+ *         beginning of the audio/video source.</li>
+ * </ul>
+ *
+ *
+ * <a name="Valid_and_Invalid_States"></a>
+ * <h3>Valid and invalid states</h3>
+ *
+ * <table border="0" cellspacing="0" cellpadding="0">
+ * <tr><td>Method Name </p></td>
+ *     <td>Valid Sates </p></td>
+ *     <td>Invalid States </p></td>
+ *     <td>Comments </p></td></tr>
+ * <tr><td>getCurrentPosition </p></td>
+ *     <td>{Idle, Initialized, Prepared, Started, Paused, Stopped, 
+ *         PlaybackCompleted} </p></td>
+ *     <td>{Error}</p></td>
+ *     <td>Successful invoke of this method in a valid state does not change the
+ *         state. Calling this method in an invalid state transfers the object
+ *         to the <em>Error</em> state. </p></td></tr>
+ * <tr><td>getDuration </p></td>
+ *     <td>{Prepared, Started, Paused, Stopped, PlaybackCompleted} </p></td>
+ *     <td>{Idle, Initialized, Error} </p></td>
+ *     <td>Successful invoke of this method in a valid state does not change the
+ *         state. Calling this method in an invalid state transfers the object 
+ *         to the <em>Error</em> state. </p></td></tr>
+ * <tr><td>getVideoHeight </p></td>
+ *     <td>{Idle, Initialized, Prepared, Started, Paused, Stopped, 
+ *         PlaybackCompleted}</p></td>
+ *     <td>{Error}</p></td>
+ *     <td>Successful invoke of this method in a valid state does not change the
+ *         state. Calling this method in an invalid state transfers the object 
+ *         to the <em>Error</em> state.  </p></td></tr>
+ * <tr><td>getVideoWidth </p></td>
+ *     <td>{Idle, Initialized, Prepared, Started, Paused, Stopped,
+ *         PlaybackCompleted}</p></td>
+ *     <td>{Error}</p></td>
+ *     <td>Successful invoke of this method in a valid state does not change 
+ *         the state. Calling this method in an invalid state transfers the 
+ *         object to the <em>Error</em> state. </p></td></tr>
+ * <tr><td>isPlaying </p></td>
+ *     <td>{Idle, Initialized, Prepared, Started, Paused, Stopped, 
+ *          PlaybackCompleted}</p></td>
+ *     <td>{Error}</p></td>
+ *     <td>Successful invoke of this method in a valid state does not change
+ *         the state. Calling this method in an invalid state transfers the 
+ *         object to the <em>Error</em> state. </p></td></tr>
+ * <tr><td>pause </p></td>
+ *     <td>{Started, Paused}</p></td>
+ *     <td>{Idle, Initialized, Prepared, Stopped, PlaybackCompleted, Error}</p></td>
+ *     <td>Successful invoke of this method in a valid state transfers the 
+ *         object to the <em>Paused</em> state. Calling this method in an 
+ *         invalid state transfers the object to the <em>Error</em> state.</p></td></tr>
+ * <tr><td>prepare </p></td>
+ *     <td>{Initialized, Stopped} </p></td>
+ *     <td>{Idle, Prepared, Started, Paused, PlaybackCompleted, Error} </p></td>
+ *     <td>Successful invoke of this method in a valid state transfers the 
+ *         object to the <em>Prepared</em> state. Calling this method in an 
+ *         invalid state throws an IllegalStateException.</p></td></tr>
+ * <tr><td>prepareAsync </p></td>
+ *     <td>{Initialized, Stopped} </p></td>
+ *     <td>{Idle, Prepared, Started, Paused, PlaybackCompleted, Error} </p></td>
+ *     <td>Successful invoke of this method in a valid state transfers the 
+ *         object to the <em>Preparing</em> state. Calling this method in an
+ *         invalid state throws an IllegalStateException.</p></td></tr>
+ * <tr><td>release </p></td>
+ *     <td>any </p></td>
+ *     <td>{} </p></td>
+ *     <td>After {@link #release()}, the object is no longer available. </p></td></tr>
+ * <tr><td>reset </p></td>
+ *     <td>{Idle, Initialized, Prepared, Started, Paused, Stopped, 
+ *         PlaybackCompleted, Error}</p></td>
+ *     <td>{}</p></td>
+ *     <td>After {@link #reset()}, the object is like being just created.</p></td></tr>
+ * <tr><td>seekTo </p></td>
+ *     <td>{Prepared, Started, Paused, PlaybackCompleted} </p></td>
+ *     <td>{Idle, Initialized, Stopped, Error}</p></td>
+ *     <td>Successful invoke of this method in a valid state does not change 
+ *         the state. Calling this method in an invalid state transfers the 
+ *         object to the <em>Error</em> state. </p></td></tr>
+ * <tr><td>setAudioStreamType </p></td>
+ *     <td>{Idle, Initialized, Stopped, Prepared, Started, Paused, 
+ *          PlaybackCompleted}</p></td>
+ *     <td>{Error}</p></td>
+ *     <td>Successful invoke of this method does not change the state.</p></td></tr>
+ * <tr><td>setDataSource </p></td>
+ *     <td>{Idle} </p></td>
+ *     <td>{Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted,
+ *          Error} </p></td>
+ *     <td>Successful invoke of this method in a valid state transfers the 
+ *         object to the <em>Initialized</em> state. Calling this method in an 
+ *         invalid state throws an IllegalStateException.</p></td></tr>
+ * <tr><td>setDisplay </p></td>
+ *     <td>any </p></td>
+ *     <td>{} </p></td>
+ *     <td>This method can be called in any state and calling it does not change
+ *         the object state. </p></td></tr>
+ * <tr><td>setLooping </p></td>
+ *     <td>{Idle, Initialized, Stopped, Prepared, Started, Paused, 
+ *         PlaybackCompleted}</p></td>
+ *     <td>{Error}</p></td>
+ *     <td>Successful invoke of this method in a valid state does not change 
+ *         the state. Calling this method in an 
+ *         invalid state transfers the object to the <em>Error</em> state.</p></td></tr>
+ * <tr><td>isLooping </p></td>
+ *     <td>any </p></td>
+ *     <td>{} </p></td>
+ *     <td>This method can be called in any state and calling it does not change
+ *         the object state. </p></td></tr>
+ * <tr><td>setOnBufferingUpdateListener </p></td>
+ *     <td>any </p></td>
+ *     <td>{} </p></td>
+ *     <td>This method can be called in any state and calling it does not change
+ *         the object state. </p></td></tr>
+ * <tr><td>setOnCompletionListener </p></td>
+ *     <td>any </p></td>
+ *     <td>{} </p></td>
+ *     <td>This method can be called in any state and calling it does not change
+ *         the object state. </p></td></tr>
+ * <tr><td>setOnErrorListener </p></td>
+ *     <td>any </p></td>
+ *     <td>{} </p></td>
+ *     <td>This method can be called in any state and calling it does not change
+ *         the object state. </p></td></tr>
+ * <tr><td>setOnPreparedListener </p></td>
+ *     <td>any </p></td>
+ *     <td>{} </p></td>
+ *     <td>This method can be called in any state and calling it does not change
+ *         the object state. </p></td></tr>
+ * <tr><td>setOnSeekCompleteListener </p></td>
+ *     <td>any </p></td>
+ *     <td>{} </p></td>
+ *     <td>This method can be called in any state and calling it does not change
+ *         the object state. </p></td></tr>
+ * <tr><td>setScreenOnWhilePlaying</></td>
+ *     <td>any </p></td>
+ *     <td>{} </p></td>
+ *     <td>This method can be called in any state and calling it does not change
+ *         the object state.  </p></td></tr>
+ * <tr><td>setVolume </p></td>
+ *     <td>{Idle, Initialized, Stopped, Prepared, Started, Paused, 
+ *          PlaybackCompleted}</p></td>
+ *     <td>{Error}</p></td>
+ *     <td>Successful invoke of this method does not change the state.
+ * <tr><td>setWakeMode </p></td>
+ *     <td>any </p></td>
+ *     <td>{} </p></td>
+ *     <td>This method can be called in any state and calling it does not change
+ *         the object state.</p></td></tr>
+ * <tr><td>start </p></td>
+ *     <td>{Prepared, Started, Paused, PlaybackCompleted}</p></td>
+ *     <td>{Idle, Initialized, Stopped, Error}</p></td>
+ *     <td>Successful invoke of this method in a valid state transfers the 
+ *         object to the <em>Started</em> state. Calling this method in an 
+ *         invalid state transfers the object to the <em>Error</em> state.</p></td></tr>
+ * <tr><td>stop </p></td>
+ *     <td>{Prepared, Started, Stopped, Paused, PlaybackCompleted}</p></td>
+ *     <td>{Idle, Initialized, Error}</p></td>
+ *     <td>Successful invoke of this method in a valid state transfers the 
+ *         object to the <em>Stopped</em> state. Calling this method in an 
+ *         invalid state transfers the object to the <em>Error</em> state.</p></td></tr>
+ * </table>
+ *
+ * <a name="Permissions"></a>
+ * <h3>Permissions</h3>
+ * <p>One may need to declare a corresponding WAKE_LOCK permission {@link
+ * android.R.styleable#AndroidManifestUsesPermission &lt;uses-permission&gt;}
+ * element.
+ *
+ */
+public class MediaPlayer
+{    
+    static {
+        System.loadLibrary("media_jni");
+    }
+    
+    private final static String TAG = "MediaPlayer";
+    
+    private int mNativeContext; // accessed by native methods
+    private int mListenerContext; // accessed by native methods
+    private Surface mSurface; // accessed by native methods
+    private SurfaceHolder  mSurfaceHolder;
+    private EventHandler mEventHandler;
+    private PowerManager.WakeLock mWakeLock = null;
+    private boolean mScreenOnWhilePlaying;
+    private boolean mStayAwake;
+    
+    /**
+     * Default constructor. Consider using one of the create() methods for 
+     * synchronously instantiating a MediaPlayer from a Uri or resource.
+     * <p>When done with the MediaPlayer, you should call  {@link #release()},
+     * to free the resources. If not released, too many MediaPlayer instances may
+     * result in an exception.</p>
+     */
+    public MediaPlayer() {
+   
+        Looper looper;
+        if ((looper = Looper.myLooper()) != null) {
+            mEventHandler = new EventHandler(this, looper);
+        } else if ((looper = Looper.getMainLooper()) != null) {
+            mEventHandler = new EventHandler(this, looper);
+        } else {
+            mEventHandler = null;
+        }
+
+        /* Native setup requires a weak reference to our object.
+         * It's easier to create it here than in C++.
+         */
+        native_setup(new WeakReference<MediaPlayer>(this));
+    }
+  
+    /**
+     * Sets the SurfaceHolder to use for displaying the video portion of the media.
+     * This call is optional. Not calling it when playing back a video will
+     * result in only the audio track being played.
+     * 
+     * @param sh the SurfaceHolder to use for video display
+     */
+    public void setDisplay(SurfaceHolder sh) {
+        mSurfaceHolder = sh;
+        mSurface = sh.getSurface();
+        updateSurfaceScreenOn();
+    }
+
+    /**
+     * Convenience method to create a MediaPlayer for a given Uri.
+     * On success, {@link #prepare()} will already have been called and must not be called again.
+     * <p>When done with the MediaPlayer, you should call  {@link #release()},
+     * to free the resources. If not released, too many MediaPlayer instances will
+     * result in an exception.</p>
+     * 
+     * @param context the Context to use 
+     * @param uri the Uri from which to get the datasource
+     * @return a MediaPlayer object, or null if creation failed
+     */
+    public static MediaPlayer create(Context context, Uri uri) {
+        return create (context, uri, null);
+    }
+    
+    /**
+     * Convenience method to create a MediaPlayer for a given Uri.
+     * On success, {@link #prepare()} will already have been called and must not be called again.
+     * <p>When done with the MediaPlayer, you should call  {@link #release()},
+     * to free the resources. If not released, too many MediaPlayer instances will
+     * result in an exception.</p>
+     * 
+     * @param context the Context to use 
+     * @param uri the Uri from which to get the datasource
+     * @param holder the SurfaceHolder to use for displaying the video
+     * @return a MediaPlayer object, or null if creation failed
+     */
+    public static MediaPlayer create(Context context, Uri uri, SurfaceHolder holder) {
+        
+        try {
+            MediaPlayer mp = new MediaPlayer();
+            mp.setDataSource(context, uri);
+            if (holder != null) {
+                mp.setDisplay(holder);
+            }
+            mp.prepare();
+            return mp;
+        } catch (IOException ex) {
+            Log.d(TAG, "create failed:", ex);
+            // fall through
+        } catch (IllegalArgumentException ex) {
+            Log.d(TAG, "create failed:", ex);
+            // fall through
+        } catch (SecurityException ex) {
+            Log.d(TAG, "create failed:", ex);
+            // fall through
+        }
+
+        return null;
+    }
+
+    /**
+     * Convenience method to create a MediaPlayer for a given resource id.
+     * On success, {@link #prepare()} will already have been called and must not be called again.
+     * <p>When done with the MediaPlayer, you should call  {@link #release()},
+     * to free the resources. If not released, too many MediaPlayer instances will
+     * result in an exception.</p>
+     * 
+     * @param context the Context to use 
+     * @param resid the raw resource id (<var>R.raw.&lt;something></var>) for 
+     *              the resource to use as the datasource
+     * @return a MediaPlayer object, or null if creation failed
+     */
+    public static MediaPlayer create(Context context, int resid) {
+        try {
+            AssetFileDescriptor afd = context.getResources().openRawResourceFd(resid);
+            if (afd == null) return null;
+
+            MediaPlayer mp = new MediaPlayer();
+            mp.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
+            afd.close();
+            mp.prepare();
+            return mp;
+        } catch (IOException ex) {
+            Log.d(TAG, "create failed:", ex);
+            // fall through
+        } catch (IllegalArgumentException ex) {
+            Log.d(TAG, "create failed:", ex);
+           // fall through
+        } catch (SecurityException ex) {
+            Log.d(TAG, "create failed:", ex);
+            // fall through
+        }
+        return null;
+    }
+    
+    /**
+     * Sets the data source as a content Uri.
+     * 
+     * @param context the Context to use when resolving the Uri
+     * @param uri the Content URI of the data you want to play
+     * @throws IllegalStateException if it is called in an invalid state
+     */
+    public void setDataSource(Context context, Uri uri)
+        throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
+        
+        String scheme = uri.getScheme();
+        if(scheme == null || scheme.equals("file")) {
+            setDataSource(uri.getPath());
+            return;
+        }
+
+        AssetFileDescriptor fd = null;
+        try {
+            ContentResolver resolver = context.getContentResolver();
+            fd = resolver.openAssetFileDescriptor(uri, "r");
+            if (fd == null) {
+                return;
+            }
+            // Note: using getDeclaredLength so that our behavior is the same
+            // as previous versions when the content provider is returning
+            // a full file.
+            if (fd.getDeclaredLength() < 0) {
+                setDataSource(fd.getFileDescriptor());
+            } else {
+                setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getDeclaredLength());
+            }
+            return;
+        } catch (SecurityException ex) {
+        } catch (IOException ex) {
+        } finally {
+            if (fd != null) {
+                fd.close();
+            }
+        }
+        setDataSource(uri.toString());
+        return;
+    }
+
+    /**
+     * Sets the data source (file-path or http/rtsp URL) to use.
+     * 
+     * @param path the path of the file, or the http/rtsp URL of the stream you want to play
+     * @throws IllegalStateException if it is called in an invalid state
+     */
+    public native void setDataSource(String path) throws IOException, IllegalArgumentException, IllegalStateException;
+
+    /**
+     * Sets the data source (FileDescriptor) to use. It is the caller's responsibility
+     * to close the file descriptor. It is safe to do so as soon as this call returns.
+     * 
+     * @param fd the FileDescriptor for the file you want to play
+     * @throws IllegalStateException if it is called in an invalid state
+     */
+    public void setDataSource(FileDescriptor fd) 
+            throws IOException, IllegalArgumentException, IllegalStateException {
+        // intentionally less than LONG_MAX
+        setDataSource(fd, 0, 0x7ffffffffffffffL);
+    }
+    
+    /**
+     * Sets the data source (FileDescriptor) to use.  It is the caller's responsibility
+     * to close the file descriptor. It is safe to do so as soon as this call returns.
+     * 
+     * @param fd the FileDescriptor for the file you want to play
+     * @param offset the offset into the file where the data to be played starts, in bytes
+     * @param length the length in bytes of the data to be played
+     * @throws IllegalStateException if it is called in an invalid state
+     */
+    public native void setDataSource(FileDescriptor fd, long offset, long length) 
+            throws IOException, IllegalArgumentException, IllegalStateException;
+
+    /**
+     * Prepares the player for playback, synchronously.
+     * 
+     * After setting the datasource and the display surface, you need to either
+     * call prepare() or prepareAsync(). For files, it is OK to call prepare(),
+     * which blocks until MediaPlayer is ready for playback.
+     * 
+     * @throws IllegalStateException if it is called in an invalid state
+     */
+    public native void prepare() throws IOException, IllegalStateException;
+    
+    /**
+     * Prepares the player for playback, asynchronously.
+     * 
+     * After setting the datasource and the display surface, you need to either
+     * call prepare() or prepareAsync(). For streams, you should call prepareAsync(),
+     * which returns immediately, rather than blocking until enough data has been
+     * buffered.
+     * 
+     * @throws IllegalStateException if it is called in an invalid state
+     */
+    public native void prepareAsync() throws IllegalStateException;
+    
+    /**
+     * Starts or resumes playback. If playback had previously been paused,
+     * playback will continue from where it was paused. If playback had
+     * been stopped, or never started before, playback will start at the
+     * beginning.
+     * 
+     * @throws IllegalStateException if it is called in an invalid state
+     */
+    public  void start() throws IllegalStateException {
+        stayAwake(true);
+        _start();
+    }
+
+    private native void _start() throws IllegalStateException;
+    
+    /**
+     * Stops playback after playback has been stopped or paused. 
+     * 
+     * @throws IllegalStateException if the internal player engine has not been
+     * initialized.
+     */
+    public void stop() throws IllegalStateException {
+        stayAwake(false);
+        _stop();
+    }
+
+    private native void _stop() throws IllegalStateException;
+    
+    /**
+     * Pauses playback. Call start() to resume.
+     * 
+     * @throws IllegalStateException if the internal player engine has not been
+     * initialized.
+     */
+    public void pause() throws IllegalStateException {
+        stayAwake(false);
+        _pause();
+    }
+
+    private native void _pause() throws IllegalStateException;
+    
+    /**
+     * Set the low-level power management behavior for this MediaPlayer.  This
+     * can be used when the MediaPlayer is not playing through a SurfaceHolder
+     * set with {@link #setDisplay(SurfaceHolder)} and thus can use the
+     * high-level {@link #setScreenOnWhilePlaying(boolean)} feature.
+     * 
+     * <p>This function has the MediaPlayer access the low-level power manager
+     * service to control the device's power usage while playing is occurring.
+     * The parameter is a combination of {@link android.os.PowerManager} wake flags.
+     * Use of this method requires {@link android.Manifest.permission#WAKE_LOCK}
+     * permission.
+     * By default, no attempt is made to keep the device awake during playback.
+     * 
+     * @param context the Context to use
+     * @param mode    the power/wake mode to set
+     * @see android.os.PowerManager
+     */
+    public void setWakeMode(Context context, int mode) {
+        boolean washeld = false;
+        if (mWakeLock != null) {
+            if (mWakeLock.isHeld()) {
+                washeld = true;
+                mWakeLock.release();
+            }
+            mWakeLock = null;
+        }
+
+        PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
+        mWakeLock = pm.newWakeLock(mode|PowerManager.ON_AFTER_RELEASE, MediaPlayer.class.getName());
+        mWakeLock.setReferenceCounted(false);
+        if (washeld) {
+            mWakeLock.acquire();
+        }
+    }
+    
+    /**
+     * Control whether we should use the attached SurfaceHolder to keep the
+     * screen on while video playback is occurring.  This is the preferred
+     * method over {@link #setWakeMode} where possible, since it doesn't
+     * require that the application have permission for low-level wake lock
+     * access.
+     * 
+     * @param screenOn Supply true to keep the screen on, false to allow it
+     * to turn off.
+     */
+    public void setScreenOnWhilePlaying(boolean screenOn) {
+        if (mScreenOnWhilePlaying != screenOn) {
+            mScreenOnWhilePlaying = screenOn;
+            updateSurfaceScreenOn();
+        }
+    }
+    
+    private void stayAwake(boolean awake) {
+        if (mWakeLock != null) {
+            if (awake && !mWakeLock.isHeld()) {
+                mWakeLock.acquire();
+            } else if (!awake && mWakeLock.isHeld()) {
+                mWakeLock.release();
+            }
+        }
+        mStayAwake = awake;
+        updateSurfaceScreenOn();
+    }
+    
+    private void updateSurfaceScreenOn() {
+        if (mSurfaceHolder != null) {
+            mSurfaceHolder.setKeepScreenOn(mScreenOnWhilePlaying && mStayAwake);
+        }
+    }
+    
+    /**
+     * Returns the width of the video.
+     * 
+     * @return the width of the video, or 0 if there is no video,
+     * no display surface was set, or prepare()/prepareAsync()
+     * have not completed yet
+     */
+    public native int getVideoWidth();
+    
+    /**
+     * Returns the height of the video.
+     * 
+     * @return the height of the video, or 0 if there is no video,
+     * no display surface was set, or prepare()/prepareAsync()
+     * have not completed yet
+     */
+    public native int getVideoHeight();
+    
+    /**
+     * Checks whether the MediaPlayer is playing.
+     * 
+     * @return true if currently playing, false otherwise
+     */
+    public native boolean isPlaying();
+    
+    /**
+     * Seeks to specified time position.
+     * 
+     * @param msec the offset in milliseconds from the start to seek to
+     * @throws IllegalStateException if the internal player engine has not been
+     * initialized
+     */
+    public native void seekTo(int msec) throws IllegalStateException;
+    
+    /**
+     * Gets the current playback position.
+     * 
+     * @return the current position in milliseconds
+     */
+    public native int getCurrentPosition();
+    
+    /**
+     * Gets the duration of the file.
+     * 
+     * @return the duration in milliseconds
+     */
+    public native int getDuration();
+    
+    /**
+     * Releases resources associated with this MediaPlayer object.
+     * It is considered good practice to call this method when you're
+     * done using the MediaPlayer.
+     */
+    public void release() {
+        stayAwake(false);
+        updateSurfaceScreenOn();
+        mOnPreparedListener = null;
+        mOnBufferingUpdateListener = null;
+        mOnCompletionListener = null;
+        mOnSeekCompleteListener = null;
+        mOnErrorListener = null;
+        mOnVideoSizeChangedListener = null;
+        _release();
+    }
+
+    private native void _release();
+    
+    /**
+     * Resets the MediaPlayer to its uninitialized state. After calling
+     * this method, you will have to initialize it again by setting the
+     * data source and calling prepare().
+     */
+    public void reset() {
+        stayAwake(false);
+        _reset();
+        // make sure none of the listeners get called anymore
+        mEventHandler.removeCallbacksAndMessages(null);
+    }
+    
+    private native void _reset();
+    
+    /**
+     * Sets the audio stream type for this MediaPlayer. See {@link AudioManager}
+     * for a list of stream types.
+     *  
+     * @param streamtype the audio stream type
+     * @see android.media.AudioManager
+     */
+    public native void setAudioStreamType(int streamtype);
+
+    /**
+     * Sets the player to be looping or non-looping.
+     * 
+     * @param looping whether to loop or not
+     */
+    public native void setLooping(boolean looping);
+
+    /**
+     * Checks whether the MediaPlayer is looping or non-looping.
+     * 
+     * @return true if the MediaPlayer is currently looping, false otherwise
+     */
+    public native boolean isLooping();
+
+    /**
+     * Sets the volume on this player. 
+     * This API is recommended for balancing the output of audio streams
+     * within an application. Unless you are writing an application to
+     * control user settings, this API should be used in preference to
+     * {@link AudioManager#setStreamVolume(int, int, int)} which sets the volume of ALL streams of
+     * a particular type. Note that the passed volume values are raw scalars.
+     * UI controls should be scaled logarithmically.
+     *
+     * @param leftVolume left volume scalar
+     * @param rightVolume right volume scalar
+     */
+    public native void setVolume(float leftVolume, float rightVolume);
+
+    /**
+     * Currently not implemented, returns null.
+     * @deprecated
+     * @hide
+     */
+    public native Bitmap getFrameAt(int msec) throws IllegalStateException;
+ 
+    private native final void native_setup(Object mediaplayer_this);
+    private native final void native_finalize();
+    @Override
+    protected void finalize() { native_finalize(); }
+
+    /* Do not change these values without updating their counterparts
+     * in include/media/mediaplayer.h!
+     */
+    private static final int MEDIA_NOP = 0; // interface test message
+    private static final int MEDIA_PREPARED = 1;
+    private static final int MEDIA_PLAYBACK_COMPLETE = 2;
+    private static final int MEDIA_BUFFERING_UPDATE = 3;
+    private static final int MEDIA_SEEK_COMPLETE = 4;
+    private static final int MEDIA_SET_VIDEO_SIZE = 5;
+    private static final int MEDIA_ERROR = 100;
+
+    // error codes from framework that indicate content issues
+    // contained in arg1 of error message
+
+    // Seek not supported - live stream
+    private static final int ERROR_SEEK_NOT_SUPPORTED = 42;
+
+    // A/V interleave exceeds the progressive streaming buffer
+    private static final int ERROR_CONTENT_IS_POORLY_INTERLEAVED = 43;
+
+    // video decoder is falling behind - content is too complex
+    private static final int ERROR_VIDEO_TRACK_IS_FALLING_BEHIND = 44;
+
+    private class EventHandler extends Handler
+    {
+        private MediaPlayer mMediaPlayer;
+
+        public EventHandler(MediaPlayer mp, Looper looper) {
+            super(looper);
+            mMediaPlayer = mp;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (mMediaPlayer.mNativeContext == 0) {
+                Log.w(TAG, "mediaplayer went away with unhandled events");
+                return;
+            }
+            switch(msg.what) {
+            case MEDIA_PREPARED:
+                if (mOnPreparedListener != null)
+                    mOnPreparedListener.onPrepared(mMediaPlayer);
+                return;
+
+            case MEDIA_PLAYBACK_COMPLETE:
+                if (mOnCompletionListener != null)
+                    mOnCompletionListener.onCompletion(mMediaPlayer);
+                stayAwake(false);
+                return;
+
+            case MEDIA_BUFFERING_UPDATE:
+                if (mOnBufferingUpdateListener != null)
+                    mOnBufferingUpdateListener.onBufferingUpdate(mMediaPlayer, msg.arg1);
+                return;
+
+            case MEDIA_SEEK_COMPLETE:
+              if (mOnSeekCompleteListener != null)
+                  mOnSeekCompleteListener.onSeekComplete(mMediaPlayer);
+              return;
+
+            case MEDIA_SET_VIDEO_SIZE:
+              if (mOnVideoSizeChangedListener != null)
+                  mOnVideoSizeChangedListener.onVideoSizeChanged(mMediaPlayer, msg.arg1, msg.arg2);
+              return;
+
+            case MEDIA_ERROR:
+                Log.e(TAG, "Error (" + msg.arg1 + "," + msg.arg2 + ")");
+                boolean error_was_handled = false;
+                if (mOnErrorListener != null) {
+                    error_was_handled = mOnErrorListener.onError(mMediaPlayer, msg.arg1, msg.arg2);
+                }
+                if (mOnCompletionListener != null && ! error_was_handled) {
+                    mOnCompletionListener.onCompletion(mMediaPlayer);
+                }
+                stayAwake(false);
+                return;
+            case MEDIA_NOP: // interface test message - ignore
+                break;
+
+            default:
+                Log.e(TAG, "Unknown message type " + msg.what);
+                return;
+            }
+        }
+    }
+
+    /**
+     * Called from native code when an interesting event happens.  This method
+     * just uses the EventHandler system to post the event back to the main app thread.
+     * We use a weak reference to the original MediaPlayer object so that the native
+     * code is safe from the object disappearing from underneath it.  (This is
+     * the cookie passed to native_setup().)
+     */
+    private static void postEventFromNative(Object mediaplayer_ref,
+                                            int what, int arg1, int arg2, Object obj)
+    {
+        MediaPlayer mp = (MediaPlayer)((WeakReference)mediaplayer_ref).get();
+        if (mp == null) {
+            return;
+        }
+
+        if (mp.mEventHandler != null) {
+            Message m = mp.mEventHandler.obtainMessage(what, arg1, arg2, obj);
+            mp.mEventHandler.sendMessage(m);
+        }
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when the media
+     * source is ready for playback.
+     */
+    public interface OnPreparedListener
+    {
+        /**
+         * Called when the media file is ready for playback.
+         * 
+         * @param mp the MediaPlayer that is ready for playback
+         */
+        void onPrepared(MediaPlayer mp);
+    }
+
+    /**
+     * Register a callback to be invoked when the media source is ready
+     * for playback.
+     *
+     * @param l the callback that will be run
+     */
+    public void setOnPreparedListener(OnPreparedListener l)
+    {
+        mOnPreparedListener = l;
+    }
+
+    private OnPreparedListener mOnPreparedListener;
+
+    /**
+     * Interface definition for a callback to be invoked when playback of
+     * a media source has completed.
+     */
+    public interface OnCompletionListener
+    {
+        /**
+         * Called when the end of a media source is reached during playback.
+         * 
+         * @param mp the MediaPlayer that reached the end of the file
+         */
+        void onCompletion(MediaPlayer mp);
+    }
+
+    /**
+     * Register a callback to be invoked when the end of a media source
+     * has been reached during playback.
+     *
+     * @param l the callback that will be run
+     */
+    public void setOnCompletionListener(OnCompletionListener l)
+    {
+        mOnCompletionListener = l;
+    }
+
+    private OnCompletionListener mOnCompletionListener;
+
+    /**
+     * Interface definition of a callback to be invoked indicating buffering
+     * status of a media resource being streamed over the network.
+     */
+    public interface OnBufferingUpdateListener
+    {
+        /**
+         * Called to update status in buffering a media stream.
+         * 
+         * @param mp      the MediaPlayer the update pertains to
+         * @param percent the percentage (0-100) of the buffer
+         *                that has been filled thus far
+         */
+        void onBufferingUpdate(MediaPlayer mp, int percent);
+    }
+   
+    /**
+     * Register a callback to be invoked when the status of a network
+     * stream's buffer has changed.
+     *
+     * @param l the callback that will be run
+     */
+    public void setOnBufferingUpdateListener(OnBufferingUpdateListener l)
+    {
+        mOnBufferingUpdateListener = l;
+    }
+
+    private OnBufferingUpdateListener mOnBufferingUpdateListener;
+    
+    /**
+     * Interface definition of a callback to be invoked indicating
+     * the completion of a seek operation.
+     */
+    public interface OnSeekCompleteListener
+    {
+        /**
+         * Called to indicate the completion of a seek operation.
+         * 
+         * @param mp the MediaPlayer that issued the seek operation
+         */
+        public void onSeekComplete(MediaPlayer mp);
+    }
+    
+    /**
+     * Register a callback to be invoked when a seek operation has been
+     * completed.
+     * 
+     * @param l the callback that will be run
+     */
+    public void setOnSeekCompleteListener(OnSeekCompleteListener l)
+    {
+        mOnSeekCompleteListener = l;
+    }
+    
+    private OnSeekCompleteListener mOnSeekCompleteListener;
+
+    /**
+     * Interface definition of a callback to be invoked when the
+     * video size is first known or updated
+     * FIXME: Unhide this API after approval
+     * @hide
+     */
+    public interface OnVideoSizeChangedListener
+    {
+        /**
+         * Called to indicate the video size
+         * 
+         * @param mp        the MediaPlayer associated with this callback
+         * @param width     the width of the video
+         * @param height    the height of the video
+         * @hide
+         */
+        public void onVideoSizeChanged(MediaPlayer mp, int width, int height);
+    }
+    
+    /**
+     * Register a callback to be invoked when the video size is
+     * known or updated.
+     * 
+     * @param l the callback that will be run
+     * @hide
+     */
+    public void setOnVideoSizeChangedListener(OnVideoSizeChangedListener l)
+    {
+        mOnVideoSizeChangedListener = l;
+    }
+    
+    private OnVideoSizeChangedListener mOnVideoSizeChangedListener;
+
+    /* Do not change these values without updating their counterparts
+     * in include/media/mediaplayer.h!
+     */
+    /** Unspecified media player error.
+     * @see android.media.MediaPlayer.OnErrorListener
+     */
+    public static final int MEDIA_ERROR_UNKNOWN = 1;
+    /** Media server died. In this case, the application must release the
+     * MediaPlayer object and instantiate a new one. 
+     * @see android.media.MediaPlayer.OnErrorListener
+     */
+    public static final int MEDIA_ERROR_SERVER_DIED = 100;
+    
+
+    /**
+     * Interface definition of a callback to be invoked when there
+     * has been an error during an asynchronous operation (other errors
+     * will throw exceptions at method call time).
+     */
+    public interface OnErrorListener
+    {
+        /**
+         * Called to indicate an error.
+         * 
+         * @param mp      the MediaPlayer the error pertains to
+         * @param what    the type of error that has occurred:
+         * <ul>
+         * <li>{@link #MEDIA_ERROR_UNKNOWN}
+         * <li>{@link #MEDIA_ERROR_SERVER_DIED}
+         * </ul>
+         * @param extra   an extra code, specific to the error type
+         * @return True if the method handled the error, false if it didn't.
+         * Returning false, or not having an OnErrorListener at all, will
+         * cause the OnCompletionListener to be called.
+         */
+        boolean onError(MediaPlayer mp, int what, int extra);
+    }
+   
+    /**
+     * Register a callback to be invoked when an error has happened
+     * during an asynchronous operation.
+     * 
+     * @param l the callback that will be run
+     */
+    public void setOnErrorListener(OnErrorListener l)
+    {
+        mOnErrorListener = l;
+    }
+
+    private OnErrorListener mOnErrorListener;
+}
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
new file mode 100644
index 0000000..4906cbb
--- /dev/null
+++ b/media/java/android/media/MediaRecorder.java
@@ -0,0 +1,491 @@
+/*
+ * Copyright (C) 2007 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.media;
+
+import android.hardware.Camera;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+import android.view.Surface;
+import java.io.IOException;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileDescriptor;
+import java.lang.ref.WeakReference;
+
+/**
+ * Used to record audio and video. The recording control is based on a
+ * simple state machine (see below).
+ *
+ * <p><img src="{@docRoot}images/mediarecorder_state_diagram.gif" border="0" />
+ * </p>
+ *
+ * <p>A common case of using MediaRecorder to record audio works as follows:
+ *
+ * <pre>MediaRecorder recorder = new MediaRecorder();
+ * recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
+ * recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
+ * recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
+ * recorder.setOutputFile(PATH_NAME);
+ * recorder.prepare();
+ * recorder.start();   // Recording is now started
+ * ...
+ * recorder.stop();
+ * recorder.reset();   // You can reuse the object by going back to setAudioSource() step
+ * recorder.release(); // Now the object cannot be reused
+ * </pre>
+ *
+ * <p>See the <a href="{@docRoot}guide/topics/media/index.html">Audio and Video</a>
+ * documentation for additional help with using MediaRecorder.
+ */
+public class MediaRecorder
+{
+    static {
+        System.loadLibrary("media_jni");
+    }
+    private final static String TAG = "MediaRecorder";
+
+    // The two fields below are accessed by native methods
+    @SuppressWarnings("unused")
+    private int mNativeContext;
+
+    @SuppressWarnings("unused")
+    private Surface mSurface;
+
+    private String mPath;
+    private FileDescriptor mFd;
+    private EventHandler mEventHandler;
+    private OnErrorListener mOnErrorListener;
+
+    /**
+     * Default constructor.
+     */
+    public MediaRecorder() {
+
+        Looper looper;
+        if ((looper = Looper.myLooper()) != null) {
+            mEventHandler = new EventHandler(this, looper);
+        } else if ((looper = Looper.getMainLooper()) != null) {
+            mEventHandler = new EventHandler(this, looper);
+        } else {
+            mEventHandler = null;
+        }
+
+        /* Native setup requires a weak reference to our object.
+         * It's easier to create it here than in C++.
+         */
+        native_setup(new WeakReference<MediaRecorder>(this));
+    }
+
+    /**
+     * Sets a Camera to use for recording. Use this function to switch
+     * quickly between preview and capture mode without a teardown of
+     * the camera object. Must call before prepare().
+     *
+     * @param c the Camera to use for recording
+     */
+    public native void setCamera(Camera c);
+
+    /**
+     * Sets a Surface to show a preview of recorded media (video). Calls this
+     * before prepare() to make sure that the desirable preview display is
+     * set.
+     *
+     * @param sv the Surface to use for the preview
+     */
+    public void setPreviewDisplay(Surface sv) {
+        mSurface = sv;
+    }
+
+    /**
+     * Defines the audio source. These constants are used with
+     * {@link MediaRecorder#setAudioSource(int)}.
+     */
+    public final class AudioSource {
+      /* Do not change these values without updating their counterparts
+       * in include/media/mediarecorder.h!
+       */
+        private AudioSource() {}
+        public static final int DEFAULT = 0;
+        /** Microphone audio source */
+        public static final int MIC = 1;
+    }
+
+    /**
+     * Defines the video source. These constants are used with
+     * {@link MediaRecorder#setVideoSource(int)}.
+     */
+    public final class VideoSource {
+      /* Do not change these values without updating their counterparts
+       * in include/media/mediarecorder.h!
+       */
+        private VideoSource() {}
+        public static final int DEFAULT = 0;
+        /** Camera video source */
+        public static final int CAMERA = 1;
+    }
+
+    /**
+     * Defines the output format. These constants are used with
+     * {@link MediaRecorder#setOutputFormat(int)}.
+     */
+    public final class OutputFormat {
+      /* Do not change these values without updating their counterparts
+       * in include/media/mediarecorder.h!
+       */
+        private OutputFormat() {}
+        public static final int DEFAULT = 0;
+        /** 3GPP media file format*/
+        public static final int THREE_GPP = 1;
+        /** MPEG4 media file format*/
+        public static final int MPEG_4 = 2;
+        /** Raw AMR file format */
+        public static final int RAW_AMR = 3;
+    };
+
+    /**
+     * Defines the audio encoding. These constants are used with
+     * {@link MediaRecorder#setAudioEncoder(int)}.
+     */
+    public final class AudioEncoder {
+      /* Do not change these values without updating their counterparts
+       * in include/media/mediarecorder.h!
+       */
+        private AudioEncoder() {}
+        public static final int DEFAULT = 0;
+        /** AMR (Narrowband) audio codec */
+        public static final int AMR_NB = 1;
+        //public static final AAC = 2;  currently unsupported
+    }
+
+    /**
+     * Defines the video encoding. These constants are used with
+     * {@link MediaRecorder#setVideoEncoder(int)}.
+     */
+    public final class VideoEncoder {
+      /* Do not change these values without updating their counterparts
+       * in include/media/mediarecorder.h!
+       */
+        private VideoEncoder() {}
+        public static final int DEFAULT = 0;
+        public static final int H263 = 1;
+        public static final int H264 = 2;
+        public static final int MPEG_4_SP = 3;
+    }
+
+    /**
+     * Sets the audio source to be used for recording. If this method is not
+     * called, the output file will not contain an audio track. The source needs
+     * to be specified before setting recording-parameters or encoders. Call
+     * this only before setOutputFormat().
+     *
+     * @param audio_source the audio source to use
+     * @throws IllegalStateException if it is called after setOutputFormat()
+     * @see android.media.MediaRecorder.AudioSource
+     */
+    public native void setAudioSource(int audio_source)
+            throws IllegalStateException;
+
+    /**
+     * Sets the video source to be used for recording. If this method is not
+     * called, the output file will not contain an video track. The source needs
+     * to be specified before setting recording-parameters or encoders. Call
+     * this only before setOutputFormat().
+     *
+     * @param video_source the video source to use
+     * @throws IllegalStateException if it is called after setOutputFormat()
+     * @see android.media.MediaRecorder.VideoSource
+     */
+    public native void setVideoSource(int video_source)
+            throws IllegalStateException;
+
+    /**
+     * Sets the format of the output file produced during recording. Call this
+     * after setAudioSource()/setVideoSource() but before prepare().
+     *
+     * @param output_format the output format to use. The output format
+     * needs to be specified before setting recording-parameters or encoders.
+     * @throws IllegalStateException if it is called after prepare() or before
+     * setAudioSource()/setVideoSource().
+     * @see android.media.MediaRecorder.OutputFormat
+     */
+    public native void setOutputFormat(int output_format)
+            throws IllegalStateException;
+
+    /**
+     * Sets the width and height of the video to be captured.  Must be called
+     * after setVideoSource(). Call this after setOutFormat() but before
+     * prepare().
+     *
+     * @param width the width of the video to be captured
+     * @param height the height of the video to be captured
+     * @throws IllegalStateException if it is called after
+     * prepare() or before setOutputFormat()
+     */
+    public native void setVideoSize(int width, int height)
+            throws IllegalStateException;
+
+    /**
+     * Sets the frame rate of the video to be captured.  Must be called
+     * after setVideoSource(). Call this after setOutFormat() but before
+     * prepare().
+     *
+     * @param rate the number of frames per second of video to capture
+     * @throws IllegalStateException if it is called after
+     * prepare() or before setOutputFormat().
+     *
+     * NOTE: On some devices that have auto-frame rate, this sets the
+     * maximum frame rate, not a constant frame rate. Actual frame rate
+     * will vary according to lighting conditions.
+     */
+    public native void setVideoFrameRate(int rate) throws IllegalStateException;
+
+    /**
+     * Sets the audio encoder to be used for recording. If this method is not
+     * called, the output file will not contain an audio track. Call this after
+     * setOutputFormat() but before prepare().
+     *
+     * @param audio_encoder the audio encoder to use.
+     * @throws IllegalStateException if it is called before
+     * setOutputFormat() or after prepare().
+     * @see android.media.MediaRecorder.AudioEncoder
+     */
+    public native void setAudioEncoder(int audio_encoder)
+            throws IllegalStateException;
+
+    /**
+     * Sets the video encoder to be used for recording. If this method is not
+     * called, the output file will not contain an video track. Call this after
+     * setOutputFormat() and before prepare().
+     *
+     * @param video_encoder the video encoder to use.
+     * @throws IllegalStateException if it is called before
+     * setOutputFormat() or after prepare()
+     * @see android.media.MediaRecorder.VideoEncoder
+     */
+    public native void setVideoEncoder(int video_encoder)
+            throws IllegalStateException;
+
+    /**
+     * Pass in the file descriptor of the file to be written. Call this after
+     * setOutputFormat() but before prepare().
+     *
+     * @param fd an open file descriptor to be written into.
+     * @throws IllegalStateException if it is called before
+     * setOutputFormat() or after prepare()
+     */
+    public void setOutputFile(FileDescriptor fd) throws IllegalStateException
+    {
+        mPath = null;
+        mFd = fd;
+    }
+
+    /**
+     * Sets the path of the output file to be produced. Call this after
+     * setOutputFormat() but before prepare().
+     *
+     * @param path The pathname to use.
+     * @throws IllegalStateException if it is called before
+     * setOutputFormat() or after prepare()
+     */
+    public void setOutputFile(String path) throws IllegalStateException
+    {
+        mFd = null;
+        mPath = path;
+    }
+
+    // native implementation
+    private native void _setOutputFile(FileDescriptor fd, long offset, long length)
+        throws IllegalStateException, IOException;
+    private native void _prepare() throws IllegalStateException, IOException;
+
+    /**
+     * Prepares the recorder to begin capturing and encoding data. This method
+     * must be called after setting up the desired audio and video sources,
+     * encoders, file format, etc., but before start().
+     *
+     * @throws IllegalStateException if it is called after
+     * start() or before setOutputFormat().
+     * @throws IOException if prepare fails otherwise.
+     */
+    public void prepare() throws IllegalStateException, IOException
+    {
+        if (mPath != null) {
+            FileOutputStream fos = new FileOutputStream(mPath);
+            try {
+                _setOutputFile(fos.getFD(), 0, 0);
+            } finally {
+                fos.close();
+            }
+        } else if (mFd != null) {
+            _setOutputFile(mFd, 0, 0);
+        } else {
+            throw new IOException("No valid output file");
+        }
+        _prepare();
+    }
+
+    /**
+     * Begins capturing and encoding data to the file specified with
+     * setOutputFile(). Call this after prepare().
+     *
+     * @throws IllegalStateException if it is called before
+     * prepare().
+     */
+    public native void start() throws IllegalStateException;
+
+    /**
+     * Stops recording. Call this after start(). Once recording is stopped,
+     * you will have to configure it again as if it has just been constructed.
+     *
+     * @throws IllegalStateException if it is called before start()
+     */
+    public native void stop() throws IllegalStateException;
+
+    /**
+     * Restarts the MediaRecorder to its idle state. After calling
+     * this method, you will have to configure it again as if it had just been
+     * constructed.
+     */
+    public void reset() {
+        native_reset();
+
+        // make sure none of the listeners get called anymore
+        mEventHandler.removeCallbacksAndMessages(null);
+    }
+
+    private native void native_reset();
+
+    /**
+     * Returns the maximum absolute amplitude that was sampled since the last
+     * call to this method. Call this only after the setAudioSource().
+     *
+     * @return the maximum absolute amplitude measured since the last call, or
+     * 0 when called for the first time
+     * @throws IllegalStateException if it is called before
+     * the audio source has been set.
+     */
+    public native int getMaxAmplitude() throws IllegalStateException;
+
+    /* Do not change this value without updating its counterpart
+     * in include/media/mediarecorder.h!
+     */
+    /** Unspecified media recorder error.
+     * @see android.media.MediaRecorder.OnErrorListener
+     */
+    public static final int MEDIA_RECORDER_ERROR_UNKNOWN = 1;
+
+    /**
+     * Interface definition for a callback to be invoked when an error
+     * occurs while recording.
+     */
+    public interface OnErrorListener
+    {
+        /**
+         * Called when an error occurs while recording.
+         * 
+         * @param mr the MediaRecorder that encountered the error
+         * @param what    the type of error that has occurred:
+         * <ul>
+         * <li>{@link #MEDIA_RECORDER_ERROR_UNKNOWN}
+         * </ul>
+         * @param extra   an extra code, specific to the error type
+         */
+        void onError(MediaRecorder mr, int what, int extra);
+    }
+
+    /**
+     * Register a callback to be invoked when an error occurs while
+     * recording.
+     *
+     * @param l the callback that will be run
+     */
+    public void setOnErrorListener(OnErrorListener l)
+    {
+        mOnErrorListener = l;
+    }
+
+    private class EventHandler extends Handler
+    {
+        private MediaRecorder mMediaRecorder;
+
+        public EventHandler(MediaRecorder mr, Looper looper) {
+            super(looper);
+            mMediaRecorder = mr;
+        }
+
+        /* Do not change this value without updating its counterpart
+         * in include/media/mediarecorder.h!
+         */
+        private static final int MEDIA_RECORDER_EVENT_ERROR = 1;
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (mMediaRecorder.mNativeContext == 0) {
+                Log.w(TAG, "mediarecorder went away with unhandled events");
+                return;
+            }
+            switch(msg.what) {
+            case MEDIA_RECORDER_EVENT_ERROR:
+                if (mOnErrorListener != null)
+                    mOnErrorListener.onError(mMediaRecorder, msg.arg1, msg.arg2);
+
+                return;
+
+            default:
+                Log.e(TAG, "Unknown message type " + msg.what);
+                return;
+            }
+        }
+    }
+
+    /**
+     * Called from native code when an interesting event happens.  This method
+     * just uses the EventHandler system to post the event back to the main app thread.
+     * We use a weak reference to the original MediaRecorder object so that the native
+     * code is safe from the object disappearing from underneath it.  (This is
+     * the cookie passed to native_setup().)
+     */
+    private static void postEventFromNative(Object mediarecorder_ref,
+                                            int what, int arg1, int arg2, Object obj)
+    {
+        MediaRecorder mr = (MediaRecorder)((WeakReference)mediarecorder_ref).get();
+        if (mr == null) {
+            return;
+        }
+
+        if (mr.mEventHandler != null) {
+            Message m = mr.mEventHandler.obtainMessage(what, arg1, arg2, obj);
+            mr.mEventHandler.sendMessage(m);
+        }
+    }
+
+    /**
+     * Releases resources associated with this MediaRecorder object.
+     * It is good practice to call this method when you're done
+     * using the MediaRecorder.
+     */
+    public native void release();
+
+    private native final void native_setup(Object mediarecorder_this) throws IllegalStateException;
+
+    private native final void native_finalize();
+
+    @Override
+    protected void finalize() { native_finalize(); }
+}
diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java
new file mode 100644
index 0000000..fc8476d
--- /dev/null
+++ b/media/java/android/media/MediaScanner.java
@@ -0,0 +1,1352 @@
+/*
+ * Copyright (C) 2007 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.media;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.IContentProvider;
+import android.content.ContentUris;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.provider.MediaStore;
+import android.provider.Settings;
+import android.provider.MediaStore.Audio;
+import android.provider.MediaStore.Images;
+import android.provider.MediaStore.Video;
+import android.provider.MediaStore.Audio.Genres;
+import android.provider.MediaStore.Audio.Playlists;
+import android.sax.Element;
+import android.sax.ElementListener;
+import android.sax.RootElement;
+import android.text.TextUtils;
+import android.util.Config;
+import android.util.Log;
+import android.util.Xml;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+
+import java.io.*;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+
+/**
+ * Internal service that no-one should use directly.
+ *
+ * {@hide}
+ */
+public class MediaScanner
+{    
+    static {
+        System.loadLibrary("media_jni");
+    }
+
+    private final static String TAG = "MediaScanner";
+
+    private static final String[] AUDIO_PROJECTION = new String[] {
+            Audio.Media._ID, // 0
+            Audio.Media.DATA, // 1
+            Audio.Media.DATE_MODIFIED, // 2
+    };
+    
+    private static final int ID_AUDIO_COLUMN_INDEX = 0;
+    private static final int PATH_AUDIO_COLUMN_INDEX = 1;
+    private static final int DATE_MODIFIED_AUDIO_COLUMN_INDEX = 2;
+ 
+    private static final String[] VIDEO_PROJECTION = new String[] {
+            Video.Media._ID, // 0
+            Video.Media.DATA, // 1
+            Video.Media.DATE_MODIFIED, // 2
+    };
+    
+    private static final int ID_VIDEO_COLUMN_INDEX = 0;
+    private static final int PATH_VIDEO_COLUMN_INDEX = 1;
+    private static final int DATE_MODIFIED_VIDEO_COLUMN_INDEX = 2;
+
+    private static final String[] IMAGES_PROJECTION = new String[] {
+            Images.Media._ID, // 0
+            Images.Media.DATA, // 1
+            Images.Media.DATE_MODIFIED, // 2
+    };
+    
+    private static final int ID_IMAGES_COLUMN_INDEX = 0;
+    private static final int PATH_IMAGES_COLUMN_INDEX = 1;
+    private static final int DATE_MODIFIED_IMAGES_COLUMN_INDEX = 2;
+    
+    private static final String[] PLAYLISTS_PROJECTION = new String[] {
+            Audio.Playlists._ID, // 0
+            Audio.Playlists.DATA, // 1
+            Audio.Playlists.DATE_MODIFIED, // 2
+    };
+
+    private static final String[] PLAYLIST_MEMBERS_PROJECTION = new String[] {
+            Audio.Playlists.Members.PLAYLIST_ID, // 0
+     };
+
+    private static final int ID_PLAYLISTS_COLUMN_INDEX = 0;
+    private static final int PATH_PLAYLISTS_COLUMN_INDEX = 1;
+    private static final int DATE_MODIFIED_PLAYLISTS_COLUMN_INDEX = 2;
+
+    private static final String[] GENRE_LOOKUP_PROJECTION = new String[] {
+            Audio.Genres._ID, // 0
+            Audio.Genres.NAME, // 1
+    };
+
+    private static final String RINGTONES_DIR = "/ringtones/";
+    private static final String NOTIFICATIONS_DIR = "/notifications/";
+    private static final String ALARMS_DIR = "/alarms/";
+    private static final String MUSIC_DIR = "/music/";
+    private static final String PODCAST_DIR = "/podcasts/";
+    
+    private static final String[] ID3_GENRES = {
+        // ID3v1 Genres
+        "Blues",
+        "Classic Rock",
+        "Country",
+        "Dance",
+        "Disco",
+        "Funk",
+        "Grunge",
+        "Hip-Hop",
+        "Jazz",
+        "Metal",
+        "New Age",
+        "Oldies",
+        "Other",
+        "Pop",
+        "R&B",
+        "Rap",
+        "Reggae",
+        "Rock",
+        "Techno",
+        "Industrial",
+        "Alternative",
+        "Ska",
+        "Death Metal",
+        "Pranks",
+        "Soundtrack",
+        "Euro-Techno",
+        "Ambient",
+        "Trip-Hop",
+        "Vocal",
+        "Jazz+Funk",
+        "Fusion",
+        "Trance",
+        "Classical",
+        "Instrumental",
+        "Acid",
+        "House",
+        "Game",
+        "Sound Clip",
+        "Gospel",
+        "Noise",
+        "AlternRock",
+        "Bass",
+        "Soul",
+        "Punk",
+        "Space",
+        "Meditative",
+        "Instrumental Pop",
+        "Instrumental Rock",
+        "Ethnic",
+        "Gothic",
+        "Darkwave",
+        "Techno-Industrial",
+        "Electronic",
+        "Pop-Folk",
+        "Eurodance",
+        "Dream",
+        "Southern Rock",
+        "Comedy",
+        "Cult",
+        "Gangsta",
+        "Top 40",
+        "Christian Rap",
+        "Pop/Funk",
+        "Jungle",
+        "Native American",
+        "Cabaret",
+        "New Wave",
+        "Psychadelic",
+        "Rave",
+        "Showtunes",
+        "Trailer",
+        "Lo-Fi",
+        "Tribal",
+        "Acid Punk",
+        "Acid Jazz",
+        "Polka",
+        "Retro",
+        "Musical",
+        "Rock & Roll",
+        "Hard Rock",
+        // The following genres are Winamp extensions
+        "Folk",
+        "Folk-Rock",
+        "National Folk",
+        "Swing",
+        "Fast Fusion",
+        "Bebob",
+        "Latin",
+        "Revival",
+        "Celtic",
+        "Bluegrass",
+        "Avantgarde",
+        "Gothic Rock",
+        "Progressive Rock",
+        "Psychedelic Rock",
+        "Symphonic Rock",
+        "Slow Rock",
+        "Big Band",
+        "Chorus",
+        "Easy Listening",
+        "Acoustic",
+        "Humour",
+        "Speech",
+        "Chanson",
+        "Opera",
+        "Chamber Music",
+        "Sonata",
+        "Symphony",
+        "Booty Bass",
+        "Primus",
+        "Porn Groove",
+        "Satire",
+        "Slow Jam",
+        "Club",
+        "Tango",
+        "Samba",
+        "Folklore",
+        "Ballad",
+        "Power Ballad",
+        "Rhythmic Soul",
+        "Freestyle",
+        "Duet",
+        "Punk Rock",
+        "Drum Solo",
+        "A capella",
+        "Euro-House",
+        "Dance Hall"
+    };
+
+    private int mNativeContext;
+    private Context mContext;
+    private IContentProvider mMediaProvider;
+    private Uri mAudioUri;
+    private Uri mVideoUri;
+    private Uri mImagesUri;
+    private Uri mThumbsUri;
+    private Uri mGenresUri;
+    private Uri mPlaylistsUri;
+    private boolean mProcessPlaylists, mProcessGenres;
+
+    // used when scanning the image database so we know whether we have to prune
+    // old thumbnail files
+    private int mOriginalCount;
+    /** Whether the scanner has set a default sound for the ringer ringtone. */
+    private boolean mDefaultRingtoneSet;
+    /** Whether the scanner has set a default sound for the notification ringtone. */
+    private boolean mDefaultNotificationSet;
+    /** The filename for the default sound for the ringer ringtone. */
+    private String mDefaultRingtoneFilename;
+    /** The filename for the default sound for the notification ringtone. */
+    private String mDefaultNotificationFilename;
+    /**
+     * The prefix for system properties that define the default sound for
+     * ringtones. Concatenate the name of the setting from Settings
+     * to get the full system property.
+     */
+    private static final String DEFAULT_RINGTONE_PROPERTY_PREFIX = "ro.config.";
+    
+    // set to true if file path comparisons should be case insensitive.
+    // this should be set when scanning files on a case insensitive file system.
+    private boolean mCaseInsensitivePaths;
+    
+    private BitmapFactory.Options mBitmapOptions = new BitmapFactory.Options();
+
+    private static class FileCacheEntry {
+        Uri mTableUri;
+        long mRowId;
+        String mPath;
+        long mLastModified;
+        boolean mSeenInFileSystem;
+        boolean mLastModifiedChanged;
+        
+        FileCacheEntry(Uri tableUri, long rowId, String path, long lastModified) {
+            mTableUri = tableUri;
+            mRowId = rowId;
+            mPath = path;
+            mLastModified = lastModified;
+            mSeenInFileSystem = false;
+            mLastModifiedChanged = false;
+        }
+
+        @Override
+        public String toString() {
+            return mPath;
+        }
+    }
+    
+    // hashes file path to FileCacheEntry.  
+    // path should be lower case if mCaseInsensitivePaths is true
+    private HashMap<String, FileCacheEntry> mFileCache; 
+
+    private ArrayList<FileCacheEntry> mPlayLists;
+    private HashMap<String, Uri> mGenreCache;
+
+
+    public MediaScanner(Context c) {
+        native_setup();
+        mContext = c;
+        mBitmapOptions.inSampleSize = 1;
+        mBitmapOptions.inJustDecodeBounds = true;
+        
+        setDefaultRingtoneFileNames();
+    }
+
+    private void setDefaultRingtoneFileNames() {
+        mDefaultRingtoneFilename = SystemProperties.get(DEFAULT_RINGTONE_PROPERTY_PREFIX
+                + Settings.System.RINGTONE);
+        mDefaultNotificationFilename = SystemProperties.get(DEFAULT_RINGTONE_PROPERTY_PREFIX
+                + Settings.System.NOTIFICATION_SOUND);
+    }
+    
+    private MyMediaScannerClient mClient = new MyMediaScannerClient();
+    
+    private class MyMediaScannerClient implements MediaScannerClient {
+    
+        private String mArtist;
+        private String mAlbumArtist;    // use this if mArtist is missing
+        private String mAlbum;
+        private String mTitle;
+        private String mComposer;
+        private String mGenre;
+        private String mMimeType;
+        private int mFileType;
+        private int mTrack;
+        private int mYear;
+        private int mDuration;
+        private String mPath;
+        private long mLastModified;
+        private long mFileSize;
+    
+        public FileCacheEntry beginFile(String path, String mimeType, long lastModified, long fileSize) {
+            
+            // special case certain file names
+            // I use regionMatches() instead of substring() below 
+            // to avoid memory allocation
+            int lastSlash = path.lastIndexOf('/');
+            if (lastSlash >= 0 && lastSlash + 2 < path.length()) {
+                // ignore those ._* files created by MacOS
+                if (path.regionMatches(lastSlash + 1, "._", 0, 2)) {
+                    return null;
+                }
+                
+                // ignore album art files created by Windows Media Player:
+                // Folder.jpg, AlbumArtSmall.jpg, AlbumArt_{...}_Large.jpg and AlbumArt_{...}_Small.jpg
+                if (path.regionMatches(true, path.length() - 4, ".jpg", 0, 4)) {
+                    if (path.regionMatches(true, lastSlash + 1, "AlbumArt_{", 0, 10) ||
+                            path.regionMatches(true, lastSlash + 1, "AlbumArt.", 0, 9)) {
+                        return null;
+                    }
+                    int length = path.length() - lastSlash - 1;
+                    if ((length == 17 && path.regionMatches(true, lastSlash + 1, "AlbumArtSmall", 0, 13)) ||
+                            (length == 10 && path.regionMatches(true, lastSlash + 1, "Folder", 0, 6))) {
+                        return null;
+                    }
+                }
+            }
+            
+            mMimeType = null;
+            // try mimeType first, if it is specified
+            if (mimeType != null) {
+                mFileType = MediaFile.getFileTypeForMimeType(mimeType);
+                if (mFileType != 0) {
+                    mMimeType = mimeType;
+                }
+            }
+            mFileSize = fileSize;
+
+            // if mimeType was not specified, compute file type based on file extension.
+            if (mMimeType == null) {
+                MediaFile.MediaFileType mediaFileType = MediaFile.getFileType(path);
+                if (mediaFileType != null) {
+                    mFileType = mediaFileType.fileType;
+                    mMimeType = mediaFileType.mimeType;
+                }
+            }
+            
+            String key = path;
+            if (mCaseInsensitivePaths) {
+                key = path.toLowerCase();
+            }
+            FileCacheEntry entry = mFileCache.get(key);
+            if (entry == null) {
+                entry = new FileCacheEntry(null, 0, path, 0);
+                mFileCache.put(key, entry);
+            }
+            entry.mSeenInFileSystem = true;
+            
+            // add some slack to avoid a rounding error
+            long delta = lastModified - entry.mLastModified;
+            if (delta > 1 || delta < -1) {
+                entry.mLastModified = lastModified;
+                entry.mLastModifiedChanged = true;
+            }
+                           
+            if (mProcessPlaylists && MediaFile.isPlayListFileType(mFileType)) {
+                mPlayLists.add(entry);
+                // we don't process playlists in the main scan, so return null
+                return null;
+            }
+            
+            // clear all the metadata
+            mArtist = null;
+            mAlbumArtist = null;
+            mAlbum = null;
+            mTitle = null;
+            mComposer = null;
+            mGenre = null;
+            mTrack = 0;
+            mYear = 0;
+            mDuration = 0;
+            mPath = path;
+            mLastModified = lastModified;
+            
+            return entry;
+        }
+        
+        public void scanFile(String path, long lastModified, long fileSize) {
+            doScanFile(path, null, lastModified, fileSize, false);
+        }
+
+        public void scanFile(String path, String mimeType, long lastModified, long fileSize) {
+            doScanFile(path, mimeType, lastModified, fileSize, false);
+        }
+
+        public Uri doScanFile(String path, String mimeType, long lastModified, long fileSize, boolean scanAlways) {
+            Uri result = null;
+//            long t1 = System.currentTimeMillis();
+            try {
+                FileCacheEntry entry = beginFile(path, mimeType, lastModified, fileSize);
+                // rescan for metadata if file was modified since last scan
+                if (entry != null && (entry.mLastModifiedChanged || scanAlways)) {
+                    boolean ringtones = (path.indexOf(RINGTONES_DIR) > 0);
+                    boolean notifications = (path.indexOf(NOTIFICATIONS_DIR) > 0);
+                    boolean alarms = (path.indexOf(ALARMS_DIR) > 0);
+                    boolean podcasts = (path.indexOf(PODCAST_DIR) > 0);
+                    boolean music = (path.indexOf(MUSIC_DIR) > 0) ||
+                        (!ringtones && !notifications && !alarms && !podcasts);
+
+                    if (mFileType == MediaFile.FILE_TYPE_MP3 ||
+                            mFileType == MediaFile.FILE_TYPE_MP4 ||
+                            mFileType == MediaFile.FILE_TYPE_M4A ||
+                            mFileType == MediaFile.FILE_TYPE_3GPP ||
+                            mFileType == MediaFile.FILE_TYPE_3GPP2 ||
+                            mFileType == MediaFile.FILE_TYPE_OGG ||
+                            mFileType == MediaFile.FILE_TYPE_MID ||
+                            mFileType == MediaFile.FILE_TYPE_WMA) {
+                        // we only extract metadata from MP3, M4A, OGG, MID and WMA files.
+                        // check MP4 files, to determine if they contain only audio.
+                        processFile(path, mimeType, this);
+                    } else if (MediaFile.isImageFileType(mFileType)) {
+                        // we used to compute the width and height but it's not worth it
+                    }
+                    
+                    result = endFile(entry, ringtones, notifications, alarms, music, podcasts);
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "RemoteException in MediaScanner.scanFile()", e);
+            }
+//            long t2 = System.currentTimeMillis();
+//            Log.v(TAG, "scanFile: " + path + " took " + (t2-t1));
+            return result;
+        }
+
+        private int parseSubstring(String s, int start, int defaultValue) {
+            int length = s.length();
+            if (start == length) return defaultValue;
+
+            char ch = s.charAt(start++);
+            // return defaultValue if we have no integer at all
+            if (ch < '0' || ch > '9') return defaultValue;
+            
+            int result = ch - '0';
+            while (start < length) {
+                ch = s.charAt(start++);
+                if (ch < '0' || ch > '9') return result;
+                result = result * 10 + (ch - '0');
+            }
+            
+            return result;
+        }                                
+                                
+        public void handleStringTag(String name, String value) {
+            if (name.equalsIgnoreCase("title") || name.startsWith("title;")) {
+                mTitle = value.trim();
+            } else if (name.equalsIgnoreCase("artist") || name.startsWith("artist;")) {
+                mArtist = value.trim();
+            } else if (name.equalsIgnoreCase("albumartist") || name.startsWith("albumartist;")) {
+                mAlbumArtist = value.trim();
+            } else if (name.equalsIgnoreCase("album") || name.startsWith("album;")) {
+                mAlbum = value.trim();
+            } else if (name.equalsIgnoreCase("composer") || name.startsWith("composer;")) {
+                mComposer = value.trim();
+            } else if (name.equalsIgnoreCase("genre") || name.startsWith("genre;")) {
+                // handle numeric genres, which PV sometimes encodes like "(20)"
+                if (value.length() > 0) {
+                    int genreCode = -1;
+                    char ch = value.charAt(0);
+                    if (ch == '(') {
+                        genreCode = parseSubstring(value, 1, -1);
+                    } else if (ch >= '0' && ch <= '9') {
+                        genreCode = parseSubstring(value, 0, -1);
+                    }
+                    if (genreCode >= 0 && genreCode < ID3_GENRES.length) {
+                        value = ID3_GENRES[genreCode];
+                    }
+                }
+                mGenre = value;
+            } else if (name.equalsIgnoreCase("year") || name.startsWith("year;")) {
+                mYear = parseSubstring(value, 0, 0);
+            } else if (name.equalsIgnoreCase("tracknumber") || name.startsWith("tracknumber;")) {
+                // track number might be of the form "2/12"
+                // we just read the number before the slash
+                int num = parseSubstring(value, 0, 0);
+                mTrack = (mTrack / 1000) * 1000 + num; 
+            } else if (name.equalsIgnoreCase("discnumber") ||
+                    name.equals("set") || name.startsWith("set;")) {
+                // set number might be of the form "1/3"
+                // we just read the number before the slash
+                int num = parseSubstring(value, 0, 0);
+                mTrack = (num * 1000) + (mTrack % 1000);
+            } else if (name.equalsIgnoreCase("duration")) {
+                mDuration = parseSubstring(value, 0, 0);
+            }
+        }
+        
+        public void setMimeType(String mimeType) {
+            mMimeType = mimeType;
+            mFileType = MediaFile.getFileTypeForMimeType(mimeType);
+        }
+        
+        /**
+         * Formats the data into a values array suitable for use with the Media
+         * Content Provider.
+         * 
+         * @return a map of values
+         */
+        private ContentValues toValues() {
+            ContentValues map = new ContentValues();
+
+            map.put(MediaStore.MediaColumns.DATA, mPath);
+            map.put(MediaStore.MediaColumns.TITLE, mTitle);
+            map.put(MediaStore.MediaColumns.DATE_MODIFIED, mLastModified);
+            map.put(MediaStore.MediaColumns.SIZE, mFileSize);
+            map.put(MediaStore.MediaColumns.MIME_TYPE, mMimeType);
+            
+            if (MediaFile.isVideoFileType(mFileType)) {
+                map.put(Video.Media.ARTIST, (mArtist != null && mArtist.length() > 0 ? mArtist : MediaFile.UNKNOWN_STRING));
+                map.put(Video.Media.ALBUM, (mAlbum != null && mAlbum.length() > 0 ? mAlbum : MediaFile.UNKNOWN_STRING));
+                map.put(Video.Media.DURATION, mDuration);
+                // FIXME - add RESOLUTION
+            } else if (MediaFile.isImageFileType(mFileType)) {
+                // FIXME - add DESCRIPTION
+                // map.put(field, value);
+            } else if (MediaFile.isAudioFileType(mFileType)) {
+                map.put(Audio.Media.ARTIST, (mArtist != null && mArtist.length() > 0 ? mArtist : MediaFile.UNKNOWN_STRING));
+                map.put(Audio.Media.ALBUM, (mAlbum != null && mAlbum.length() > 0 ? mAlbum : MediaFile.UNKNOWN_STRING));
+                map.put(Audio.Media.COMPOSER, mComposer);
+                if (mYear != 0) {
+                    map.put(Audio.Media.YEAR, mYear);
+                }
+                map.put(Audio.Media.TRACK, mTrack);
+                map.put(Audio.Media.DURATION, mDuration);
+            }
+            return map;
+        }
+    
+        private Uri endFile(FileCacheEntry entry, boolean ringtones, boolean notifications,
+                boolean alarms, boolean music, boolean podcasts) 
+                throws RemoteException {
+            // update database
+            Uri tableUri;
+            boolean isAudio = MediaFile.isAudioFileType(mFileType);
+            boolean isVideo = MediaFile.isVideoFileType(mFileType);
+            boolean isImage = MediaFile.isImageFileType(mFileType);
+            if (isVideo) {
+                tableUri = mVideoUri;
+            } else if (isImage) {
+                tableUri = mImagesUri;
+            } else if (isAudio) {
+                tableUri = mAudioUri;
+            } else {
+                // don't add file to database if not audio, video or image
+                return null;
+            }
+            entry.mTableUri = tableUri;
+            
+             // use album artist if artist is missing
+            if (mArtist == null || mArtist.length() == 0) {
+                mArtist = mAlbumArtist;
+            }
+
+            ContentValues values = toValues();
+            String title = values.getAsString(MediaStore.MediaColumns.TITLE);
+            if (TextUtils.isEmpty(title)) {
+                title = values.getAsString(MediaStore.MediaColumns.DATA);
+                // extract file name after last slash
+                int lastSlash = title.lastIndexOf('/');
+                if (lastSlash >= 0) {
+                    lastSlash++;
+                    if (lastSlash < title.length()) {
+                        title = title.substring(lastSlash);
+                    }
+                }
+                // truncate the file extension (if any)
+                int lastDot = title.lastIndexOf('.');
+                if (lastDot > 0) {
+                    title = title.substring(0, lastDot);
+                }
+                values.put(MediaStore.MediaColumns.TITLE, title);
+            }
+            if (isAudio) {
+                values.put(Audio.Media.IS_RINGTONE, ringtones);
+                values.put(Audio.Media.IS_NOTIFICATION, notifications);
+                values.put(Audio.Media.IS_ALARM, alarms);
+                values.put(Audio.Media.IS_MUSIC, music);
+                values.put(Audio.Media.IS_PODCAST, podcasts);
+            } else if (isImage) {
+                // nothing right now
+            }
+            
+            Uri result = null;
+            long rowId = entry.mRowId;
+            if (rowId == 0) {
+                // new file, insert it
+                result = mMediaProvider.insert(tableUri, values);
+                if (result != null) {
+                    rowId = ContentUris.parseId(result);
+                    entry.mRowId = rowId;
+                }
+            } else {
+                // updated file
+                result = ContentUris.withAppendedId(tableUri, rowId);
+                mMediaProvider.update(result, values, null, null);
+            }
+            if (mProcessGenres && mGenre != null) {
+                String genre = mGenre;
+                Uri uri = mGenreCache.get(genre);
+                if (uri == null) {
+                    Cursor cursor = null;
+                    try {
+                        // see if the genre already exists
+                        cursor = mMediaProvider.query(
+                                mGenresUri,
+                                GENRE_LOOKUP_PROJECTION, MediaStore.Audio.Genres.NAME + "=?",
+                                        new String[] { genre }, null);
+                        if (cursor == null || cursor.getCount() == 0) {
+                            // genre does not exist, so create the genre in the genre table
+                            values.clear();
+                            values.put(MediaStore.Audio.Genres.NAME, genre);
+                            uri = mMediaProvider.insert(mGenresUri, values);
+                        } else {
+                            // genre already exists, so compute its Uri
+                            cursor.moveToNext();
+                            uri = ContentUris.withAppendedId(mGenresUri, cursor.getLong(0));
+                        }
+                        if (uri != null) {
+                            uri = Uri.withAppendedPath(uri, Genres.Members.CONTENT_DIRECTORY);
+                            mGenreCache.put(genre, uri);
+                        }
+                    } finally {
+                        // release the cursor if it exists
+                        if (cursor != null) {
+                            cursor.close();
+                        }
+                    }
+                }
+              
+                if (uri != null) {
+                    // add entry to audio_genre_map  
+                    values.clear();
+                    values.put(MediaStore.Audio.Genres.Members.AUDIO_ID, Long.valueOf(rowId));
+                    mMediaProvider.insert(uri, values);
+                }
+            }
+            
+            if (notifications && !mDefaultNotificationSet) {
+                if (TextUtils.isEmpty(mDefaultNotificationFilename) ||
+                        doesPathHaveFilename(entry.mPath, mDefaultNotificationFilename)) {
+                    setSettingIfNotSet(Settings.System.NOTIFICATION_SOUND, tableUri, rowId);
+                    mDefaultNotificationSet = true;
+                }
+            } else if (ringtones && !mDefaultRingtoneSet) {
+                if (TextUtils.isEmpty(mDefaultRingtoneFilename) ||
+                        doesPathHaveFilename(entry.mPath, mDefaultRingtoneFilename)) {
+                    setSettingIfNotSet(Settings.System.RINGTONE, tableUri, rowId);
+                    mDefaultRingtoneSet = true;
+                }
+            }
+            
+            return result;
+        }
+        
+        private boolean doesPathHaveFilename(String path, String filename) {
+            int pathFilenameStart = path.lastIndexOf(File.separatorChar) + 1;
+            int filenameLength = filename.length();
+            return path.regionMatches(pathFilenameStart, filename, 0, filenameLength) &&
+                    pathFilenameStart + filenameLength == path.length();
+        }
+        
+        private void setSettingIfNotSet(String settingName, Uri uri, long rowId) {
+            
+            String existingSettingValue = Settings.System.getString(mContext.getContentResolver(),
+                    settingName);
+            
+            if (TextUtils.isEmpty(existingSettingValue)) {
+                // Set the setting to the given URI
+                Settings.System.putString(mContext.getContentResolver(), settingName,
+                        ContentUris.withAppendedId(uri, rowId).toString());
+            }
+        }
+        
+    }; // end of anonymous MediaScannerClient instance
+    
+    private void prescan(String filePath) throws RemoteException {
+        Cursor c = null;
+        String where = null;
+        String[] selectionArgs = null;
+         
+        if (mFileCache == null) {
+            mFileCache = new HashMap<String, FileCacheEntry>();
+        } else {
+            mFileCache.clear();
+        }
+        if (mPlayLists == null) {
+            mPlayLists = new ArrayList<FileCacheEntry>();
+        } else {
+            mPlayLists.clear();
+        }
+  
+        // Build the list of files from the content provider
+        try {
+            // Read existing files from the audio table
+            if (filePath != null) {
+                where = MediaStore.Audio.Media.DATA + "=?";
+                selectionArgs = new String[] { filePath };
+            }
+            c = mMediaProvider.query(mAudioUri, AUDIO_PROJECTION, where, selectionArgs, null);
+ 
+            if (c != null) {
+                try {
+                    while (c.moveToNext()) {
+                        long rowId = c.getLong(ID_AUDIO_COLUMN_INDEX);
+                        String path = c.getString(PATH_AUDIO_COLUMN_INDEX);
+                        long lastModified = c.getLong(DATE_MODIFIED_AUDIO_COLUMN_INDEX);
+                        
+                        String key = path;
+                        if (mCaseInsensitivePaths) {
+                            key = path.toLowerCase();
+                        }
+                        mFileCache.put(key, new FileCacheEntry(mAudioUri, rowId, path,
+                                lastModified));
+                    }
+                } finally {
+                    c.close();
+                    c = null;
+                }
+            }
+
+            // Read existing files from the video table
+            if (filePath != null) {
+                where = MediaStore.Video.Media.DATA + "=?";
+            } else {
+                where = null;
+            }
+            c = mMediaProvider.query(mVideoUri, VIDEO_PROJECTION, where, selectionArgs, null);
+ 
+            if (c != null) {
+                try {
+                    while (c.moveToNext()) {
+                        long rowId = c.getLong(ID_VIDEO_COLUMN_INDEX);
+                        String path = c.getString(PATH_VIDEO_COLUMN_INDEX);
+                        long lastModified = c.getLong(DATE_MODIFIED_VIDEO_COLUMN_INDEX);
+                        
+                        String key = path;
+                        if (mCaseInsensitivePaths) {
+                            key = path.toLowerCase();
+                        }
+                        mFileCache.put(key, new FileCacheEntry(mVideoUri, rowId, path,
+                                lastModified));
+                    }
+                } finally {
+                    c.close();
+                    c = null;
+                }
+            }
+
+            // Read existing files from the images table
+            if (filePath != null) {
+                where = MediaStore.Images.Media.DATA + "=?";
+            } else {
+                where = null;
+            }
+            mOriginalCount = 0;
+            c = mMediaProvider.query(mImagesUri, IMAGES_PROJECTION, where, selectionArgs, null);
+ 
+            if (c != null) {
+                try {
+                    mOriginalCount = c.getCount();
+                    while (c.moveToNext()) {
+                        long rowId = c.getLong(ID_IMAGES_COLUMN_INDEX);
+                        String path = c.getString(PATH_IMAGES_COLUMN_INDEX);
+                       long lastModified = c.getLong(DATE_MODIFIED_IMAGES_COLUMN_INDEX);
+    
+                        String key = path;
+                        if (mCaseInsensitivePaths) {
+                            key = path.toLowerCase();
+                        }
+                        mFileCache.put(key, new FileCacheEntry(mImagesUri, rowId, path,
+                                lastModified));
+                    }
+                } finally {
+                    c.close();
+                    c = null;
+                }
+            }
+            
+            if (mProcessPlaylists) {
+                // Read existing files from the playlists table
+                if (filePath != null) {
+                    where = MediaStore.Audio.Playlists.DATA + "=?";
+                } else {
+                    where = null;
+                }
+                c = mMediaProvider.query(mPlaylistsUri, PLAYLISTS_PROJECTION, where, selectionArgs, null);
+     
+                if (c != null) {
+                    try {
+                        while (c.moveToNext()) {
+                            String path = c.getString(PATH_IMAGES_COLUMN_INDEX);
+            
+                            if (path != null && path.length() > 0) {
+                                long rowId = c.getLong(ID_PLAYLISTS_COLUMN_INDEX);
+                                long lastModified = c.getLong(DATE_MODIFIED_PLAYLISTS_COLUMN_INDEX);
+    
+                                String key = path;
+                                if (mCaseInsensitivePaths) {
+                                    key = path.toLowerCase();
+                                }
+                                mFileCache.put(key, new FileCacheEntry(mPlaylistsUri, rowId, path,
+                                        lastModified));
+                            }
+                        }
+                    } finally {
+                        c.close();
+                        c = null;
+                    }
+                }
+            }
+        }
+        finally {
+            if (c != null) {
+                c.close();
+            }
+        }
+    }
+    
+    private boolean inScanDirectory(String path, String[] directories) {
+        for (int i = 0; i < directories.length; i++) {
+            if (path.startsWith(directories[i])) {
+                return true;
+            }
+        }
+        return false;
+    }
+    
+    private void pruneDeadThumbnailFiles() {
+        HashSet<String> existingFiles = new HashSet<String>();
+        String directory = "/sdcard/DCIM/.thumbnails";
+        String [] files = (new File(directory)).list();
+        if (files == null)
+            files = new String[0];
+        
+        for (int i = 0; i < files.length; i++) {
+            String fullPathString = directory + "/" + files[i];
+            existingFiles.add(fullPathString);
+        }
+        
+        try {
+            Cursor c = mMediaProvider.query(
+                    mThumbsUri, 
+                    new String [] { "_data" }, 
+                    null, 
+                    null, 
+                    null);
+            Log.v(TAG, "pruneDeadThumbnailFiles... " + c);
+            if (c != null && c.moveToFirst()) {
+                do {
+                    String fullPathString = c.getString(0);
+                    existingFiles.remove(fullPathString);
+                } while (c.moveToNext());
+            }
+            
+            for (String fileToDelete : existingFiles) {
+                if (Config.LOGV)
+                    Log.v(TAG, "fileToDelete is " + fileToDelete);
+                try {
+                    (new File(fileToDelete)).delete();
+                } catch (SecurityException ex) {
+                }
+            }
+            
+            Log.v(TAG, "/pruneDeadThumbnailFiles... " + c);
+            if (c != null) {
+                c.close();
+            }
+        } catch (RemoteException e) {
+            // We will soon be killed...
+        }
+    }
+
+    private void postscan(String[] directories) throws RemoteException {
+        Iterator<FileCacheEntry> iterator = mFileCache.values().iterator();
+
+        while (iterator.hasNext()) {
+            FileCacheEntry entry = iterator.next();
+            String path = entry.mPath;
+            
+            // remove database entries for files that no longer exist.
+            boolean fileMissing = false;
+            
+            if (!entry.mSeenInFileSystem) {
+                if (inScanDirectory(path, directories)) {
+                    // we didn't see this file in the scan directory.
+                    fileMissing = true;
+                } else {
+                    // the file is outside of our scan directory,
+                    // so we need to check for file existence here.
+                    File testFile = new File(path);
+                    if (!testFile.exists()) {
+                        fileMissing = true;
+                    }
+                }
+            }
+            
+            if (fileMissing) {
+                // do not delete missing playlists, since they may have been modified by the user.
+                // the user can delete them in the media player instead.
+                // instead, clear the path and lastModified fields in the row
+                MediaFile.MediaFileType mediaFileType = MediaFile.getFileType(path);
+                int fileType = (mediaFileType == null ? 0 : mediaFileType.fileType);
+
+                if (MediaFile.isPlayListFileType(fileType)) {
+                    ContentValues values = new ContentValues();
+                    values.put(MediaStore.Audio.Playlists.DATA, "");
+                    values.put(MediaStore.Audio.Playlists.DATE_MODIFIED, 0);
+                    mMediaProvider.update(ContentUris.withAppendedId(mPlaylistsUri, entry.mRowId), values, null, null);
+                } else {
+                    mMediaProvider.delete(ContentUris.withAppendedId(entry.mTableUri, entry.mRowId), null, null);
+                    iterator.remove();
+                }
+            }
+        }
+        
+        // handle playlists last, after we know what media files are on the storage.
+        if (mProcessPlaylists) {
+            processPlayLists();
+        }
+        
+        if (mOriginalCount == 0 && mImagesUri.equals(Images.Media.getContentUri("external")))
+            pruneDeadThumbnailFiles();
+        
+        // allow GC to clean up
+        mGenreCache = null;
+        mPlayLists = null;
+        mFileCache = null;
+        mMediaProvider = null;
+    }
+    
+    private void initialize(String volumeName) {
+        mMediaProvider = mContext.getContentResolver().acquireProvider("media");
+        
+        mAudioUri = Audio.Media.getContentUri(volumeName);
+        mVideoUri = Video.Media.getContentUri(volumeName);
+        mImagesUri = Images.Media.getContentUri(volumeName);
+        mThumbsUri = Images.Thumbnails.getContentUri(volumeName);
+
+        if (!volumeName.equals("internal")) {
+            // we only support playlists on external media
+            mProcessPlaylists = true;
+            mProcessGenres = true;
+            mGenreCache = new HashMap<String, Uri>();
+            mGenresUri = Genres.getContentUri(volumeName);
+            mPlaylistsUri = Playlists.getContentUri(volumeName);
+            // assuming external storage is FAT (case insensitive), except on the simulator.
+            if ( Process.supportsProcesses()) {
+                mCaseInsensitivePaths = true;
+            }
+        }          
+    }
+
+    public void scanDirectories(String[] directories, String volumeName) {
+        try {
+            long start = System.currentTimeMillis();
+            initialize(volumeName);    
+            prescan(null);
+            long prescan = System.currentTimeMillis();
+            
+            for (int i = 0; i < directories.length; i++) {
+                processDirectory(directories[i], MediaFile.sFileExtensions, mClient);
+            }
+            long scan = System.currentTimeMillis();
+            postscan(directories);
+            long end = System.currentTimeMillis();
+            
+            if (Config.LOGD) {
+                Log.d(TAG, " prescan time: " + (prescan - start) + "ms\n");
+                Log.d(TAG, "    scan time: " + (scan - prescan) + "ms\n");
+                Log.d(TAG, "postscan time: " + (end - scan) + "ms\n");
+                Log.d(TAG, "   total time: " + (end - start) + "ms\n");
+            }
+        } catch (SQLException e) {
+            // this might happen if the SD card is removed while the media scanner is running
+            Log.e(TAG, "SQLException in MediaScanner.scan()", e);
+        } catch (UnsupportedOperationException e) {
+            // this might happen if the SD card is removed while the media scanner is running
+            Log.e(TAG, "UnsupportedOperationException in MediaScanner.scan()", e);
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException in MediaScanner.scan()", e);
+        }
+    }
+
+    // this function is used to scan a single file
+    public Uri scanSingleFile(String path, String volumeName, String mimeType) {
+        try {
+            initialize(volumeName);        
+            prescan(path);
+    
+            File file = new File(path);
+            // always scan the file, so we can return the content://media Uri for existing files
+            return mClient.doScanFile(path, mimeType, file.lastModified(), file.length(), true);
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException in MediaScanner.scanFile()", e);
+            return null;
+        }
+    }
+
+    // returns the number of matching file/directory names, starting from the right
+    private int matchPaths(String path1, String path2) {
+        int result = 0;
+        int end1 = path1.length();
+        int end2 = path2.length();
+        
+        while (end1 > 0 && end2 > 0) {
+            int slash1 = path1.lastIndexOf('/', end1 - 1);
+            int slash2 = path2.lastIndexOf('/', end2 - 1);
+            int backSlash1 = path1.lastIndexOf('\\', end1 - 1);
+            int backSlash2 = path2.lastIndexOf('\\', end2 - 1);
+            int start1 = (slash1 > backSlash1 ? slash1 : backSlash1);
+            int start2 = (slash2 > backSlash2 ? slash2 : backSlash2);
+            if (start1 < 0) start1 = 0; else start1++;
+            if (start2 < 0) start2 = 0; else start2++;
+            int length = end1 - start1;
+            if (end2 - start2 != length) break;
+            if (path1.regionMatches(true, start1, path2, start2, length)) {
+                result++;
+                end1 = start1 - 1;
+                end2 = start2 - 1;
+            } else break;
+        }
+               
+        return result;
+    }
+
+    private boolean addPlayListEntry(String entry, String playListDirectory, 
+            Uri uri, ContentValues values, int index) {
+        
+        // watch for trailing whitespace
+        int entryLength = entry.length();
+        while (entryLength > 0 && Character.isWhitespace(entry.charAt(entryLength - 1))) entryLength--;
+        // path should be longer than 3 characters.
+        // avoid index out of bounds errors below by returning here.
+        if (entryLength < 3) return false;
+        if (entryLength < entry.length()) entry = entry.substring(0, entryLength);
+
+        // does entry appear to be an absolute path?
+        // look for Unix or DOS absolute paths
+        char ch1 = entry.charAt(0);
+        boolean fullPath = (ch1 == '/' ||
+                (Character.isLetter(ch1) && entry.charAt(1) == ':' && entry.charAt(2) == '\\'));
+        // if we have a relative path, combine entry with playListDirectory
+        if (!fullPath)
+            entry = playListDirectory + entry;
+            
+        //FIXME - should we look for "../" within the path?
+        
+        // best matching MediaFile for the play list entry
+        FileCacheEntry bestMatch = null;
+        
+        // number of rightmost file/directory names for bestMatch
+        int bestMatchLength = 0;    
+                
+        Iterator<FileCacheEntry> iterator = mFileCache.values().iterator();
+        while (iterator.hasNext()) {
+            FileCacheEntry cacheEntry = iterator.next();
+            String path = cacheEntry.mPath;
+        
+            if (path.equalsIgnoreCase(entry)) {
+                bestMatch = cacheEntry;
+                break;    // don't bother continuing search
+            }
+            
+            int matchLength = matchPaths(path, entry);
+            if (matchLength > bestMatchLength) {
+                bestMatch = cacheEntry;
+                bestMatchLength = matchLength;
+            }
+        }
+        
+        if (bestMatch == null) {
+            return false;
+        }
+        
+        try {
+        // OK, now we need to add this to the database
+            values.clear();
+            values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, Integer.valueOf(index));
+            values.put(MediaStore.Audio.Playlists.Members.AUDIO_ID, Long.valueOf(bestMatch.mRowId));
+            mMediaProvider.insert(uri, values);
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException in MediaScanner.addPlayListEntry()", e);
+            return false;
+        }
+
+        return true;
+    }
+    
+    private void processM3uPlayList(String path, String playListDirectory, Uri uri, ContentValues values) {
+        BufferedReader reader = null;
+        try {
+            File f = new File(path);
+            if (f.exists()) {
+                reader = new BufferedReader(
+                        new InputStreamReader(new FileInputStream(f)), 8192);
+                String line = reader.readLine();
+                int index = 0;
+                while (line != null) {
+                    // ignore comment lines, which begin with '#'
+                    if (line.length() > 0 && line.charAt(0) != '#') {
+                        values.clear();
+                        if (addPlayListEntry(line, playListDirectory, uri, values, index))
+                            index++;
+                    }
+                    line = reader.readLine();
+                }
+            }
+        } catch (IOException e) {
+            Log.e(TAG, "IOException in MediaScanner.processM3uPlayList()", e);
+        } finally {
+            try {
+                if (reader != null)
+                    reader.close();
+            } catch (IOException e) {
+                Log.e(TAG, "IOException in MediaScanner.processM3uPlayList()", e);
+            }
+        }
+    }
+
+    private void processPlsPlayList(String path, String playListDirectory, Uri uri, ContentValues values) {
+        BufferedReader reader = null;
+        try {
+            File f = new File(path);
+            if (f.exists()) {
+                reader = new BufferedReader(
+                        new InputStreamReader(new FileInputStream(f)), 8192);
+                String line = reader.readLine();
+                int index = 0;
+                while (line != null) {
+                    // ignore comment lines, which begin with '#'
+                    if (line.startsWith("File")) {
+                        int equals = line.indexOf('=');
+                        if (equals > 0) {
+                            values.clear();
+                            if (addPlayListEntry(line.substring(equals + 1), playListDirectory, uri, values, index))
+                                index++;
+                        }
+                    }
+                    line = reader.readLine();
+                }
+            }
+        } catch (IOException e) {
+            Log.e(TAG, "IOException in MediaScanner.processPlsPlayList()", e);
+        } finally {
+            try {
+                if (reader != null)
+                    reader.close();
+            } catch (IOException e) {
+                Log.e(TAG, "IOException in MediaScanner.processPlsPlayList()", e);
+            }
+        }
+    }
+
+    class WplHandler implements ElementListener {
+
+        final ContentHandler handler;
+        String playListDirectory;
+        Uri uri;
+        ContentValues values = new ContentValues();
+        int index = 0;
+
+        public WplHandler(String playListDirectory, Uri uri) {
+            this.playListDirectory = playListDirectory;
+            this.uri = uri;
+            
+            RootElement root = new RootElement("smil");
+            Element body = root.getChild("body");
+            Element seq = body.getChild("seq");
+            Element media = seq.getChild("media");
+            media.setElementListener(this);
+
+            this.handler = root.getContentHandler();
+        }
+
+        public void start(Attributes attributes) {
+            String path = attributes.getValue("", "src");
+            if (path != null) {
+                values.clear();
+                if (addPlayListEntry(path, playListDirectory, uri, values, index)) {
+                    index++;
+                }
+            }
+        }
+
+       public void end() {
+       }
+
+        ContentHandler getContentHandler() {
+            return handler;
+        }
+    }
+
+    private void processWplPlayList(String path, String playListDirectory, Uri uri) {
+        FileInputStream fis = null;
+        try {
+            File f = new File(path);
+            if (f.exists()) {
+                fis = new FileInputStream(f);
+
+                Xml.parse(fis, Xml.findEncodingByName("UTF-8"), new WplHandler(playListDirectory, uri).getContentHandler());
+            }
+        } catch (SAXException e) {
+            e.printStackTrace();
+        } catch (IOException e) {
+            e.printStackTrace();
+        } finally {
+            try {
+                if (fis != null)
+                    fis.close();
+            } catch (IOException e) {
+                Log.e(TAG, "IOException in MediaScanner.processWplPlayList()", e);
+            }
+        }
+    }
+    
+    private void processPlayLists() throws RemoteException {
+        Iterator<FileCacheEntry> iterator = mPlayLists.iterator();
+        while (iterator.hasNext()) {
+            FileCacheEntry entry = iterator.next();
+            String path = entry.mPath;  
+
+            // only process playlist files if they are new or have been modified since the last scan
+            if (entry.mLastModifiedChanged) {
+                ContentValues values = new ContentValues();
+                int lastSlash = path.lastIndexOf('/');
+                if (lastSlash < 0) throw new IllegalArgumentException("bad path " + path);
+                Uri uri, membersUri;
+                long rowId = entry.mRowId;
+                if (rowId == 0) {
+                    // Create a new playlist
+        
+                    int lastDot = path.lastIndexOf('.');
+                    String name = (lastDot < 0 ? path.substring(lastSlash + 1) : path.substring(lastSlash + 1, lastDot));
+                    values.put(MediaStore.Audio.Playlists.NAME, name);
+                    values.put(MediaStore.Audio.Playlists.DATA, path);
+                    values.put(MediaStore.Audio.Playlists.DATE_MODIFIED, entry.mLastModified);
+                    uri = mMediaProvider.insert(mPlaylistsUri, values);
+                    rowId = ContentUris.parseId(uri);
+                    membersUri = Uri.withAppendedPath(uri, Playlists.Members.CONTENT_DIRECTORY);
+                } else {
+                    uri = ContentUris.withAppendedId(mPlaylistsUri, rowId);
+                    
+                    // update lastModified value of existing playlist
+                    values.put(MediaStore.Audio.Playlists.DATE_MODIFIED, entry.mLastModified);
+                    mMediaProvider.update(uri, values, null, null);
+
+                    // delete members of existing playlist
+                    membersUri = Uri.withAppendedPath(uri, Playlists.Members.CONTENT_DIRECTORY);
+                    mMediaProvider.delete(membersUri, null, null);
+                }
+               
+                String playListDirectory = path.substring(0, lastSlash + 1);
+                MediaFile.MediaFileType mediaFileType = MediaFile.getFileType(path);
+                int fileType = (mediaFileType == null ? 0 : mediaFileType.fileType);
+
+                if (fileType == MediaFile.FILE_TYPE_M3U)
+                    processM3uPlayList(path, playListDirectory, membersUri, values);
+                else if (fileType == MediaFile.FILE_TYPE_PLS)
+                    processPlsPlayList(path, playListDirectory, membersUri, values);
+                else if (fileType == MediaFile.FILE_TYPE_WPL)
+                    processWplPlayList(path, playListDirectory, membersUri);
+                    
+                Cursor cursor = mMediaProvider.query(membersUri, PLAYLIST_MEMBERS_PROJECTION, null,
+                        null, null);
+                try {
+                    if (cursor == null || cursor.getCount() == 0) {
+                        Log.d(TAG, "playlist is empty - deleting");
+                        mMediaProvider.delete(uri, null, null);
+                    }
+                } finally {
+                    if (cursor != null) cursor.close();
+                }
+            }
+        }
+    }
+    
+    private native void processDirectory(String path, String extensions, MediaScannerClient client);
+    private native void processFile(String path, String mimeType, MediaScannerClient client);
+    public native void setLocale(String locale);
+    
+    public native byte[] extractAlbumArt(FileDescriptor fd);
+
+    private native final void native_setup();
+    private native final void native_finalize();
+    @Override
+    protected void finalize() { 
+        mContext.getContentResolver().releaseProvider(mMediaProvider);
+        native_finalize(); 
+    }
+}
diff --git a/media/java/android/media/MediaScannerClient.java b/media/java/android/media/MediaScannerClient.java
new file mode 100644
index 0000000..cf1a8da
--- /dev/null
+++ b/media/java/android/media/MediaScannerClient.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2007 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.media;
+
+/**
+ * {@hide}
+ */
+public interface MediaScannerClient
+{    
+    public void scanFile(String path, long lastModified, long fileSize);
+    
+    public void scanFile(String path, String mimeType, long lastModified, long fileSize);
+
+    /**
+     * Called by native code to return metadata extracted from media files.
+     */
+    public void handleStringTag(String name, String value);
+    
+    /**
+     * Called by native code to return mime type extracted from DRM content.
+     */
+    public void setMimeType(String mimeType);
+}
diff --git a/media/java/android/media/MediaScannerConnection.java b/media/java/android/media/MediaScannerConnection.java
new file mode 100644
index 0000000..d2596b8
--- /dev/null
+++ b/media/java/android/media/MediaScannerConnection.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2008 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.media;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.media.IMediaScannerListener;
+import android.media.IMediaScannerService;
+import android.net.Uri;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Config;
+import android.util.Log;
+
+
+/**
+ * MediaScannerConnection provides a way for applications to pass a 
+ * newly created or downloaded media file to the media scanner service.
+ * The media scanner service will read metadata from the file and add 
+ * the file to the media content provider.
+ * The MediaScannerConnectionClient provides an interface for the 
+ * media scanner service to return the Uri for a newly scanned file
+ * to the client of the MediaScannerConnection class.
+ */
+public class MediaScannerConnection implements ServiceConnection {
+
+    private static final String TAG = "MediaScannerConnection";
+
+    private Context mContext;
+    private MediaScannerConnectionClient mClient;
+    private IMediaScannerService mService;
+    private boolean mConnected; // true if connect() has been called since last disconnect()
+    
+    private IMediaScannerListener.Stub mListener = new IMediaScannerListener.Stub() {
+        public void scanCompleted(String path, Uri uri) {
+            MediaScannerConnectionClient client = mClient;
+            if (client != null) {
+                client.onScanCompleted(path, uri);
+            }
+        }
+    };
+
+    /**
+     * An interface for notifying clients of MediaScannerConnection
+     * when a connection to the MediaScanner service has been established
+     * and when the scanning of a file has completed.
+     */
+    public interface MediaScannerConnectionClient {
+        /**
+         * Called to notify the client when a connection to the 
+         * MediaScanner service has been established.
+         */    
+        public void onMediaScannerConnected();
+
+        /**
+         * Called to notify the client when the media scanner has finished
+         * scanning a file.
+         * @param path the path to the file that has been scanned.
+         * @param uri the Uri for the file if the scanning operation succeeded 
+         * and the file was added to the media database, or null if scanning failed. 
+         */    
+        public void onScanCompleted(String path, Uri uri);
+    }
+
+    /**
+     * Constructs a new MediaScannerConnection object.
+     * @param context the Context object, required for establishing a connection to
+     * the media scanner service.
+     * @param client an optional object implementing the MediaScannerConnectionClient
+     * interface, for receiving notifications from the media scanner.
+     */
+    public MediaScannerConnection(Context context, MediaScannerConnectionClient client) {
+        mContext = context;
+        mClient = client;
+    }
+
+    /**
+     * Initiates a connection to the media scanner service.
+     * {@link MediaScannerConnectionClient#onMediaScannerConnected()}
+     * will be called when the connection is established.
+     */
+    public void connect() {
+        synchronized (this) {
+            if (!mConnected) {
+                Intent intent = new Intent(IMediaScannerService.class.getName());
+                mContext.bindService(intent, this, Context.BIND_AUTO_CREATE);
+                mConnected = true;
+            }
+        }
+    }
+
+    /**
+     * Releases the connection to the media scanner service.
+     */
+    public void disconnect() {
+        synchronized (this) {
+            if (mConnected) {
+                if (Config.LOGV) {
+                    Log.v(TAG, "Disconnecting from Media Scanner");
+                }
+                try {
+                    mContext.unbindService(this);
+                } catch (IllegalArgumentException ex) {
+                    if (Config.LOGV) {
+                        Log.v(TAG, "disconnect failed: " + ex);
+                    }
+                }
+                mConnected = false;
+            }
+        }
+    }
+    
+    /**
+     * Returns whether we are connected to the media scanner service
+     * @return true if we are connected, false otherwise
+     */
+    public synchronized boolean isConnected() {
+        return (mService != null && mConnected);
+    }
+
+    /**
+     * Requests the media scanner to scan a file.
+     * @param path the path to the file to be scanned.
+     * @param mimeType  an optional mimeType for the file.
+     * If mimeType is null, then the mimeType will be inferred from the file extension.
+     * Success or failure of the scanning operation cannot be determined until 
+     * {@link MediaScannerConnectionClient#onScanCompleted(String, Uri)} is called.
+     */
+     public void scanFile(String path, String mimeType) {
+        synchronized (this) {
+            if (mService == null || !mConnected) {
+                throw new IllegalStateException("not connected to MediaScannerService");
+            }
+            try {
+                if (Config.LOGV) {
+                    Log.v(TAG, "Scanning file " + path);
+                }
+                mService.requestScanFile(path, mimeType, mListener);
+            } catch (RemoteException e) {
+                if (Config.LOGD) {
+                    Log.d(TAG, "Failed to scan file " + path);
+                }
+            }
+        }
+    }
+
+    /**
+     * Part of the ServiceConnection interface.  Do not call.
+     */
+    public void onServiceConnected(ComponentName className, IBinder service) {
+        if (Config.LOGV) {
+            Log.v(TAG, "Connected to Media Scanner");
+        }
+        synchronized (this) {
+            mService = IMediaScannerService.Stub.asInterface(service);
+            if (mService != null && mClient != null) {
+                mClient.onMediaScannerConnected();
+            }
+        }
+    }
+
+    /**
+     * Part of the ServiceConnection interface.  Do not call.
+     */
+    public void onServiceDisconnected(ComponentName className) {
+        if (Config.LOGV) {
+            Log.v(TAG, "Disconnected from Media Scanner");
+        }
+        synchronized (this) {
+            mService = null;
+        }
+    }
+}
diff --git a/media/java/android/media/ResampleInputStream.java b/media/java/android/media/ResampleInputStream.java
new file mode 100644
index 0000000..e26eae5
--- /dev/null
+++ b/media/java/android/media/ResampleInputStream.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2008 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.media;
+
+import android.util.Config;
+import android.util.Log;
+
+import java.io.InputStream;
+import java.io.IOException;
+
+
+/**
+ * ResampleInputStream
+ * @hide
+ */
+public final class ResampleInputStream extends InputStream
+{    
+    static {
+        System.loadLibrary("media_jni");
+    }
+    
+    private final static String TAG = "ResampleInputStream";
+    
+    // pcm input stream
+    private InputStream mInputStream;
+
+    // sample rates, assumed to be normalized
+    private final int mRateIn;
+    private final int mRateOut;
+    
+    // input pcm data
+    private byte[] mBuf;
+    private int mBufCount;
+    
+    // length of 2:1 fir
+    private static final int mFirLength = 29;
+    
+    // helper for bytewise read()
+    private final byte[] mOneByte = new byte[1];
+    
+    /**
+     * Create a new ResampleInputStream, which converts the sample rate
+     * @param inputStream InputStream containing 16 bit PCM.
+     * @param rateIn the input sample rate.
+     * @param rateOut the output sample rate.
+     * This only handles rateIn == rateOut / 2 for the moment.
+     */
+    public ResampleInputStream(InputStream inputStream, int rateIn, int rateOut) {
+        // only support 2:1 at the moment
+        if (rateIn != 2 * rateOut) throw new IllegalArgumentException("only support 2:1 at the moment");
+        rateIn = 2;
+        rateOut = 1;
+
+        mInputStream = inputStream;
+        mRateIn = rateIn;
+        mRateOut = rateOut;
+    }
+
+    @Override
+    public int read() throws IOException {
+        int rtn = read(mOneByte, 0, 1);
+        return rtn == 1 ? (0xff & mOneByte[0]) : -1;
+    }
+    
+    @Override
+    public int read(byte[] b) throws IOException {
+        return read(b, 0, b.length);
+    }
+
+    @Override
+    public int read(byte[] b, int offset, int length) throws IOException {
+        if (mInputStream == null) throw new IllegalStateException("not open");
+
+        // ensure that mBuf is big enough to cover requested 'length'
+        int nIn = ((length / 2) * mRateIn / mRateOut + mFirLength) * 2;
+        if (mBuf == null) {
+            mBuf = new byte[nIn];
+        } else if (nIn > mBuf.length) {
+            byte[] bf = new byte[nIn];
+            System.arraycopy(mBuf, 0, bf, 0, mBufCount);
+            mBuf = bf;
+        }
+        
+        // read until we have enough data for at least one output sample
+        while (true) {
+            int len = ((mBufCount / 2 - mFirLength) * mRateOut / mRateIn) * 2;
+            if (len > 0) {
+                length = len < length ? len : (length / 2) * 2;
+                break;
+            }
+            // TODO: should mBuf.length below be nIn instead?
+            int n = mInputStream.read(mBuf, mBufCount, mBuf.length - mBufCount);
+            if (n == -1) return -1;
+            mBufCount += n;
+        }
+
+        // resample input data
+        fir21(mBuf, 0, b, offset, length / 2);
+        
+        // move any unused bytes to front of mBuf
+        int nFwd = length * mRateIn / mRateOut;
+        mBufCount -= nFwd;
+        if (mBufCount > 0) System.arraycopy(mBuf, nFwd, mBuf, 0, mBufCount);
+        
+        return length;
+    }
+
+/*
+    @Override
+    public int available() throws IOException {
+        int nsamples = (mIn - mOut + mInputStream.available()) / 2;
+        return ((nsamples - mFirLength) * mRateOut / mRateIn) * 2;
+    }
+*/
+
+    @Override
+    public void close() throws IOException {
+        try {
+            if (mInputStream != null) mInputStream.close();
+        } finally {
+            mInputStream = null;
+        }
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        if (mInputStream != null) {
+            close();
+            throw new IllegalStateException("someone forgot to close ResampleInputStream");
+        }
+    }
+
+    //
+    // fir filter code JNI interface
+    //
+    private static native void fir21(byte[] in, int inOffset,
+            byte[] out, int outOffset, int npoints);
+
+}
diff --git a/media/java/android/media/Ringtone.java b/media/java/android/media/Ringtone.java
new file mode 100644
index 0000000..e80d8aa
--- /dev/null
+++ b/media/java/android/media/Ringtone.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2006 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.media;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.media.AudioManager;
+import android.media.MediaPlayer;
+import android.net.Uri;
+import android.provider.DrmStore;
+import android.provider.MediaStore;
+import android.provider.Settings;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+
+/**
+ * Ringtone provides a quick method for playing a ringtone, notification, or
+ * other similar types of sounds.
+ * <p>
+ * For ways of retrieving {@link Ringtone} objects or to show a ringtone
+ * picker, see {@link RingtoneManager}.
+ * 
+ * @see RingtoneManager
+ */
+public class Ringtone {
+    private static String TAG = "Ringtone";
+
+    private static final String[] MEDIA_COLUMNS = new String[] {
+        MediaStore.Audio.Media._ID,
+        MediaStore.Audio.Media.DATA,
+        MediaStore.Audio.Media.TITLE
+    };
+
+    private static final String[] DRM_COLUMNS = new String[] {
+        DrmStore.Audio._ID,
+        DrmStore.Audio.DATA,
+        DrmStore.Audio.TITLE
+    };
+
+    private MediaPlayer mAudio;
+
+    private Uri mUri;
+    private String mTitle;
+    private FileDescriptor mFileDescriptor;
+    private AssetFileDescriptor mAssetFileDescriptor;
+
+    private int mStreamType = AudioManager.STREAM_RING;
+
+    private Context mContext;
+
+    Ringtone(Context context) {
+        mContext = context;
+    }
+
+    /**
+     * Sets the stream type where this ringtone will be played.
+     * 
+     * @param streamType The stream, see {@link AudioManager}.
+     */
+    public void setStreamType(int streamType) {
+        mStreamType = streamType;
+        
+        if (mAudio != null) {
+            /*
+             * The stream type has to be set before the media player is
+             * prepared. Re-initialize it.
+             */
+            try {
+                openMediaPlayer();
+            } catch (IOException e) {
+                Log.w(TAG, "Couldn't set the stream type", e);
+            }
+        }
+    }
+
+    /**
+     * Gets the stream type where this ringtone will be played.
+     * 
+     * @return The stream type, see {@link AudioManager}.
+     */
+    public int getStreamType() {
+        return mStreamType;
+    }
+
+    /**
+     * Returns a human-presentable title for ringtone. Looks in media and DRM
+     * content providers. If not in either, uses the filename
+     * 
+     * @param context A context used for querying. 
+     */
+    public String getTitle(Context context) {
+        if (mTitle != null) return mTitle;
+        return mTitle = getTitle(context, mUri, true);
+    }
+
+    private static String getTitle(Context context, Uri uri, boolean followSettingsUri) {
+        Cursor cursor = null;
+        ContentResolver res = context.getContentResolver();
+        
+        String title = null;
+
+        if (uri != null) {
+            String authority = uri.getAuthority();
+
+            if (Settings.AUTHORITY.equals(authority)) {
+                if (followSettingsUri) {
+                    Uri actualUri = RingtoneManager.getActualDefaultRingtoneUri(context,
+                            RingtoneManager.getDefaultType(uri));
+                    String actualTitle = getTitle(context, actualUri, false);
+                    title = context
+                            .getString(com.android.internal.R.string.ringtone_default_with_actual,
+                                    actualTitle);
+                }
+            } else {
+                
+                if (DrmStore.AUTHORITY.equals(authority)) {
+                    cursor = res.query(uri, DRM_COLUMNS, null, null, null);
+                } else if (MediaStore.AUTHORITY.equals(authority)) {
+                    cursor = res.query(uri, MEDIA_COLUMNS, null, null, null);
+                }
+                
+                if (cursor != null && cursor.getCount() == 1) {
+                    cursor.moveToFirst();
+                    return cursor.getString(2);
+                } else {
+                    title = uri.getLastPathSegment();
+                }
+            }
+        }
+
+        if (title == null) {
+            title = context.getString(com.android.internal.R.string.ringtone_unknown);
+            
+            if (title == null) {
+                title = "";
+            }
+        }
+        
+        return title;
+    }
+    
+    private void openMediaPlayer() throws IOException {
+        mAudio = new MediaPlayer();
+        if (mUri != null) {
+            mAudio.setDataSource(mContext, mUri);
+        } else if (mFileDescriptor != null) {
+            mAudio.setDataSource(mFileDescriptor);
+        } else if (mAssetFileDescriptor != null) {
+            // Note: using getDeclaredLength so that our behavior is the same
+            // as previous versions when the content provider is returning
+            // a full file.
+            if (mAssetFileDescriptor.getDeclaredLength() < 0) {
+                mAudio.setDataSource(mAssetFileDescriptor.getFileDescriptor());
+            } else {
+                mAudio.setDataSource(mAssetFileDescriptor.getFileDescriptor(),
+                        mAssetFileDescriptor.getStartOffset(),
+                        mAssetFileDescriptor.getDeclaredLength());
+            }
+        } else {
+            throw new IOException("No data source set.");
+        }
+        mAudio.setAudioStreamType(mStreamType);
+        mAudio.prepare();
+    }
+
+    void open(FileDescriptor fd) throws IOException {
+        mFileDescriptor = fd;
+        openMediaPlayer();
+    }
+
+    void open(AssetFileDescriptor fd) throws IOException {
+        mAssetFileDescriptor = fd;
+        openMediaPlayer();
+    }
+
+    void open(Uri uri) throws IOException {
+        mUri = uri;
+        openMediaPlayer();
+    }
+    
+    /**
+     * Plays the ringtone.
+     */
+    public void play() {
+        if (mAudio == null) {
+            try {
+                openMediaPlayer();
+            } catch (Exception ex) {
+                Log.e(TAG, "play() caught ", ex);
+                mAudio = null;
+            }
+        }
+        if (mAudio != null) {
+            mAudio.start();
+        }
+    }
+
+    /**
+     * Stops a playing ringtone.
+     */
+    public void stop() {
+        if (mAudio != null) {
+            mAudio.reset();
+            mAudio.release();
+            mAudio = null;
+        }
+    }
+
+    /**
+     * Whether this ringtone is currently playing.
+     * 
+     * @return True if playing, false otherwise.
+     */
+    public boolean isPlaying() {
+        return mAudio != null && mAudio.isPlaying();
+    }
+
+    void setTitle(String title) {
+        mTitle = title;
+    }
+}
diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java
new file mode 100644
index 0000000..2f0007f
--- /dev/null
+++ b/media/java/android/media/RingtoneManager.java
@@ -0,0 +1,713 @@
+/*
+ * Copyright (C) 2007 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.media;
+
+import com.android.internal.database.SortCursor;
+
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.app.Activity;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Environment;
+import android.provider.DrmStore;
+import android.provider.MediaStore;
+import android.provider.Settings;
+import android.provider.Settings.System;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * RingtoneManager provides access to ringtones, notification, and other types
+ * of sounds. It manages querying the different media providers and combines the
+ * results into a single cursor. It also provides a {@link Ringtone} for each
+ * ringtone. We generically call these sounds ringtones, however the
+ * {@link #TYPE_RINGTONE} refers to the type of sounds that are suitable for the
+ * phone ringer.
+ * <p>
+ * To show a ringtone picker to the user, use the
+ * {@link #ACTION_RINGTONE_PICKER} intent to launch the picker as a subactivity.
+ * 
+ * @see Ringtone
+ */
+public class RingtoneManager {
+
+    private static final String TAG = "RingtoneManager";
+
+    // Make sure these are in sync with attrs.xml:
+    // <attr name="ringtoneType">
+    
+    /**
+     * Type that refers to sounds that are used for the phone ringer.
+     */
+    public static final int TYPE_RINGTONE = 1;
+    
+    /**
+     * Type that refers to sounds that are used for notifications.
+     */
+    public static final int TYPE_NOTIFICATION = 2;
+    
+    /**
+     * Type that refers to sounds that are used for the alarm.
+     */
+    public static final int TYPE_ALARM = 4;
+    
+    /**
+     * All types of sounds.
+     */
+    public static final int TYPE_ALL = TYPE_RINGTONE | TYPE_NOTIFICATION | TYPE_ALARM;
+    
+    // </attr>
+    
+    /**
+     * Activity Action: Shows a ringtone picker.
+     * <p>
+     * Input: {@link #EXTRA_RINGTONE_EXISTING_URI},
+     * {@link #EXTRA_RINGTONE_SHOW_DEFAULT},
+     * {@link #EXTRA_RINGTONE_SHOW_SILENT}, {@link #EXTRA_RINGTONE_TYPE},
+     * {@link #EXTRA_RINGTONE_DEFAULT_URI}, {@link #EXTRA_RINGTONE_TITLE},
+     * {@link #EXTRA_RINGTONE_INCLUDE_DRM}.
+     * <p>
+     * Output: {@link #EXTRA_RINGTONE_PICKED_URI}.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_RINGTONE_PICKER = "android.intent.action.RINGTONE_PICKER";
+
+    /**
+     * Given to the ringtone picker as a boolean. Whether to show an item for
+     * "Default".
+     * 
+     * @see #ACTION_RINGTONE_PICKER
+     */
+    public static final String EXTRA_RINGTONE_SHOW_DEFAULT =
+            "android.intent.extra.ringtone.SHOW_DEFAULT";
+    
+    /**
+     * Given to the ringtone picker as a boolean. Whether to show an item for
+     * "Silent". If the "Silent" item is picked,
+     * {@link #EXTRA_RINGTONE_PICKED_URI} will be null.
+     * 
+     * @see #ACTION_RINGTONE_PICKER
+     */
+    public static final String EXTRA_RINGTONE_SHOW_SILENT =
+            "android.intent.extra.ringtone.SHOW_SILENT";
+
+    /**
+     * Given to the ringtone picker as a boolean. Whether to include DRM ringtones.
+     */
+    public static final String EXTRA_RINGTONE_INCLUDE_DRM =
+            "android.intent.extra.ringtone.INCLUDE_DRM";
+    
+    /**
+     * Given to the ringtone picker as a {@link Uri}. The {@link Uri} of the
+     * current ringtone, which will be used to show a checkmark next to the item
+     * for this {@link Uri}. If showing an item for "Default" (@see
+     * {@link #EXTRA_RINGTONE_SHOW_DEFAULT}), this can also be one of
+     * {@link System#DEFAULT_RINGTONE_URI} or
+     * {@link System#DEFAULT_NOTIFICATION_URI} to have the "Default" item
+     * checked.
+     * 
+     * @see #ACTION_RINGTONE_PICKER
+     */
+    public static final String EXTRA_RINGTONE_EXISTING_URI =
+            "android.intent.extra.ringtone.EXISTING_URI";
+    
+    /**
+     * Given to the ringtone picker as a {@link Uri}. The {@link Uri} of the
+     * ringtone to play when the user attempts to preview the "Default"
+     * ringtone. This can be one of {@link System#DEFAULT_RINGTONE_URI} or
+     * {@link System#DEFAULT_NOTIFICATION_URI} to have the "Default" point to
+     * the current sound for the given default sound type. If you are showing a
+     * ringtone picker for some other type of sound, you are free to provide any
+     * {@link Uri} here.
+     */
+    public static final String EXTRA_RINGTONE_DEFAULT_URI =
+            "android.intent.extra.ringtone.DEFAULT_URI";
+    
+    /**
+     * Given to the ringtone picker as an int. Specifies which ringtone type(s) should be
+     * shown in the picker. One or more of {@link #TYPE_RINGTONE},
+     * {@link #TYPE_NOTIFICATION}, {@link #TYPE_ALARM}, or {@link #TYPE_ALL}
+     * (bitwise-ored together).
+     */
+    public static final String EXTRA_RINGTONE_TYPE = "android.intent.extra.ringtone.TYPE";
+
+    /**
+     * Given to the ringtone picker as a {@link CharSequence}. The title to
+     * show for the ringtone picker. This has a default value that is suitable
+     * in most cases.
+     */
+    public static final String EXTRA_RINGTONE_TITLE = "android.intent.extra.ringtone.TITLE";
+    
+    /**
+     * Returned from the ringtone picker as a {@link Uri}.
+     * <p>
+     * It will be one of:
+     * <li> the picked ringtone,
+     * <li> a {@link Uri} that equals {@link System#DEFAULT_RINGTONE_URI} or
+     * {@link System#DEFAULT_NOTIFICATION_URI} if the default was chosen,
+     * <li> null if the "Silent" item was picked.
+     * 
+     * @see #ACTION_RINGTONE_PICKER
+     */
+    public static final String EXTRA_RINGTONE_PICKED_URI =
+            "android.intent.extra.ringtone.PICKED_URI";
+    
+    // Make sure the column ordering and then ..._COLUMN_INDEX are in sync
+    
+    private static final String[] INTERNAL_COLUMNS = new String[] {
+        MediaStore.Audio.Media._ID, MediaStore.Audio.Media.TITLE,
+        "\"" + MediaStore.Audio.Media.INTERNAL_CONTENT_URI + "\""
+    };
+
+    private static final String[] DRM_COLUMNS = new String[] {
+        DrmStore.Audio._ID, DrmStore.Audio.TITLE,
+        "\"" + DrmStore.Audio.CONTENT_URI + "\""
+    };
+
+    private static final String[] MEDIA_COLUMNS = new String[] {
+        MediaStore.Audio.Media._ID, MediaStore.Audio.Media.TITLE,
+        "\"" + MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "\""
+    };
+    
+    /**
+     * The column index (in the cursor returned by {@link #getCursor()} for the
+     * row ID.
+     */
+    public static final int ID_COLUMN_INDEX = 0;
+
+    /**
+     * The column index (in the cursor returned by {@link #getCursor()} for the
+     * title.
+     */
+    public static final int TITLE_COLUMN_INDEX = 1;
+
+    /**
+     * The column index (in the cursor returned by {@link #getCursor()} for the
+     * media provider's URI.
+     */
+    public static final int URI_COLUMN_INDEX = 2;
+
+    private Activity mActivity;
+    private Context mContext;
+    
+    private Cursor mCursor;
+
+    private int mType = TYPE_RINGTONE;
+    
+    /**
+     * If a column (item from this list) exists in the Cursor, its value must
+     * be true (value of 1) for the row to be returned.
+     */
+    private List<String> mFilterColumns = new ArrayList<String>();
+    
+    private boolean mStopPreviousRingtone = true;
+    private Ringtone mPreviousRingtone;
+
+    private boolean mIncludeDrm;
+    
+    /**
+     * Constructs a RingtoneManager. This constructor is recommended as its
+     * constructed instance manages cursor(s).
+     * 
+     * @param activity The activity used to get a managed cursor.
+     */
+    public RingtoneManager(Activity activity) {
+        mContext = mActivity = activity;
+        setType(mType);
+    }
+
+    /**
+     * Constructs a RingtoneManager. The instance constructed by this
+     * constructor will not manage the cursor(s), so the client should handle
+     * this itself.
+     * 
+     * @param context The context to used to get a cursor.
+     */
+    public RingtoneManager(Context context) {
+        mContext = context;
+        setType(mType);
+    }
+
+    /**
+     * Sets which type(s) of ringtones will be listed by this.
+     * 
+     * @param type The type(s), one or more of {@link #TYPE_RINGTONE},
+     *            {@link #TYPE_NOTIFICATION}, {@link #TYPE_ALARM},
+     *            {@link #TYPE_ALL}.
+     * @see #EXTRA_RINGTONE_TYPE           
+     */
+    public void setType(int type) {
+
+        if (mCursor != null) {
+            throw new IllegalStateException(
+                    "Setting filter columns should be done before querying for ringtones.");
+        }
+        
+        mType = type;
+        setFilterColumnsList(type);
+    }
+
+    /**
+     * Infers the playback stream type based on what type of ringtones this
+     * manager is returning.
+     * 
+     * @return The stream type.
+     * @hide Pending API Council approval
+     */
+    public int inferStreamType() {
+        switch (mType) {
+            
+            case TYPE_ALARM:
+                return AudioManager.STREAM_ALARM;
+                
+            case TYPE_NOTIFICATION:
+                return AudioManager.STREAM_NOTIFICATION;
+                
+            default:
+                return AudioManager.STREAM_RING;
+        }
+    }
+    
+    /**
+     * Whether retrieving another {@link Ringtone} will stop playing the
+     * previously retrieved {@link Ringtone}.
+     * <p>
+     * If this is false, make sure to {@link Ringtone#stop()} any previous
+     * ringtones to free resources.
+     * 
+     * @param stopPreviousRingtone If true, the previously retrieved
+     *            {@link Ringtone} will be stopped.
+     */
+    public void setStopPreviousRingtone(boolean stopPreviousRingtone) {
+        mStopPreviousRingtone = stopPreviousRingtone;
+    }
+
+    /**
+     * @see #setStopPreviousRingtone(boolean)
+     */
+    public boolean getStopPreviousRingtone() {
+        return mStopPreviousRingtone;
+    }
+
+    /**
+     * Stops playing the last {@link Ringtone} retrieved from this.
+     */
+    public void stopPreviousRingtone() {
+        if (mPreviousRingtone != null) {
+            mPreviousRingtone.stop();
+        }
+    }
+    
+    /**
+     * Returns whether DRM ringtones will be included.
+     * 
+     * @return Whether DRM ringtones will be included.
+     * @see #setIncludeDrm(boolean)
+     */
+    public boolean getIncludeDrm() {
+        return mIncludeDrm;
+    }
+
+    /**
+     * Sets whether to include DRM ringtones.
+     * 
+     * @param includeDrm Whether to include DRM ringtones.
+     */
+    public void setIncludeDrm(boolean includeDrm) {
+        mIncludeDrm = includeDrm;
+    }
+
+    /**
+     * Returns a {@link Cursor} of all the ringtones available. The returned
+     * cursor will be the same cursor returned each time this method is called,
+     * so do not {@link Cursor#close()} the cursor. The cursor can be
+     * {@link Cursor#deactivate()} safely.
+     * <p>
+     * If {@link RingtoneManager#RingtoneManager(Activity)} was not used, the
+     * caller should manage the returned cursor through its activity's life
+     * cycle to prevent leaking the cursor.
+     * 
+     * @return A {@link Cursor} of all the ringtones available.
+     * @see #ID_COLUMN_INDEX
+     * @see #TITLE_COLUMN_INDEX
+     * @see #URI_COLUMN_INDEX
+     */
+    public Cursor getCursor() {
+        if (mCursor != null && mCursor.requery()) {
+            return mCursor;
+        }
+        
+        final Cursor internalCursor = getInternalRingtones();
+        final Cursor drmCursor = mIncludeDrm ? getDrmRingtones() : null;
+        final Cursor mediaCursor = getMediaRingtones();
+             
+        return mCursor = new SortCursor(new Cursor[] { internalCursor, drmCursor, mediaCursor },
+                MediaStore.MediaColumns.TITLE);
+    }
+
+    /**
+     * Gets a {@link Ringtone} for the ringtone at the given position in the
+     * {@link Cursor}.
+     * 
+     * @param position The position (in the {@link Cursor}) of the ringtone.
+     * @return A {@link Ringtone} pointing to the ringtone.
+     */
+    public Ringtone getRingtone(int position) {
+        if (mStopPreviousRingtone && mPreviousRingtone != null) {
+            mPreviousRingtone.stop();
+        }
+        
+        mPreviousRingtone = getRingtone(mContext, getRingtoneUri(position), inferStreamType());
+        return mPreviousRingtone;
+    }
+
+    /**
+     * Gets a {@link Uri} for the ringtone at the given position in the {@link Cursor}.
+     * 
+     * @param position The position (in the {@link Cursor}) of the ringtone.
+     * @return A {@link Uri} pointing to the ringtone.
+     */
+    public Uri getRingtoneUri(int position) {
+        final Cursor cursor = getCursor();
+        
+        if (!cursor.moveToPosition(position)) {
+            return null;
+        }
+        
+        return getUriFromCursor(cursor);
+    }
+    
+    private static Uri getUriFromCursor(Cursor cursor) {
+        return ContentUris.withAppendedId(Uri.parse(cursor.getString(URI_COLUMN_INDEX)), cursor
+                .getLong(ID_COLUMN_INDEX));
+    }
+    
+    /**
+     * Gets the position of a {@link Uri} within this {@link RingtoneManager}.
+     * 
+     * @param ringtoneUri The {@link Uri} to retreive the position of.
+     * @return The position of the {@link Uri}, or -1 if it cannot be found.
+     */
+    public int getRingtonePosition(Uri ringtoneUri) {
+        
+        if (ringtoneUri == null) return -1;
+        
+        final Cursor cursor = getCursor();
+        final int cursorCount = cursor.getCount();
+        
+        if (!cursor.moveToFirst()) {
+            return -1;
+        }
+        
+        // Only create Uri objects when the actual URI changes
+        Uri currentUri = null;
+        String previousUriString = null;
+        for (int i = 0; i < cursorCount; i++) {
+            String uriString = cursor.getString(URI_COLUMN_INDEX);
+            if (currentUri == null || !uriString.equals(previousUriString)) {
+                currentUri = Uri.parse(uriString);
+            }
+            
+            if (ringtoneUri.equals(ContentUris.withAppendedId(currentUri, cursor
+                    .getLong(ID_COLUMN_INDEX)))) {
+                return i;
+            }
+            
+            cursor.move(1);
+            
+            previousUriString = uriString;
+        }
+        
+        return -1;
+    }
+
+    /**
+     * Returns a valid ringtone URI. No guarantees on which it returns. If it
+     * cannot find one, returns null.
+     * 
+     * @param context The context to use for querying.
+     * @return A ringtone URI, or null if one cannot be found.
+     */
+    public static Uri getValidRingtoneUri(Context context) {
+        final RingtoneManager rm = new RingtoneManager(context);
+        
+        Uri uri = getValidRingtoneUriFromCursorAndClose(context, rm.getInternalRingtones());
+
+        if (uri == null) {
+            uri = getValidRingtoneUriFromCursorAndClose(context, rm.getMediaRingtones());
+        }
+        
+        if (uri == null) {
+            uri = getValidRingtoneUriFromCursorAndClose(context, rm.getDrmRingtones());
+        }
+        
+        return uri;
+    }
+    
+    private static Uri getValidRingtoneUriFromCursorAndClose(Context context, Cursor cursor) {
+        if (cursor != null) {
+            Uri uri = null;
+            
+            if (cursor.moveToFirst()) {
+                uri = getUriFromCursor(cursor);
+            }
+            cursor.close();
+            
+            return uri;
+        } else {
+            return null;
+        }
+    }
+
+    private Cursor getInternalRingtones() {
+        return query(
+                MediaStore.Audio.Media.INTERNAL_CONTENT_URI, INTERNAL_COLUMNS,
+                constructBooleanTrueWhereClause(mFilterColumns),
+                null, MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
+    }
+    
+    private Cursor getDrmRingtones() {
+        // DRM store does not have any columns to use for filtering 
+        return query(
+                DrmStore.Audio.CONTENT_URI, DRM_COLUMNS,
+                null, null, DrmStore.Audio.TITLE);
+    }
+
+    private Cursor getMediaRingtones() {
+         // Get the external media cursor. First check to see if it is mounted.
+        final String status = Environment.getExternalStorageState();
+        
+        return (status.equals(Environment.MEDIA_MOUNTED) ||
+                    status.equals(Environment.MEDIA_MOUNTED_READ_ONLY))
+                ? query(
+                    MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, MEDIA_COLUMNS,
+                    constructBooleanTrueWhereClause(mFilterColumns), null,
+                    MediaStore.Audio.Media.DEFAULT_SORT_ORDER)
+                : null;
+    }
+    
+    private void setFilterColumnsList(int type) {
+        List<String> columns = mFilterColumns;
+        columns.clear();
+        
+        if ((type & TYPE_RINGTONE) != 0) {
+            columns.add(MediaStore.Audio.AudioColumns.IS_RINGTONE);
+        }
+        
+        if ((type & TYPE_NOTIFICATION) != 0) {
+            columns.add(MediaStore.Audio.AudioColumns.IS_NOTIFICATION);
+        }
+        
+        if ((type & TYPE_ALARM) != 0) {
+            columns.add(MediaStore.Audio.AudioColumns.IS_ALARM);
+        }
+    }
+    
+    /**
+     * Constructs a where clause that consists of at least one column being 1
+     * (true). This is used to find all matching sounds for the given sound
+     * types (ringtone, notifications, etc.)
+     * 
+     * @param columns The columns that must be true.
+     * @return The where clause.
+     */
+    private static String constructBooleanTrueWhereClause(List<String> columns) {
+        
+        if (columns == null) return null;
+        
+        StringBuilder sb = new StringBuilder();
+        for (int i = columns.size() - 1; i >= 0; i--) {
+            sb.append(columns.get(i)).append("=1 or ");
+        }
+        
+        if (columns.size() > 0) {
+            // Remove last ' or '
+            sb.setLength(sb.length() - 4);
+        }
+        
+        return sb.toString();
+    }
+    
+    private Cursor query(Uri uri,
+            String[] projection,
+            String selection,
+            String[] selectionArgs,
+            String sortOrder) {
+        if (mActivity != null) {
+            return mActivity.managedQuery(uri, projection, selection, selectionArgs, sortOrder);
+        } else {
+            return mContext.getContentResolver().query(uri, projection, selection, selectionArgs,
+                    sortOrder);
+        }
+    }
+    
+    /**
+     * Returns a {@link Ringtone} for a given sound URI.
+     * <p>
+     * If the given URI cannot be opened for any reason, this method will
+     * attempt to fallback on another sound. If it cannot find any, it will
+     * return null.
+     * 
+     * @param context A context used to query.
+     * @param ringtoneUri The {@link Uri} of a sound or ringtone.
+     * @return A {@link Ringtone} for the given URI, or null.
+     */
+    public static Ringtone getRingtone(final Context context, Uri ringtoneUri) {
+        // Don't set the stream type
+        return getRingtone(context, ringtoneUri, -1);
+    }
+
+    /**
+     * Returns a {@link Ringtone} for a given sound URI on the given stream
+     * type. Normally, if you change the stream type on the returned
+     * {@link Ringtone}, it will re-create the {@link MediaPlayer}. This is just
+     * an optimized route to avoid that.
+     * 
+     * @param streamType The stream type for the ringtone, or -1 if it should
+     *            not be set (and the default used instead).
+     * @see #getRingtone(Context, Uri)
+     */
+    private static Ringtone getRingtone(final Context context, Uri ringtoneUri, int streamType) {
+
+        try {
+            Ringtone r = new Ringtone(context);
+            if (streamType >= 0) {
+                r.setStreamType(streamType);
+            }
+            r.open(ringtoneUri);
+            return r;
+        } catch (Exception ex) {
+            Log.e(TAG, "Failed to open ringtone " + ringtoneUri);
+        }
+
+        // Ringtone doesn't exist, use the fallback ringtone.
+        try {
+            AssetFileDescriptor afd = context.getResources().openRawResourceFd(
+                    com.android.internal.R.raw.fallbackring);
+            if (afd != null) {
+                Ringtone r = new Ringtone(context);
+                r.open(afd);
+                afd.close();
+                return r;
+            }
+        } catch (Exception ex) {
+        }
+        
+        // we should never get here
+        Log.e(TAG, "unable to find a usable ringtone");
+        return null;
+    }
+    
+    /**
+     * Gets the current default sound's {@link Uri}. This will give the actual
+     * sound {@link Uri}, instead of using this, most clients can use
+     * {@link System#DEFAULT_RINGTONE_URI}.
+     * 
+     * @param context A context used for querying.
+     * @param type The type whose default sound should be returned. One of
+     *            {@link #TYPE_RINGTONE} or {@link #TYPE_NOTIFICATION}.
+     * @return A {@link Uri} pointing to the default sound for the sound type.
+     * @see #setActualDefaultRingtoneUri(Context, int, Uri)
+     */
+    public static Uri getActualDefaultRingtoneUri(Context context, int type) {
+        String setting = getSettingForType(type);
+        if (setting == null) return null;
+        final String uriString = Settings.System.getString(context.getContentResolver(), setting); 
+        return uriString != null ? Uri.parse(uriString) : getValidRingtoneUri(context);
+    }
+    
+    /**
+     * Sets the {@link Uri} of the default sound for a given sound type.
+     * 
+     * @param context A context used for querying.
+     * @param type The type whose default sound should be set. One of
+     *            {@link #TYPE_RINGTONE} or {@link #TYPE_NOTIFICATION}.
+     * @param ringtoneUri A {@link Uri} pointing to the default sound to set.
+     * @see #getActualDefaultRingtoneUri(Context, int)
+     */
+    public static void setActualDefaultRingtoneUri(Context context, int type, Uri ringtoneUri) {
+        String setting = getSettingForType(type);
+        if (setting == null) return;
+        Settings.System.putString(context.getContentResolver(), setting, ringtoneUri.toString());
+    }
+    
+    private static String getSettingForType(int type) {
+        if ((type & TYPE_RINGTONE) != 0) {
+            return Settings.System.RINGTONE;
+        } else if ((type & TYPE_NOTIFICATION) != 0) {
+            return Settings.System.NOTIFICATION_SOUND;
+        } else {
+            return null;
+        }
+    }
+    
+    /**
+     * Returns whether the given {@link Uri} is one of the default ringtones.
+     * 
+     * @param ringtoneUri The ringtone {@link Uri} to be checked.
+     * @return Whether the {@link Uri} is a default.
+     */
+    public static boolean isDefault(Uri ringtoneUri) {
+        return getDefaultType(ringtoneUri) != -1;
+    }
+    
+    /**
+     * Returns the type of a default {@link Uri}.
+     * 
+     * @param defaultRingtoneUri The default {@link Uri}. For example,
+     *            {@link System#DEFAULT_RINGTONE_URI} or
+     *            {@link System#DEFAULT_NOTIFICATION_URI}.
+     * @return The type of the defaultRingtoneUri, or -1.
+     */
+    public static int getDefaultType(Uri defaultRingtoneUri) {
+        if (defaultRingtoneUri == null) {
+            return -1;
+        } else if (defaultRingtoneUri.equals(Settings.System.DEFAULT_RINGTONE_URI)) {
+            return TYPE_RINGTONE;
+        } else if (defaultRingtoneUri.equals(Settings.System.DEFAULT_NOTIFICATION_URI)) {
+            return TYPE_NOTIFICATION;
+        } else {
+            return -1;
+        }
+    }
+ 
+    /**
+     * Returns the {@link Uri} for the default ringtone of a particular type.
+     * Rather than returning the actual ringtone's sound {@link Uri}, this will
+     * return the symbolic {@link Uri} which will resolved to the actual sound
+     * when played.
+     * 
+     * @param type The ringtone type whose default should be returned.
+     * @return The {@link Uri} of the default ringtone for the given type.
+     */
+    public static Uri getDefaultUri(int type) {
+        if ((type & TYPE_RINGTONE) != 0) {
+            return Settings.System.DEFAULT_RINGTONE_URI;
+        } else if ((type & TYPE_NOTIFICATION) != 0) {
+            return Settings.System.DEFAULT_NOTIFICATION_URI;
+        } else {
+            return null;
+        }
+    }
+    
+}
diff --git a/media/java/android/media/SoundPool.java b/media/java/android/media/SoundPool.java
new file mode 100644
index 0000000..000430f
--- /dev/null
+++ b/media/java/android/media/SoundPool.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2007 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.media;
+
+import android.util.AndroidRuntimeException;
+import android.util.Log;
+import java.io.File;
+import java.io.FileDescriptor;
+import android.os.ParcelFileDescriptor;
+import java.lang.ref.WeakReference;
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import java.io.IOException;
+
+/*
+ * The SoundPool class manages and plays audio resources for applications.
+ */
+public class SoundPool
+{
+    static { System.loadLibrary("soundpool"); }
+
+    private final static String TAG = "SoundPool";
+
+    private int mNativeContext; // accessed by native methods
+
+    public SoundPool(int maxStreams, int streamType, int srcQuality) {
+        native_setup(new WeakReference<SoundPool>(this), maxStreams, streamType, srcQuality);
+    }
+
+    public int load(String path, int priority)
+    {
+        // pass network streams to player
+        if (path.startsWith("http:"))
+            return _load(path, priority);
+
+        // try local path
+        int id = 0;
+        try {
+            File f = new File(path);
+            if (f != null) {
+                ParcelFileDescriptor fd = ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY);
+                if (fd != null) {
+                    id = _load(fd.getFileDescriptor(), 0, f.length(), priority);
+                    //Log.v(TAG, "close fd");
+                    fd.close();
+                }
+            }
+        } catch (java.io.IOException e) {}
+        return id;
+    }
+
+    public int load(Context context, int resId, int priority) {
+        AssetFileDescriptor afd = context.getResources().openRawResourceFd(resId);
+        int id = 0;
+        if (afd != null) {
+            id = _load(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength(), priority);
+            try {
+                //Log.v(TAG, "close fd");
+                afd.close();
+            } catch (java.io.IOException ex) {
+                //Log.d(TAG, "close failed:", ex);
+            }
+        }
+        return id;
+    }
+
+    public int load(AssetFileDescriptor afd, int priority) {
+        if (afd != null) {
+            long len = afd.getLength();
+            if (len < 0) {
+                throw new AndroidRuntimeException("no length for fd");
+            }
+            return _load(afd.getFileDescriptor(), afd.getStartOffset(), len, priority);
+        } else {
+            return 0;
+        }
+    }
+
+    public int load(FileDescriptor fd, long offset, long length, int priority) {
+        return _load(fd, offset, length, priority);
+    }
+
+    private native final int _load(String uri, int priority);
+
+    private native final int _load(FileDescriptor fd, long offset, long length, int priority);
+
+    public native final boolean unload(int soundID);
+
+    public native final int play(int soundID, float leftVolume, float rightVolume,
+            int priority, int loop, float rate);
+
+    public native final void pause(int streamID);
+
+    public native final void resume(int streamID);
+
+    public native final void stop(int streamID);
+
+    public native final void setVolume(int streamID,
+            float leftVolume, float rightVolume);
+
+    public native final void setPriority(int streamID, int priority);
+
+    public native final void setLoop(int streamID, int loop);
+
+    public native final void setRate(int streamID, float rate);
+
+    public native final void release();
+
+    private native final void native_setup(Object mediaplayer_this,
+            int maxStreams, int streamType, int srcQuality);
+
+    protected void finalize() { release(); }
+}
diff --git a/media/java/android/media/ToneGenerator.java b/media/java/android/media/ToneGenerator.java
new file mode 100644
index 0000000..0901fbf
--- /dev/null
+++ b/media/java/android/media/ToneGenerator.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2008 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.media;
+
+
+
+/**
+ * This class provides methods to play DTMF tones (ITU-T Recommendation Q.23), 
+ * call supervisory tones (3GPP TS 22.001, CEPT) and proprietary tones (3GPP TS 31.111). 
+ * Depending on call state and routing options, tones are mixed to the downlink audio
+ * or output to the speaker phone or headset. 
+ * This API is not for generating tones over the uplink audio path.   
+ */
+public class ToneGenerator
+{
+
+    /* Values for toneType parameter of ToneGenerator() constructor */
+    /*
+     * List of all available tones: These constants must be kept consistant with
+     * the enum in ToneGenerator C++ class.     */
+
+	/**
+     * DTMF tone for key 0: 1336Hz, 941Hz, continuous</p>
+     * 
+     * @see #ToneGenerator(int, int)
+     */
+    public static final int TONE_DTMF_0 = 0;
+    /**
+     * DTMF tone for key 1: 1209Hz, 697Hz, continuous
+     * 
+     * @see #ToneGenerator(int, int)
+     */
+    public static final int TONE_DTMF_1 = 1;
+    /**
+     * DTMF tone for key 2: 1336Hz, 697Hz, continuous
+     * 
+     * @see #ToneGenerator(int, int)
+     */
+   public static final int TONE_DTMF_2 = 2;
+   /**
+    * DTMF tone for key 3: 1477Hz, 697Hz, continuous
+    * 
+    * @see #ToneGenerator(int, int)
+    */
+    public static final int TONE_DTMF_3 = 3;
+    /**
+     * DTMF tone for key 4: 1209Hz, 770Hz, continuous
+     * 
+     * @see #ToneGenerator(int, int)
+     */
+    public static final int TONE_DTMF_4 = 4;
+    /**
+     * DTMF tone for key 5: 1336Hz, 770Hz, continuous
+     * 
+     * @see #ToneGenerator(int, int)
+     */
+    public static final int TONE_DTMF_5 = 5;
+    /**
+     * DTMF tone for key 6: 1477Hz, 770Hz, continuous
+     * 
+     * @see #ToneGenerator(int, int)
+     */
+    public static final int TONE_DTMF_6 = 6;
+    /**
+     * DTMF tone for key 7: 1209Hz, 852Hz, continuous
+     * 
+     * @see #ToneGenerator(int, int)
+     */
+    public static final int TONE_DTMF_7 = 7;
+    /**
+     * DTMF tone for key 8: 1336Hz, 852Hz, continuous
+     * 
+     * @see #ToneGenerator(int, int)
+     */
+    public static final int TONE_DTMF_8 = 8;
+    /**
+     * DTMF tone for key 9: 1477Hz, 852Hz, continuous
+     * 
+     * @see #ToneGenerator(int, int)
+     */
+    public static final int TONE_DTMF_9 = 9;
+    /**
+     * DTMF tone for key *: 1209Hz, 941Hz, continuous
+     * 
+     * @see #ToneGenerator(int, int)
+     */
+    public static final int TONE_DTMF_S = 10;
+    /**
+     * DTMF tone for key #: 1477Hz, 941Hz, continuous
+     * 
+     * @see #ToneGenerator(int, int)
+     */
+    public static final int TONE_DTMF_P = 11;
+    /**
+     * DTMF tone for key A: 1633Hz, 697Hz, continuous
+     * 
+     * @see #ToneGenerator(int, int)
+     */
+    public static final int TONE_DTMF_A = 12;
+    /**
+     * DTMF tone for key B: 1633Hz, 770Hz, continuous
+     * 
+     * @see #ToneGenerator(int, int)
+     */
+    public static final int TONE_DTMF_B = 13;
+    /**
+     * DTMF tone for key C: 1633Hz, 852Hz, continuous
+     * 
+     * @see #ToneGenerator(int, int)
+     */
+    public static final int TONE_DTMF_C = 14;
+    /**
+     * DTMF tone for key D: 1633Hz, 941Hz, continuous
+     * 
+     * @see #ToneGenerator(int, int)
+     */
+    public static final int TONE_DTMF_D = 15;
+    /**
+     * Call supervisory tone, Dial tone: 425Hz, continuous
+     * 
+     * @see #ToneGenerator(int, int)
+     */
+    public static final int TONE_SUP_DIAL = 16;
+    /**
+     * Call supervisory tone, Busy: 425Hz, 500ms ON, 500ms OFF...
+     * 
+     * @see #ToneGenerator(int, int)
+     */
+    public static final int TONE_SUP_BUSY = 17;
+    /**
+     * Call supervisory tone, Congestion: 425Hz, 200ms ON, 200ms OFF...
+     * 
+     * @see #ToneGenerator(int, int)
+     */
+    public static final int TONE_SUP_CONGESTION = 18;
+    /**
+     * Call supervisory tone, Radio path acknowlegment : 425Hz, 200ms ON
+     * 
+     * @see #ToneGenerator(int, int)
+     */
+    public static final int TONE_SUP_RADIO_ACK = 19;
+    /**
+     * Call supervisory tone, Radio path not available: 425Hz, 200ms ON, 200 OFF 3 bursts
+     * 
+     * @see #ToneGenerator(int, int)
+     */
+    public static final int TONE_SUP_RADIO_NOTAVAIL = 20;
+    /**
+     * Call supervisory tone, Error/Special info: 950Hz+1400Hz+1800Hz, 330ms ON, 1s OFF...
+     * 
+     * @see #ToneGenerator(int, int)
+     */
+    public static final int TONE_SUP_ERROR = 21;
+    /**
+     * Call supervisory tone, Call Waiting: 425Hz, 200ms ON, 600ms OFF, 200ms ON, 3s OFF...
+     * 
+     * @see #ToneGenerator(int, int)
+     */
+    public static final int TONE_SUP_CALL_WAITING = 22;
+    /**
+     * Call supervisory tone, Ring Tone: 425Hz, 1s ON, 4s OFF...
+     * 
+     * @see #ToneGenerator(int, int)
+     */
+    public static final int TONE_SUP_RINGTONE = 23;
+    /**
+     * Proprietary tone, general beep: 400Hz+1200Hz, 35ms ON
+     * 
+     * @see #ToneGenerator(int, int)
+     */
+    public static final int TONE_PROP_BEEP = 24;
+    /**
+     * Proprietary tone, positive acknowlegement: 1200Hz, 100ms ON, 100ms OFF 2 bursts
+     * 
+     * @see #ToneGenerator(int, int)
+     */
+    public static final int TONE_PROP_ACK = 25;
+    /**
+     * Proprietary tone, negative acknowlegement: 300Hz+400Hz+500Hz, 400ms ON
+     * 
+     * @see #ToneGenerator(int, int)
+     */
+    public static final int TONE_PROP_NACK = 26;
+    /**
+     * Proprietary tone, prompt tone: 400Hz+1200Hz, 200ms ON
+     * 
+     * @see #ToneGenerator(int, int)
+     */
+    public static final int  TONE_PROP_PROMPT = 27;
+    /**
+     * Proprietary tone, general double beep: twice 400Hz+1200Hz, 35ms ON, 200ms OFF, 35ms ON
+     * 
+     * @see #ToneGenerator(int, int)
+     */
+    public static final int TONE_PROP_BEEP2 = 28;
+
+    /** Maximum volume, for use with {@link #ToneGenerator(int,int)} */
+    public static final int MAX_VOLUME = AudioSystem.MAX_VOLUME;
+    /** Minimum volume setting, for use with {@link #ToneGenerator(int,int)} */
+    public static final int MIN_VOLUME = AudioSystem.MIN_VOLUME;
+
+
+    /**
+     * ToneGenerator class contructor specifying output stream type and volume.
+     * 
+     * @param streamType The streame type used for tone playback (e.g. STREAM_MUSIC).
+     * @param volume     The volume of the tone, given in percentage of maximum volume (from 0-100).
+     * 
+     */
+    public ToneGenerator(int streamType, int volume) {
+        native_setup(streamType, volume);
+    }
+
+    /**
+     * This method starts the playback of a tone of the specified type.
+     * only one tone can play at a time: if a tone is playing while this method is called, 
+     * this tone is stopped and replaced by the one requested.
+     * @param toneType   The type of tone generate chosen from the following list:
+     * <ul>
+     * <li>{@link #TONE_DTMF_0}
+     * <li>{@link #TONE_DTMF_1}
+     * <li>{@link #TONE_DTMF_2}
+     * <li>{@link #TONE_DTMF_3}
+     * <li>{@link #TONE_DTMF_4}
+     * <li>{@link #TONE_DTMF_5}
+     * <li>{@link #TONE_DTMF_6}
+     * <li>{@link #TONE_DTMF_7}
+     * <li>{@link #TONE_DTMF_8}
+     * <li>{@link #TONE_DTMF_9}
+     * <li>{@link #TONE_DTMF_A}
+     * <li>{@link #TONE_DTMF_B}
+     * <li>{@link #TONE_DTMF_C}
+     * <li>{@link #TONE_DTMF_D}
+     * <li>{@link #TONE_SUP_DIAL}
+     * <li>{@link #TONE_SUP_BUSY}
+     * <li>{@link #TONE_SUP_CONGESTION}
+     * <li>{@link #TONE_SUP_RADIO_ACK}
+     * <li>{@link #TONE_SUP_RADIO_NOTAVAIL}
+     * <li>{@link #TONE_SUP_ERROR}
+     * <li>{@link #TONE_SUP_CALL_WAITING}
+     * <li>{@link #TONE_SUP_RINGTONE}
+     * <li>{@link #TONE_PROP_BEEP}
+     * <li>{@link #TONE_PROP_ACK}
+     * <li>{@link #TONE_PROP_NACK}
+     * <li>{@link #TONE_PROP_PROMPT}
+     * <li>{@link #TONE_PROP_BEEP2}
+     * </ul>
+     * @see #ToneGenerator(int, int)
+    */
+    public native boolean startTone(int toneType);
+
+    /**
+     * This method stops the tone currently playing playback.
+     * @see #ToneGenerator(int, int)
+     */
+    public native void stopTone();
+
+    /**
+     * Releases resources associated with this ToneGenerator object. It is good
+     * practice to call this method when you're done using the ToneGenerator.
+     */
+    public native void release();
+
+    private native final void native_setup(int streamType, int volume);
+
+    private native final void native_finalize();
+    protected void finalize() { native_finalize(); }
+
+    private int mNativeContext; // accessed by native methods
+
+
+}
diff --git a/media/java/android/media/package.html b/media/java/android/media/package.html
new file mode 100644
index 0000000..3de7167
--- /dev/null
+++ b/media/java/android/media/package.html
@@ -0,0 +1,13 @@
+<HTML>
+<BODY>
+Provides classes that manage various media interfaces in audio and video.
+<p>The Media APIs are used to play and, in some cases, record media files. This includes
+audio (e.g., play MP3s or other music files, ringtones, game sound effects, or DTMF tones)
+and video (e.g., play a video streamed over the web or from local storage).
+</p>
+<p>Other special classes in the package offer the ability to detect the faces of people 
+in Bitmaps ({@link android.media.FaceDetector}), control audio routing (to the device or a headset)
+and control alerts such as ringtones and phone vibrations ({@link android.media.AudioManager}).
+</p>
+</BODY>
+</HTML>