summaryrefslogtreecommitdiffstats
path: root/media/java
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2009-03-03 19:31:44 -0800
committerThe Android Open Source Project <initial-contribution@android.com>2009-03-03 19:31:44 -0800
commit9066cfe9886ac131c34d59ed0e2d287b0e3c0087 (patch)
treed88beb88001f2482911e3d28e43833b50e4b4e97 /media/java
parentd83a98f4ce9cfa908f5c54bbd70f03eec07e7553 (diff)
downloadframeworks_base-9066cfe9886ac131c34d59ed0e2d287b0e3c0087.zip
frameworks_base-9066cfe9886ac131c34d59ed0e2d287b0e3c0087.tar.gz
frameworks_base-9066cfe9886ac131c34d59ed0e2d287b0e3c0087.tar.bz2
auto import from //depot/cupcake/@135843
Diffstat (limited to 'media/java')
-rw-r--r--media/java/android/drm/mobile1/DrmConstraintInfo.java96
-rw-r--r--media/java/android/drm/mobile1/DrmException.java34
-rw-r--r--media/java/android/drm/mobile1/DrmRawContent.java464
-rw-r--r--media/java/android/drm/mobile1/DrmRights.java136
-rw-r--r--media/java/android/drm/mobile1/DrmRightsManager.java255
-rw-r--r--media/java/android/drm/mobile1/package.html5
-rw-r--r--media/java/android/media/AmrInputStream.java141
-rw-r--r--media/java/android/media/AsyncPlayer.java235
-rw-r--r--media/java/android/media/AudioFormat.java51
-rw-r--r--media/java/android/media/AudioManager.java1086
-rw-r--r--media/java/android/media/AudioRecord.java798
-rw-r--r--media/java/android/media/AudioService.java1236
-rw-r--r--media/java/android/media/AudioSystem.java223
-rw-r--r--media/java/android/media/AudioTrack.java1050
-rw-r--r--media/java/android/media/FaceDetector.java201
-rw-r--r--media/java/android/media/IAudioService.aidl74
-rw-r--r--media/java/android/media/IMediaScannerListener.aidl33
-rw-r--r--media/java/android/media/IMediaScannerService.aidl44
-rw-r--r--media/java/android/media/JetPlayer.java423
-rw-r--r--media/java/android/media/MediaFile.java185
-rw-r--r--media/java/android/media/MediaMetadataRetriever.java256
-rw-r--r--media/java/android/media/MediaPlayer.java1211
-rw-r--r--media/java/android/media/MediaRecorder.java491
-rw-r--r--media/java/android/media/MediaScanner.java1352
-rw-r--r--media/java/android/media/MediaScannerClient.java37
-rw-r--r--media/java/android/media/MediaScannerConnection.java189
-rw-r--r--media/java/android/media/ResampleInputStream.java153
-rw-r--r--media/java/android/media/Ringtone.java239
-rw-r--r--media/java/android/media/RingtoneManager.java713
-rw-r--r--media/java/android/media/SoundPool.java127
-rw-r--r--media/java/android/media/ToneGenerator.java286
-rw-r--r--media/java/android/media/package.html13
32 files changed, 11837 insertions, 0 deletions
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..d40ca5a
--- /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>