summaryrefslogtreecommitdiffstats
path: root/media/java
diff options
context:
space:
mode:
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/AsyncPlayer.java234
-rw-r--r--media/java/android/media/AudioManager.java911
-rw-r--r--media/java/android/media/AudioService.java1104
-rw-r--r--media/java/android/media/AudioSystem.java209
-rw-r--r--media/java/android/media/FaceDetector.java201
-rw-r--r--media/java/android/media/IAudioService.aidl72
-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/MediaFile.java181
-rw-r--r--media/java/android/media/MediaMetadataRetriever.java136
-rw-r--r--media/java/android/media/MediaPlayer.java797
-rw-r--r--media/java/android/media/MediaRecorder.java315
-rw-r--r--media/java/android/media/MediaScanner.java1355
-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/Ringtone.java221
-rw-r--r--media/java/android/media/RingtoneManager.java680
-rw-r--r--media/java/android/media/SoundPool.java111
-rw-r--r--media/java/android/media/ToneGenerator.java286
-rw-r--r--media/java/android/media/package.html13
26 files changed, 8119 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/AsyncPlayer.java b/media/java/android/media/AsyncPlayer.java
new file mode 100644
index 0000000..0620f32
--- /dev/null
+++ b/media/java/android/media/AsyncPlayer.java
@@ -0,0 +1,234 @@
+/*
+ * 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;
+
+/**
+ * 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.setDataSource(cmd.context, cmd.uri);
+ player.setAudioStreamType(cmd.stream);
+ player.setLooping(cmd.looping);
+ player.prepare();
+ if (mPlayer != null) {
+ // stop the previous one.
+ mPlayer.stop();
+ mPlayer.release();
+ }
+ player.start();
+ mPlayer = player;
+ }
+ catch (IOException e) {
+ Log.w(mTag, "error loading sound for " + 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/AudioManager.java b/media/java/android/media/AudioManager.java
new file mode 100644
index 0000000..1c09c29
--- /dev/null
+++ b/media/java/android/media/AudioManager.java
@@ -0,0 +1,911 @@
+/*
+ * 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;
+
+ /**
+ * 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";
+
+ /**
+ * 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";
+
+ /** 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 and message alerts */
+ 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;
+ /** Number of audio streams */
+ 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
+ };
+
+ /** @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
+ };
+
+ /**
+ * 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 stream types that are affected by the ringer
+ * mode (for example, the ring stream type). 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){
+ setRouting(MODE_IN_CALL, on ? ROUTE_BLUETOOTH : 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) == 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;
+ /**
+ * Routing audio output to bluetooth
+ */
+ public static final int ROUTE_BLUETOOTH = AudioSystem.ROUTE_BLUETOOTH;
+ /**
+ * Routing audio output to headset
+ */
+ public static final int ROUTE_HEADSET = AudioSystem.ROUTE_HEADSET;
+ /**
+ * 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;
+ /**
+ * Focuse has moved up
+ * @see #playSoundEffect(int)
+ */
+ public static final int FX_FOCUS_NAVIGATION_UP = 1;
+ /**
+ * Focuse has moved down
+ * @see #playSoundEffect(int)
+ */
+ public static final int FX_FOCUS_NAVIGATION_DOWN = 2;
+ /**
+ * Focuse has moved left
+ * @see #playSoundEffect(int)
+ */
+ public static final int FX_FOCUS_NAVIGATION_LEFT = 3;
+ /**
+ * Focuse has moved right
+ * @see #playSoundEffect(int)
+ */
+ public static final int FX_FOCUS_NAVIGATION_RIGHT = 4;
+ /**
+ * @hide Number of sound effects
+ */
+ public static final int NUM_SOUND_EFFECTS = 5;
+
+ /**
+ * 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},
+ */
+ 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);
+ }
+ }
+
+ /**
+ * 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/AudioService.java b/media/java/android/media/AudioService.java
new file mode 100644
index 0000000..0ee903d
--- /dev/null
+++ b/media/java/android/media/AudioService.java
@@ -0,0 +1,1104 @@
+/*
+ * 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 com.android.internal.telephony.ITelephony;
+
+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.System;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.VolumePanel;
+
+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 float SOUND_EFFECT_VOLUME = 1.0f;
+
+ /* 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"
+ };
+
+ /* 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
+ };
+
+ 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);
+ if (Settings.System.getInt(mContentResolver, Settings.System.SOUND_EFFECTS_ENABLED, 0) == 1) {
+ 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]);
+
+ VolumeStreamState[] streams = mStreamStates = new VolumeStreamState[AudioSystem.NUM_STREAMS];
+
+ for (int i = 0; i < AudioSystem.NUM_STREAMS; i++) {
+ final int[] levels;
+
+ switch (i) {
+
+ case AudioSystem.STREAM_MUSIC:
+ levels = volumeLevelsFine;
+ break;
+
+ case AudioSystem.STREAM_VOICE_CALL:
+ levels = volumeLevelsPhone;
+ break;
+
+ default:
+ levels = volumeLevelsCoarse;
+ break;
+ }
+
+ 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);
+
+ 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
+ || isStreamAffectedByRingerMode(streamType)) {
+ // Check if the ringer mode changes with this volume adjustment. If
+ // it does, it will handle adjusting the volome, so we won't below
+ adjustVolume = checkForRingerModeChange(oldIndex, direction);
+ }
+
+ if (adjustVolume && streamState.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);
+ }
+ }
+
+ // UI
+ mVolumePanel.postVolumeChanged(streamType, flags);
+ }
+
+ /** @see AudioManager#setStreamVolume(int, int, int) */
+ public void setStreamVolume(int streamType, int index, int flags) {
+ ensureValidStreamType(streamType);
+ setStreamVolumeInt(streamType, index);
+ // UI, etc.
+ mVolumePanel.postVolumeChanged(streamType, flags);
+ }
+
+ /**
+ * 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.
+ */
+ private void setStreamVolumeInt(int streamType, int index) {
+ VolumeStreamState streamState = mStreamStates[streamType];
+ if (streamState.setIndex(index)) {
+ // 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
+ if (mRingerMode == AudioManager.RINGER_MODE_NORMAL) {
+ for (int streamType = AudioSystem.NUM_STREAMS - 1; streamType >= 0; streamType--) {
+ if (!isStreamAffectedByRingerMode(streamType)) continue;
+ // Bring back last audible volume
+ setStreamVolumeInt(streamType, mStreamStates[streamType].mLastAudibleIndex);
+ }
+ } else {
+ for (int streamType = AudioSystem.NUM_STREAMS - 1; streamType >= 0; streamType--) {
+ if (!isStreamAffectedByRingerMode(streamType)) continue;
+ // Either silent or vibrate, either way volume is 0
+ setStreamVolumeInt(streamType, 0);
+ }
+ }
+
+ // 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;
+ }
+ }
+ }
+
+ /** @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);
+ }
+ }
+ }
+
+ /** @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, 0,
+ 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"));
+ isOffhook = phone.isOffhook();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Couldn't connect to phone service", e);
+ }
+
+ // TODO: applications can influence this
+ 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
+ Intent broadcast = new Intent(AudioManager.RINGER_MODE_CHANGED_ACTION);
+ broadcast.putExtra(AudioManager.EXTRA_RINGER_MODE, mRingerMode);
+ mContext.sendStickyBroadcast(broadcast);
+ }
+
+ private void broadcastVibrateSetting(int vibrateType) {
+ // Send broadcast
+ 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>();
+ }
+
+ 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) {
+ synchronized (mSoundEffectsLock) {
+ if (mSoundPool == null) {
+ return;
+ }
+
+ if (SOUND_EFFECT_FILES_MAP[effectType][1] > 0) {
+ mSoundPool.play(SOUND_EFFECT_FILES_MAP[effectType][1], SOUND_EFFECT_VOLUME, SOUND_EFFECT_VOLUME,
+ 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();
+ for (int streamType = AudioSystem.NUM_STREAMS - 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);
+ break;
+ }
+ }
+ }
+
+}
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
new file mode 100644
index 0000000..70b9f18
--- /dev/null
+++ b/media/java/android/media/AudioSystem.java
@@ -0,0 +1,209 @@
+/*
+ * 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;
+ public static final int NUM_STREAMS = 5;
+
+ /* 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);
+ public static final int ROUTE_BLUETOOTH = (1 << 2);
+ public static final int ROUTE_HEADSET = (1 << 3);
+ public static final int ROUTE_ALL =
+ (ROUTE_EARPIECE | ROUTE_SPEAKER | ROUTE_BLUETOOTH | ROUTE_HEADSET);
+
+ /*
+ * 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/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..3e33d98
--- /dev/null
+++ b/media/java/android/media/IAudioService.aidl
@@ -0,0 +1,72 @@
+/*
+ * 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);
+
+ 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/MediaFile.java b/media/java/android/media/MediaFile.java
new file mode 100644
index 0000000..22361aa
--- /dev/null
+++ b/media/java/android/media/MediaFile.java
@@ -0,0 +1,181 @@
+/*
+ * 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("MID", 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("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..c0c6dad
--- /dev/null
+++ b/media/java/android/media/MediaMetadataRetriever.java
@@ -0,0 +1,136 @@
+/*
+ * 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.graphics.Bitmap;
+
+/**
+ * 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");
+ }
+
+ 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);
+
+ /**
+ * Call this method before the rest. 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;
+
+ /**
+ * 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;
+}
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
new file mode 100644
index 0000000..b6064e1
--- /dev/null
+++ b/media/java/android/media/MediaPlayer.java
@@ -0,0 +1,797 @@
+/*
+ * 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;
+
+/**
+ * Used to play audio and video files and streams.
+ * See the <a href="/android/toolbox/apis/media.html">Android Media APIs</a>
+ * page for help using using MediaPlayer.
+ */
+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. Call this after reset(), or before
+ * any other method (including setDataSource()) that might throw
+ * IllegalStateException in this class.
+ *
+ * @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 order other than the one specified above
+ */
+ 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;
+ }
+
+ ParcelFileDescriptor fd = null;
+ try {
+ ContentResolver resolver = context.getContentResolver();
+ fd = resolver.openFileDescriptor(uri, "r");
+ if (fd == null) {
+ return;
+ }
+ setDataSource(fd.getFileDescriptor());
+ 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. Call this after
+ * reset(), or before any other method (including setDataSource()) that might
+ * throw IllegalStateException in this class.
+ *
+ * @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 order other than the one specified above
+ */
+ 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.
+ * Call this after reset(), or before any other method (including setDataSource())
+ * that might throw IllegalStateException in this class.
+ *
+ * @param fd the FileDescriptor for the file you want to play
+ * @throws IllegalStateException if it is called
+ * in an order other than the one specified above
+ */
+ 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.
+ * Call this after reset(), or before any other method (including setDataSource())
+ * that might throw IllegalStateException in this class.
+ *
+ * @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 order other than the one specified above
+ */
+ public native void setDataSource(FileDescriptor fd, long offset, long length)
+ throws IOException, IllegalArgumentException, IllegalStateException;
+
+ /**
+ * Prepares the player for playback, synchronously. Call this after
+ * setDataSource() or stop(), and before any other method that might
+ * throw IllegalStateException in this class.
+ *
+ * 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 order other than the one specified above
+ */
+ public native void prepare() throws IOException, IllegalStateException;
+
+ /**
+ * Prepares the player for playback, asynchronously. Call this after
+ * setDataSource() or stop(), and before any other method that might
+ * throw IllegalStateException in this class.
+ *
+ * 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 order other than the one specified above
+ */
+ 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. Call this after receiving onCompletion or onPrepared
+ * event notification from OnCompletionListener or OnPreparedListener
+ * interface, or called after prepare() or pause().
+ *
+ * @throws IllegalStateException if it is called
+ * in an order other than the one specified above
+ */
+ public void start() throws IllegalStateException {
+ stayAwake(true);
+ _start();
+ }
+
+ private native void _start() throws IllegalStateException;
+
+ /**
+ * Stops playback after playback has been stopped or paused.
+ * Call this after start() or pause(), or after receiving the onPrepared
+ * event notification from OnPreparedListener interface.
+ *
+ * @throws IllegalStateException if it is called
+ * in an order other than the one specified above
+ */
+ public void stop() throws IllegalStateException {
+ stayAwake(false);
+ _stop();
+ }
+
+ private native void _stop() throws IllegalStateException;
+
+ /**
+ * Pauses playback. Call start() to resume. Call this after start()
+ * and before any other method that might throw IllegalStateException in this class.
+ *
+ * @throws IllegalStateException if it is called
+ * in an order other than the one specified above
+ */
+ 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. Call this after setDataSource() method.
+ *
+ * @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. Call this after setDataSource() method.
+ *
+ * @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. Call this after
+ * setDataSource() method.
+ *
+ * @return true if currently playing, false otherwise
+ */
+ public native boolean isPlaying();
+
+ /**
+ * Seeks to specified time position. Call this after start(), pause(), or
+ * prepare(), or after receiving onPrepared or onCompletion event notification
+ * from OnPreparedListener or OnCompletionListener interface.
+ *
+ * @param msec the offset in milliseconds from the start to seek to
+ * @throws IllegalStateException if it is called
+ * in an order other than the one specified above
+ */
+ public native void seekTo(int msec) throws IllegalStateException;
+
+ /**
+ * Gets the current playback position. Call this after setDataSource() method.
+ *
+ * @return the current position in milliseconds
+ */
+ public native int getCurrentPosition();
+
+ /**
+ * Gets the duration of the file. Call this after setDataSource() method.
+ *
+ * @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() {
+ if (mWakeLock != null) mWakeLock.release();
+ updateSurfaceScreenOn();
+ mOnPreparedListener = null;
+ mOnBufferingUpdateListener = null;
+ mOnCompletionListener = null;
+ mOnSeekCompleteListener = null;
+ mOnErrorListener = 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() {
+ _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. Call this
+ * after setDataSource method.
+ *
+ * @param looping whether to loop or not
+ */
+ public native void setLooping(boolean looping);
+
+ /**
+ * Sets the volume on this player. Call after setDataSource method.
+ * 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
+ * AudioManager::setStreamVolume API 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);
+
+ /**
+ * Returns a Bitmap containing the video frame at the specified time. Call
+ * this after setDataSource() or stop().
+ *
+ * @param msec the time at which to capture the video frame, in milliseconds
+ * @return a Bitmap containing the video frame at the specified time
+ * @throws IllegalStateException if it is called
+ * in an order other than the one specified above
+ * @hide
+ */
+ public native Bitmap getFrameAt(int msec) throws IllegalStateException;
+
+ private native final void native_setup(Object mediaplayer_this);
+ private native final void native_finalize();
+ 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_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_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
+ * file 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 file 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 file has completed.
+ */
+ public interface OnCompletionListener
+ {
+ /**
+ * Called when the end of a media file 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 file
+ * 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;
+
+ /* Do not change these values without updating their counterparts
+ * in include/media/mediaplayer.h!
+ */
+ /** Unspecified media player error. @see #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 #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..870d7f3
--- /dev/null
+++ b/media/java/android/media/MediaRecorder.java
@@ -0,0 +1,315 @@
+/*
+ * 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.view.Surface;
+
+/**
+ * Used to record audio and video. The recording control is based on a
+ * simple state machine (see below).
+ *
+ * <p><img src="../../../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="../../../toolbox/apis/media.html">Android Media APIs</a>
+ * page for additional help with using MediaRecorder.
+ */
+public class MediaRecorder
+{
+ static {
+ System.loadLibrary("media_jni");
+ }
+
+ // The two fields below are accessed by native methods
+ @SuppressWarnings("unused")
+ private int mNativeContext;
+
+ @SuppressWarnings("unused")
+ private Surface mSurface;
+
+ /**
+ * Default constructor.
+ */
+ public MediaRecorder() {
+ native_setup();
+ }
+
+ /**
+ * 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)}.
+ * @hide
+ */
+ 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 codec */
+ public static final int THREE_GPP = 1;
+ /** MPEG4 media codec */
+ public static final int MPEG_4 = 2;
+ }
+
+ /**
+ * 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)}.
+ * @hide
+ */
+ 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
+ * @hide
+ */
+ 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()
+ * @hide
+ */
+ 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().
+ * @hide
+ */
+ 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
+ * @hide
+ */
+ public native void setVideoEncoder(int video_encoder)
+ throws IllegalStateException;
+
+ /**
+ * 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 native void setOutputFile(String path) throws IllegalStateException;
+
+ /**
+ * 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().
+ */
+ public native void prepare() throws IllegalStateException;
+
+ /**
+ * 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 native void 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;
+
+
+ /**
+ * 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() 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..dbffefd
--- /dev/null
+++ b/media/java/android/media/MediaScanner.java
@@ -0,0 +1,1355 @@
+/*
+ * 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[] 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 music = (path.indexOf(MUSIC_DIR) > 0) ||
+ (!ringtones && !notifications && !alarms);
+
+ 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);
+ }
+ } 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;
+ }
+
+ public Uri endFile(FileCacheEntry entry, boolean ringtones, boolean notifications, boolean alarms, boolean music)
+ 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);
+ } else if (isImage) {
+ File parentFile = new File(entry.mPath).getParentFile();
+ if (parentFile == null) {
+ return null;
+ }
+ String path = parentFile.toString().toLowerCase();
+ String name = parentFile.getName().toLowerCase();
+
+ values.put(Images.ImageColumns.BUCKET_ID, path.hashCode());
+ values.put(Images.ImageColumns.BUCKET_DISPLAY_NAME, name);
+ }
+
+ 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 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/Ringtone.java b/media/java/android/media/Ringtone.java
new file mode 100644
index 0000000..aba0e7c
--- /dev/null
+++ b/media/java/android/media/Ringtone.java
@@ -0,0 +1,221 @@
+/*
+ * 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.os.ParcelFileDescriptor;
+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;
+ }
+
+ /**
+ * 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) {
+ mAudio.setDataSource(mAssetFileDescriptor.getFileDescriptor(),
+ mAssetFileDescriptor.getStartOffset(),
+ mAssetFileDescriptor.getLength());
+ } 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..7397218
--- /dev/null
+++ b/media/java/android/media/RingtoneManager.java
@@ -0,0 +1,680 @@
+/*
+ * 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.ContentResolver;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.database.MergeCursor;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+import android.provider.DrmStore;
+import android.provider.MediaStore;
+import android.provider.Settings;
+import android.provider.Settings.System;
+import android.util.Config;
+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);
+ }
+
+ /**
+ * 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();
+ }
+
+ return mPreviousRingtone = getRingtone(mContext, getRingtoneUri(position));
+ }
+
+ /**
+ * 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) {
+ ContentResolver res = context.getContentResolver();
+
+ try {
+ Ringtone r = new Ringtone(context);
+ 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..1688b1b
--- /dev/null
+++ b/media/java/android/media/SoundPool.java
@@ -0,0 +1,111 @@
+/*
+ * 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.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;
+ }
+
+ 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>