diff options
Diffstat (limited to 'media')
122 files changed, 15868 insertions, 13634 deletions
diff --git a/media/java/android/drm/mobile1/DrmConstraintInfo.java b/media/java/android/drm/mobile1/DrmConstraintInfo.java deleted file mode 100644 index 50ae8bd..0000000 --- a/media/java/android/drm/mobile1/DrmConstraintInfo.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * 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/DrmRawContent.java b/media/java/android/drm/mobile1/DrmRawContent.java deleted file mode 100644 index 046b84a..0000000 --- a/media/java/android/drm/mobile1/DrmRawContent.java +++ /dev/null @@ -1,464 +0,0 @@ -/* - * 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 deleted file mode 100644 index bcccb6a..0000000 --- a/media/java/android/drm/mobile1/DrmRights.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * 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 deleted file mode 100644 index 1bc36ec..0000000 --- a/media/java/android/drm/mobile1/DrmRightsManager.java +++ /dev/null @@ -1,255 +0,0 @@ -/* - * 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 deleted file mode 100644 index 1c9bf9d..0000000 --- a/media/java/android/drm/mobile1/package.html +++ /dev/null @@ -1,5 +0,0 @@ -<html> -<body> - {@hide} -</body> -</html> diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 93ab401..d652cae 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -16,6 +16,7 @@ package android.media; +import android.Manifest; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.app.PendingIntent; @@ -23,6 +24,7 @@ import android.bluetooth.BluetoothDevice; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.media.RemoteController.OnClientUpdateListener; import android.os.Binder; import android.os.Build; import android.os.Handler; @@ -323,6 +325,12 @@ public class AudioManager { public static final int FLAG_FIXED_VOLUME = 1 << 5; /** + * Indicates the volume set/adjust call is for Bluetooth absolute volume + * @hide + */ + public static final int FLAG_BLUETOOTH_ABS_VOLUME = 1 << 6; + + /** * Ringer mode that will be silent and will not vibrate. (This overrides the * vibrate setting.) * @@ -437,6 +445,38 @@ public class AudioManager { } /** + * Sends a simulated key event for a media button. + * To simulate a key press, you must first send a KeyEvent built with a + * {@link KeyEvent#ACTION_DOWN} action, then another event with the {@link KeyEvent#ACTION_UP} + * action. + * <p>The key event will be sent to the current media key event consumer which registered with + * {@link AudioManager#registerMediaButtonEventReceiver(PendingIntent)}. + * @param keyEvent a {@link KeyEvent} instance whose key code is one of + * {@link KeyEvent#KEYCODE_MUTE}, + * {@link KeyEvent#KEYCODE_HEADSETHOOK}, + * {@link KeyEvent#KEYCODE_MEDIA_PLAY}, + * {@link KeyEvent#KEYCODE_MEDIA_PAUSE}, + * {@link KeyEvent#KEYCODE_MEDIA_PLAY_PAUSE}, + * {@link KeyEvent#KEYCODE_MEDIA_STOP}, + * {@link KeyEvent#KEYCODE_MEDIA_NEXT}, + * {@link KeyEvent#KEYCODE_MEDIA_PREVIOUS}, + * {@link KeyEvent#KEYCODE_MEDIA_REWIND}, + * {@link KeyEvent#KEYCODE_MEDIA_RECORD}, + * {@link KeyEvent#KEYCODE_MEDIA_FAST_FORWARD}, + * {@link KeyEvent#KEYCODE_MEDIA_CLOSE}, + * {@link KeyEvent#KEYCODE_MEDIA_EJECT}, + * or {@link KeyEvent#KEYCODE_MEDIA_AUDIO_TRACK}. + */ + public void dispatchMediaKeyEvent(KeyEvent keyEvent) { + IAudioService service = getService(); + try { + service.dispatchMediaKeyEvent(keyEvent); + } catch (RemoteException e) { + Log.e(TAG, "dispatchMediaKeyEvent threw exception ", e); + } + } + + /** * @hide */ public void preDispatchKeyEvent(KeyEvent event, int stream) { @@ -551,9 +591,10 @@ public class AudioManager { IAudioService service = getService(); try { if (mUseMasterVolume) { - service.adjustMasterVolume(direction, flags); + service.adjustMasterVolume(direction, flags, mContext.getOpPackageName()); } else { - service.adjustStreamVolume(streamType, direction, flags); + service.adjustStreamVolume(streamType, direction, flags, + mContext.getOpPackageName()); } } catch (RemoteException e) { Log.e(TAG, "Dead object in adjustStreamVolume", e); @@ -581,9 +622,9 @@ public class AudioManager { IAudioService service = getService(); try { if (mUseMasterVolume) { - service.adjustMasterVolume(direction, flags); + service.adjustMasterVolume(direction, flags, mContext.getOpPackageName()); } else { - service.adjustVolume(direction, flags); + service.adjustVolume(direction, flags, mContext.getOpPackageName()); } } catch (RemoteException e) { Log.e(TAG, "Dead object in adjustVolume", e); @@ -611,9 +652,10 @@ public class AudioManager { IAudioService service = getService(); try { if (mUseMasterVolume) { - service.adjustMasterVolume(direction, flags); + service.adjustMasterVolume(direction, flags, mContext.getOpPackageName()); } else { - service.adjustSuggestedStreamVolume(direction, suggestedStreamType, flags); + service.adjustSuggestedStreamVolume(direction, suggestedStreamType, flags, + mContext.getOpPackageName()); } } catch (RemoteException e) { Log.e(TAG, "Dead object in adjustSuggestedStreamVolume", e); @@ -632,7 +674,7 @@ public class AudioManager { public void adjustMasterVolume(int steps, int flags) { IAudioService service = getService(); try { - service.adjustMasterVolume(steps, flags); + service.adjustMasterVolume(steps, flags, mContext.getOpPackageName()); } catch (RemoteException e) { Log.e(TAG, "Dead object in adjustMasterVolume", e); } @@ -784,9 +826,9 @@ public class AudioManager { IAudioService service = getService(); try { if (mUseMasterVolume) { - service.setMasterVolume(index, flags); + service.setMasterVolume(index, flags, mContext.getOpPackageName()); } else { - service.setStreamVolume(streamType, index, flags); + service.setStreamVolume(streamType, index, flags, mContext.getOpPackageName()); } } catch (RemoteException e) { Log.e(TAG, "Dead object in setStreamVolume", e); @@ -843,16 +885,16 @@ public class AudioManager { * Sets the volume index for master volume. * * @param index The volume index to set. See - * {@link #getMasterMaxVolume(int)} for the largest valid value. + * {@link #getMasterMaxVolume()} for the largest valid value. * @param flags One or more flags. - * @see #getMasterMaxVolume(int) - * @see #getMasterVolume(int) + * @see #getMasterMaxVolume() + * @see #getMasterVolume() * @hide */ public void setMasterVolume(int index, int flags) { IAudioService service = getService(); try { - service.setMasterVolume(index, flags); + service.setMasterVolume(index, flags, mContext.getOpPackageName()); } catch (RemoteException e) { Log.e(TAG, "Dead object in setMasterVolume", e); } @@ -1314,19 +1356,6 @@ public class AudioManager { } /** - * @hide - * Signals whether remote submix audio rerouting is enabled. - */ - public void setRemoteSubmixOn(boolean on, int address) { - IAudioService service = getService(); - try { - service.setRemoteSubmixOn(on, address); - } catch (RemoteException e) { - Log.e(TAG, "Dead object in setRemoteSubmixOn", e); - } - } - - /** * Sets audio routing to the wired headset on or off. * * @param on set <var>true</var> to route audio to/from wired @@ -1543,12 +1572,36 @@ public class AudioManager { /** * @hide - * Checks whether speech recognition is active - * @return true if a recording with source {@link MediaRecorder.AudioSource#VOICE_RECOGNITION} - * is underway. + * Checks whether any local or remote media playback is active. + * Local playback refers to playback for instance on the device's speakers or wired headphones. + * Remote playback refers to playback for instance on a wireless display mirroring the + * devices's, or on a device using a Cast-like protocol. + * @return true if media playback, from which the device is aware, is active. */ - public boolean isSpeechRecognitionActive() { - return AudioSystem.isSourceActive(MediaRecorder.AudioSource.VOICE_RECOGNITION); + public boolean isLocalOrRemoteMusicActive() { + IAudioService service = getService(); + try { + return service.isLocalOrRemoteMusicActive(); + } catch (RemoteException e) { + Log.e(TAG, "Dead object in isLocalOrRemoteMusicActive()", e); + return false; + } + } + + /** + * @hide + * Checks whether the current audio focus is exclusive. + * @return true if the top of the audio focus stack requested focus + * with {@link #AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE} + */ + public boolean isAudioFocusExclusive() { + IAudioService service = getService(); + try { + return service.getCurrentAudioFocus() == AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE; + } catch (RemoteException e) { + Log.e(TAG, "Dead object in isAudioFocusExclusive()", e); + return false; + } } /** @@ -1563,7 +1616,8 @@ public class AudioManager { } IAudioService service = getService(); try { - service.adjustLocalOrRemoteStreamVolume(streamType, direction); + service.adjustLocalOrRemoteStreamVolume(streamType, direction, + mContext.getOpPackageName()); } catch (RemoteException e) { Log.e(TAG, "Dead object in adjustLocalOrRemoteStreamVolume", e); } @@ -1582,7 +1636,7 @@ public class AudioManager { */ /** * @hide - * @deprecated Use {@link #setPrameters(String)} instead + * @deprecated Use {@link #setParameters(String)} instead */ @Deprecated public void setParameter(String key, String value) { setParameters(key+"="+value); @@ -1656,10 +1710,16 @@ public class AudioManager { * @see #playSoundEffect(int) */ public static final int FX_KEYPRESS_RETURN = 8; + + /** + * Invalid keypress sound + * @see #playSoundEffect(int) + */ + public static final int FX_KEYPRESS_INVALID = 9; /** * @hide Number of sound effects */ - public static final int NUM_SOUND_EFFECTS = 9; + public static final int NUM_SOUND_EFFECTS = 10; /** * Plays a sound effect (Key clicks, lid open/close...) @@ -1673,6 +1733,7 @@ public class AudioManager { * {@link #FX_KEYPRESS_SPACEBAR}, * {@link #FX_KEYPRESS_DELETE}, * {@link #FX_KEYPRESS_RETURN}, + * {@link #FX_KEYPRESS_INVALID}, * NOTE: This version uses the UI settings to determine * whether sounds are heard or not. */ @@ -1705,6 +1766,7 @@ public class AudioManager { * {@link #FX_KEYPRESS_SPACEBAR}, * {@link #FX_KEYPRESS_DELETE}, * {@link #FX_KEYPRESS_RETURN}, + * {@link #FX_KEYPRESS_INVALID}, * @param volume Sound effect volume. * The volume value is a raw scalar so UI controls should be scaled logarithmically. * If a volume of -1 is specified, the AudioManager.STREAM_MUSIC stream volume minus 3dB will be used. @@ -1760,6 +1822,12 @@ public class AudioManager { } /** + * @hide + * Used to indicate no audio focus has been gained or lost. + */ + public static final int AUDIOFOCUS_NONE = 0; + + /** * Used to indicate a gain of audio focus, or a request of audio focus, of unknown duration. * @see OnAudioFocusChangeListener#onAudioFocusChange(int) * @see #requestAudioFocus(OnAudioFocusChangeListener, int, int) @@ -1784,6 +1852,15 @@ public class AudioManager { */ public static final int AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK = 3; /** + * Used to indicate a temporary request of audio focus, anticipated to last a short + * amount of time, during which no other applications, or system components, should play + * anything. Examples of exclusive and transient audio focus requests are voice + * memo recording and speech recognition, during which the system shouldn't play any + * notifications, and media playback should have paused. + * @see #requestAudioFocus(OnAudioFocusChangeListener, int, int) + */ + public static final int AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE = 4; + /** * Used to indicate a loss of audio focus of unknown duration. * @see OnAudioFocusChangeListener#onAudioFocusChange(int) */ @@ -1947,14 +2024,17 @@ public class AudioManager { * for the playback of driving directions, or notifications sounds. * Use {@link #AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK} to indicate also that it's ok for * the previous focus owner to keep playing if it ducks its audio output. + * Alternatively use {@link #AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE} for a temporary request + * that benefits from the system not playing disruptive sounds like notifications, for + * usecases such as voice memo recording, or speech recognition. * Use {@link #AUDIOFOCUS_GAIN} for a focus request of unknown duration such * as the playback of a song or a video. * @return {@link #AUDIOFOCUS_REQUEST_FAILED} or {@link #AUDIOFOCUS_REQUEST_GRANTED} */ public int requestAudioFocus(OnAudioFocusChangeListener l, int streamType, int durationHint) { int status = AUDIOFOCUS_REQUEST_FAILED; - if ((durationHint < AUDIOFOCUS_GAIN) || (durationHint > AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK)) - { + if ((durationHint < AUDIOFOCUS_GAIN) || + (durationHint > AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)) { Log.e(TAG, "Invalid duration hint, audio focus request denied"); return status; } @@ -1964,7 +2044,7 @@ public class AudioManager { try { status = service.requestAudioFocus(streamType, durationHint, mICallBack, mAudioFocusDispatcher, getIdForAudioFocusListener(l), - mContext.getPackageName() /* package name */); + mContext.getOpPackageName() /* package name */); } catch (RemoteException e) { Log.e(TAG, "Can't call requestAudioFocus() on AudioService due to "+e); } @@ -1985,8 +2065,8 @@ public class AudioManager { IAudioService service = getService(); try { service.requestAudioFocus(streamType, durationHint, mICallBack, null, - AudioService.IN_VOICE_COMM_FOCUS_ID, - "system" /* dump-friendly package name */); + MediaFocusControl.IN_VOICE_COMM_FOCUS_ID, + mContext.getOpPackageName()); } catch (RemoteException e) { Log.e(TAG, "Can't call requestAudioFocusForCall() on AudioService due to "+e); } @@ -2001,7 +2081,7 @@ public class AudioManager { public void abandonAudioFocusForCall() { IAudioService service = getService(); try { - service.abandonAudioFocus(null, AudioService.IN_VOICE_COMM_FOCUS_ID); + service.abandonAudioFocus(null, MediaFocusControl.IN_VOICE_COMM_FOCUS_ID); } catch (RemoteException e) { Log.e(TAG, "Can't call abandonAudioFocusForCall() on AudioService due to "+e); } @@ -2206,6 +2286,55 @@ public class AudioManager { } /** + * Registers a {@link RemoteController} instance for it to receive media metadata updates + * and playback state information from applications using {@link RemoteControlClient}, and + * control their playback. + * <p>Registration requires the {@link OnClientUpdateListener} listener to be one of the + * enabled notification listeners (see + * {@link android.service.notification.NotificationListenerService}). + * @param rctlr the object to register. + * @return true if the {@link RemoteController} was successfully registered, false if an + * error occurred, due to an internal system error, or insufficient permissions. + */ + public boolean registerRemoteController(RemoteController rctlr) { + if (rctlr == null) { + return false; + } + IAudioService service = getService(); + final RemoteController.OnClientUpdateListener l = rctlr.getUpdateListener(); + final ComponentName listenerComponent = new ComponentName(mContext, l.getClass()); + try { + int[] artworkDimensions = rctlr.getArtworkSize(); + boolean reg = service.registerRemoteController(rctlr.getRcDisplay(), + artworkDimensions[0]/*w*/, artworkDimensions[1]/*h*/, + listenerComponent); + rctlr.setIsRegistered(reg); + return reg; + } catch (RemoteException e) { + Log.e(TAG, "Dead object in registerRemoteController " + e); + return false; + } + } + + /** + * Unregisters a {@link RemoteController}, causing it to no longer receive media metadata and + * playback state information, and no longer be capable of controlling playback. + * @param rctlr the object to unregister. + */ + public void unregisterRemoteController(RemoteController rctlr) { + if (rctlr == null) { + return; + } + IAudioService service = getService(); + try { + service.unregisterRemoteControlDisplay(rctlr.getRcDisplay()); + rctlr.setIsRegistered(false); + } catch (RemoteException e) { + Log.e(TAG, "Dead object in unregisterRemoteControlDisplay " + e); + } + } + + /** * @hide * Registers a remote control display that will be sent information by remote control clients. * Use this method if your IRemoteControlDisplay is not going to display artwork, otherwise @@ -2213,6 +2342,7 @@ public class AudioManager { * artwork size directly, or * {@link #remoteControlDisplayUsesBitmapSize(IRemoteControlDisplay, int, int)} later if artwork * is not yet needed. + * <p>Registration requires the {@link Manifest.permission#MEDIA_CONTENT_CONTROL} permission. * @param rcd the IRemoteControlDisplay */ public void registerRemoteControlDisplay(IRemoteControlDisplay rcd) { @@ -2223,6 +2353,7 @@ public class AudioManager { /** * @hide * Registers a remote control display that will be sent information by remote control clients. + * <p>Registration requires the {@link Manifest.permission#MEDIA_CONTENT_CONTROL} permission. * @param rcd * @param w the maximum width of the expected bitmap. Negative values indicate it is * useless to send artwork. @@ -2235,8 +2366,6 @@ public class AudioManager { } IAudioService service = getService(); try { - // passing a negative value for art work width and height as they are unknown at - // this stage service.registerRemoteControlDisplay(rcd, w, h); } catch (RemoteException e) { Log.e(TAG, "Dead object in registerRemoteControlDisplay " + e); @@ -2328,6 +2457,26 @@ public class AudioManager { } /** + * @hide + * Notify the user of a RemoteControlClient that it should update its metadata with the + * new value for the given key. + * @param generationId the RemoteControlClient generation counter for which this request is + * issued. Requests for an older generation than current one will be ignored. + * @param key the metadata key for which a new value exists + * @param value the new metadata value + */ + public void updateRemoteControlClientMetadata(int generationId, int key, + Rating value) { + IAudioService service = getService(); + try { + service.updateRemoteControlClientMetadata(generationId, key, value); + } catch (RemoteException e) { + Log.e(TAG, "Dead object in updateRemoteControlClientMetadata("+ generationId + ", " + + key +", " + value + ")", e); + } + } + + /** * @hide * Reload audio settings. This method is called by Settings backup * agent when audio settings are restored and causes the AudioService @@ -2342,6 +2491,21 @@ public class AudioManager { } } + /** + * @hide + * Notifies AudioService that it is connected to an A2DP device that supports absolute volume, + * so that AudioService can send volume change events to the A2DP device, rather than handling + * them. + */ + public void avrcpSupportsAbsoluteVolume(String address, boolean support) { + IAudioService service = getService(); + try { + service.avrcpSupportsAbsoluteVolume(address, support); + } catch (RemoteException e) { + Log.e(TAG, "Dead object in avrcpSupportsAbsoluteVolume", e); + } + } + /** * {@hide} */ diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java index 5383d08..f49ef2e 100644 --- a/media/java/android/media/AudioRecord.java +++ b/media/java/android/media/AudioRecord.java @@ -17,8 +17,6 @@ package android.media; import java.lang.ref.WeakReference; -import java.lang.IllegalArgumentException; -import java.lang.IllegalStateException; import java.nio.ByteBuffer; import android.os.Handler; @@ -89,7 +87,7 @@ public class AudioRecord private static final int AUDIORECORD_ERROR_SETUP_NATIVEINITFAILED = -20; // Events: - // to keep in sync with frameworks/base/include/media/AudioRecord.h + // to keep in sync with frameworks/av/include/media/AudioRecord.h /** * Event id denotes when record head has reached a previously set marker. */ @@ -99,7 +97,7 @@ public class AudioRecord */ private static final int NATIVE_EVENT_NEW_POS = 3; - private final static String TAG = "AudioRecord-Java"; + private final static String TAG = "android.media.AudioRecord"; //--------------------------------------------------------- @@ -124,29 +122,25 @@ public class AudioRecord /** * The audio data sampling rate in Hz. */ - private int mSampleRate = 22050; + private int mSampleRate; /** * The number of input audio channels (1 is mono, 2 is stereo) */ - private int mChannelCount = 1; + private int mChannelCount; /** * The audio channel mask */ - private int mChannels = AudioFormat.CHANNEL_IN_MONO; - /** - * The current audio channel configuration - */ - private int mChannelConfiguration = AudioFormat.CHANNEL_IN_MONO; + private int mChannelMask; /** * The encoding of the audio samples. * @see AudioFormat#ENCODING_PCM_8BIT * @see AudioFormat#ENCODING_PCM_16BIT */ - private int mAudioFormat = AudioFormat.ENCODING_PCM_16BIT; + private int mAudioFormat; /** * Where the audio data is recorded from. */ - private int mRecordSource = MediaRecorder.AudioSource.DEFAULT; + private int mRecordSource; /** * Indicates the state of the AudioRecord instance. */ @@ -214,7 +208,6 @@ public class AudioRecord public AudioRecord(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes) throws IllegalArgumentException { - mState = STATE_UNINITIALIZED; mRecordingState = RECORDSTATE_STOPPED; // remember which looper is associated with the AudioRecord instanciation @@ -232,7 +225,7 @@ public class AudioRecord //TODO: update native initialization when information about hardware init failure // due to capture device already open is available. int initResult = native_setup( new WeakReference<AudioRecord>(this), - mRecordSource, mSampleRate, mChannels, mAudioFormat, mNativeBufferSizeInBytes, + mRecordSource, mSampleRate, mChannelMask, mAudioFormat, mNativeBufferSizeInBytes, session); if (initResult != SUCCESS) { loge("Error code "+initResult+" when initializing native AudioRecord object."); @@ -250,7 +243,7 @@ public class AudioRecord // postconditions: // mRecordSource is valid // mChannelCount is valid - // mChannels is valid + // mChannelMask is valid // mAudioFormat is valid // mSampleRate is valid private void audioParamCheck(int audioSource, int sampleRateInHz, @@ -259,46 +252,40 @@ public class AudioRecord //-------------- // audio source if ( (audioSource < MediaRecorder.AudioSource.DEFAULT) || - (audioSource > MediaRecorder.getAudioSourceMax()) ) { - throw (new IllegalArgumentException("Invalid audio source.")); - } else { - mRecordSource = audioSource; + ((audioSource > MediaRecorder.getAudioSourceMax()) && + (audioSource != MediaRecorder.AudioSource.HOTWORD)) ) { + throw new IllegalArgumentException("Invalid audio source."); } + mRecordSource = audioSource; //-------------- // sample rate if ( (sampleRateInHz < 4000) || (sampleRateInHz > 48000) ) { - throw (new IllegalArgumentException(sampleRateInHz - + "Hz is not a supported sample rate.")); - } else { - mSampleRate = sampleRateInHz; + throw new IllegalArgumentException(sampleRateInHz + + "Hz is not a supported sample rate."); } + mSampleRate = sampleRateInHz; //-------------- // channel config - mChannelConfiguration = channelConfig; - switch (channelConfig) { case AudioFormat.CHANNEL_IN_DEFAULT: // AudioFormat.CHANNEL_CONFIGURATION_DEFAULT case AudioFormat.CHANNEL_IN_MONO: case AudioFormat.CHANNEL_CONFIGURATION_MONO: mChannelCount = 1; - mChannels = AudioFormat.CHANNEL_IN_MONO; + mChannelMask = AudioFormat.CHANNEL_IN_MONO; break; case AudioFormat.CHANNEL_IN_STEREO: case AudioFormat.CHANNEL_CONFIGURATION_STEREO: mChannelCount = 2; - mChannels = AudioFormat.CHANNEL_IN_STEREO; + mChannelMask = AudioFormat.CHANNEL_IN_STEREO; break; case (AudioFormat.CHANNEL_IN_FRONT | AudioFormat.CHANNEL_IN_BACK): mChannelCount = 2; - mChannels = channelConfig; + mChannelMask = channelConfig; break; default: - mChannelCount = 0; - mChannels = AudioFormat.CHANNEL_INVALID; - mChannelConfiguration = AudioFormat.CHANNEL_INVALID; - throw (new IllegalArgumentException("Unsupported channel configuration.")); + throw new IllegalArgumentException("Unsupported channel configuration."); } //-------------- @@ -312,9 +299,8 @@ public class AudioRecord mAudioFormat = audioFormat; break; default: - mAudioFormat = AudioFormat.ENCODING_INVALID; - throw (new IllegalArgumentException("Unsupported sample encoding." - + " Should be ENCODING_PCM_8BIT or ENCODING_PCM_16BIT.")); + throw new IllegalArgumentException("Unsupported sample encoding." + + " Should be ENCODING_PCM_8BIT or ENCODING_PCM_16BIT."); } } @@ -331,7 +317,7 @@ public class AudioRecord int frameSizeInBytes = mChannelCount * (mAudioFormat == AudioFormat.ENCODING_PCM_8BIT ? 1 : 2); if ((audioBufferSize % frameSizeInBytes != 0) || (audioBufferSize < 1)) { - throw (new IllegalArgumentException("Invalid audio buffer size.")); + throw new IllegalArgumentException("Invalid audio buffer size."); } mNativeBufferSizeInBytes = audioBufferSize; @@ -393,7 +379,7 @@ public class AudioRecord * and {@link AudioFormat#CHANNEL_IN_STEREO}. */ public int getChannelConfiguration() { - return mChannelConfiguration; + return mChannelMask; } /** @@ -421,7 +407,9 @@ public class AudioRecord * @see AudioRecord#RECORDSTATE_RECORDING */ public int getRecordingState() { - return mRecordingState; + synchronized (mRecordingStateLock) { + return mRecordingState; + } } /** @@ -440,10 +428,12 @@ public class AudioRecord /** * Returns the minimum buffer size required for the successful creation of an AudioRecord - * object. + * object, in byte units. * Note that this size doesn't guarantee a smooth recording under load, and higher values * should be chosen according to the expected frequency at which the AudioRecord instance * will be polled for new data. + * See {@link #AudioRecord(int, int, int, int, int)} for more information on valid + * configuration values. * @param sampleRateInHz the sample rate expressed in Hertz. * @param channelConfig describes the configuration of the audio channels. * See {@link AudioFormat#CHANNEL_IN_MONO} and @@ -453,10 +443,9 @@ public class AudioRecord * @return {@link #ERROR_BAD_VALUE} if the recording parameters are not supported by the * hardware, or an invalid parameter was passed, * or {@link #ERROR} if the implementation was unable to query the hardware for its - * output properties, + * input properties, * or the minimum buffer size expressed in bytes. - * @see #AudioRecord(int, int, int, int, int) for more information on valid - * configuration values. + * @see #AudioRecord(int, int, int, int, int) */ static public int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat) { int channelCount = 0; @@ -474,21 +463,21 @@ public class AudioRecord case AudioFormat.CHANNEL_INVALID: default: loge("getMinBufferSize(): Invalid channel configuration."); - return AudioRecord.ERROR_BAD_VALUE; + return ERROR_BAD_VALUE; } // PCM_8BIT is not supported at the moment if (audioFormat != AudioFormat.ENCODING_PCM_16BIT) { loge("getMinBufferSize(): Invalid audio format."); - return AudioRecord.ERROR_BAD_VALUE; + return ERROR_BAD_VALUE; } int size = native_get_min_buff_size(sampleRateInHz, channelCount, audioFormat); if (size == 0) { - return AudioRecord.ERROR_BAD_VALUE; + return ERROR_BAD_VALUE; } else if (size == -1) { - return AudioRecord.ERROR; + return ERROR; } else { return size; @@ -514,8 +503,8 @@ public class AudioRecord public void startRecording() throws IllegalStateException { if (mState != STATE_INITIALIZED) { - throw(new IllegalStateException("startRecording() called on an " - +"uninitialized AudioRecord.")); + throw new IllegalStateException("startRecording() called on an " + + "uninitialized AudioRecord."); } // start recording @@ -536,8 +525,8 @@ public class AudioRecord public void startRecording(MediaSyncEvent syncEvent) throws IllegalStateException { if (mState != STATE_INITIALIZED) { - throw(new IllegalStateException("startRecording() called on an " - +"uninitialized AudioRecord.")); + throw new IllegalStateException("startRecording() called on an " + + "uninitialized AudioRecord."); } // start recording @@ -555,7 +544,7 @@ public class AudioRecord public void stop() throws IllegalStateException { if (mState != STATE_INITIALIZED) { - throw(new IllegalStateException("stop() called on an uninitialized AudioRecord.")); + throw new IllegalStateException("stop() called on an uninitialized AudioRecord."); } // stop recording @@ -585,6 +574,7 @@ public class AudioRecord } if ( (audioData == null) || (offsetInBytes < 0 ) || (sizeInBytes < 0) + || (offsetInBytes + sizeInBytes < 0) // detect integer overflow || (offsetInBytes + sizeInBytes > audioData.length)) { return ERROR_BAD_VALUE; } @@ -609,6 +599,7 @@ public class AudioRecord } if ( (audioData == null) || (offsetInShorts < 0 ) || (sizeInShorts < 0) + || (offsetInShorts + sizeInShorts < 0) // detect integer overflow || (offsetInShorts + sizeInShorts > audioData.length)) { return ERROR_BAD_VALUE; } @@ -692,6 +683,9 @@ public class AudioRecord * {@link #ERROR_INVALID_OPERATION} */ public int setNotificationMarkerPosition(int markerInFrames) { + if (mState == STATE_UNINITIALIZED) { + return ERROR_INVALID_OPERATION; + } return native_set_marker_pos(markerInFrames); } @@ -700,10 +694,14 @@ public class AudioRecord * Sets the period at which the listener is called, if set with * {@link #setRecordPositionUpdateListener(OnRecordPositionUpdateListener)} or * {@link #setRecordPositionUpdateListener(OnRecordPositionUpdateListener, Handler)}. + * It is possible for notifications to be lost if the period is too small. * @param periodInFrames update period expressed in frames * @return error code or success, see {@link #SUCCESS}, {@link #ERROR_INVALID_OPERATION} */ public int setPositionNotificationPeriod(int periodInFrames) { + if (mState == STATE_UNINITIALIZED) { + return ERROR_INVALID_OPERATION; + } return native_set_pos_update_period(periodInFrames); } @@ -769,9 +767,8 @@ public class AudioRecord } break; default: - Log.e(TAG, "[ android.media.AudioRecord.NativeEventHandler ] " + - "Unknown event type: " + msg.what); - break; + loge("Unknown native event type: " + msg.what); + break; } } }; @@ -837,11 +834,11 @@ public class AudioRecord //------------------ private static void logd(String msg) { - Log.d(TAG, "[ android.media.AudioRecord ] " + msg); + Log.d(TAG, msg); } private static void loge(String msg) { - Log.e(TAG, "[ android.media.AudioRecord ] " + msg); + Log.e(TAG, msg); } } diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java index aa91200..1f5fefd 100644 --- a/media/java/android/media/AudioService.java +++ b/media/java/android/media/AudioService.java @@ -22,11 +22,12 @@ import static android.media.AudioManager.RINGER_MODE_SILENT; import static android.media.AudioManager.RINGER_MODE_VIBRATE; import android.app.Activity; +import android.app.ActivityManager; import android.app.ActivityManagerNative; +import android.app.AppOpsManager; import android.app.KeyguardManager; import android.app.PendingIntent; import android.app.PendingIntent.CanceledException; -import android.app.PendingIntent.OnFinished; import android.bluetooth.BluetoothA2dp; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; @@ -47,6 +48,7 @@ import android.content.res.XmlResourceParser; import android.database.ContentObserver; import android.media.MediaPlayer.OnCompletionListener; import android.media.MediaPlayer.OnErrorListener; +import android.net.Uri; import android.os.Binder; import android.os.Build; import android.os.Bundle; @@ -86,6 +88,7 @@ import java.lang.reflect.Field; import java.util.ArrayList; import java.util.concurrent.ConcurrentHashMap; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -105,7 +108,7 @@ import java.util.Stack; * * @hide */ -public class AudioService extends IAudioService.Stub implements OnFinished { +public class AudioService extends IAudioService.Stub { private static final String TAG = "AudioService"; @@ -117,9 +120,10 @@ public class AudioService extends IAudioService.Stub implements OnFinished { /** How long to delay before persisting a change in volume/ringer mode. */ private static final int PERSIST_DELAY = 500; - private Context mContext; - private ContentResolver mContentResolver; - private boolean mVoiceCapable; + private final Context mContext; + private final ContentResolver mContentResolver; + private final AppOpsManager mAppOps; + private final boolean mVoiceCapable; /** The UI */ private VolumePanel mVolumePanel; @@ -138,39 +142,28 @@ public class AudioService extends IAudioService.Stub implements OnFinished { private static final int MSG_PERSIST_MASTER_VOLUME = 2; private static final int MSG_PERSIST_RINGER_MODE = 3; private static final int MSG_MEDIA_SERVER_DIED = 4; - private static final int MSG_MEDIA_SERVER_STARTED = 5; - private static final int MSG_PLAY_SOUND_EFFECT = 6; - private static final int MSG_BTA2DP_DOCK_TIMEOUT = 7; - private static final int MSG_LOAD_SOUND_EFFECTS = 8; - private static final int MSG_SET_FORCE_USE = 9; - private static final int MSG_PERSIST_MEDIABUTTONRECEIVER = 10; - private static final int MSG_BT_HEADSET_CNCT_FAILED = 11; - private static final int MSG_RCDISPLAY_CLEAR = 12; - private static final int MSG_RCDISPLAY_UPDATE = 13; - private static final int MSG_SET_ALL_VOLUMES = 14; - private static final int MSG_PERSIST_MASTER_VOLUME_MUTE = 15; - private static final int MSG_REPORT_NEW_ROUTES = 16; - private static final int MSG_REEVALUATE_REMOTE = 17; - private static final int MSG_RCC_NEW_PLAYBACK_INFO = 18; - private static final int MSG_RCC_NEW_VOLUME_OBS = 19; - private static final int MSG_SET_FORCE_BT_A2DP_USE = 20; + private static final int MSG_PLAY_SOUND_EFFECT = 5; + private static final int MSG_BTA2DP_DOCK_TIMEOUT = 6; + private static final int MSG_LOAD_SOUND_EFFECTS = 7; + private static final int MSG_SET_FORCE_USE = 8; + private static final int MSG_BT_HEADSET_CNCT_FAILED = 9; + private static final int MSG_SET_ALL_VOLUMES = 10; + private static final int MSG_PERSIST_MASTER_VOLUME_MUTE = 11; + private static final int MSG_REPORT_NEW_ROUTES = 12; + private static final int MSG_SET_FORCE_BT_A2DP_USE = 13; + private static final int MSG_CHECK_MUSIC_ACTIVE = 14; + private static final int MSG_BROADCAST_AUDIO_BECOMING_NOISY = 15; + private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME = 16; + private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED = 17; + private static final int MSG_PERSIST_SAFE_VOLUME_STATE = 18; + private static final int MSG_BROADCAST_BT_CONNECTION_STATE = 19; + private static final int MSG_UNLOAD_SOUND_EFFECTS = 20; // start of messages handled under wakelock // these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(), // and not with sendMsg(..., ..., SENDMSG_QUEUE, ...) - private static final int MSG_SET_WIRED_DEVICE_CONNECTION_STATE = 21; - private static final int MSG_SET_A2DP_CONNECTION_STATE = 22; + private static final int MSG_SET_WIRED_DEVICE_CONNECTION_STATE = 100; + private static final int MSG_SET_A2DP_CONNECTION_STATE = 101; // end of messages handled under wakelock - private static final int MSG_SET_RSX_CONNECTION_STATE = 23; // change remote submix connection - private static final int MSG_CHECK_MUSIC_ACTIVE = 24; - private static final int MSG_BROADCAST_AUDIO_BECOMING_NOISY = 25; - private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME = 26; - private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED = 27; - private static final int MSG_PERSIST_SAFE_VOLUME_STATE = 28; - private static final int MSG_PROMOTE_RCC = 29; - private static final int MSG_BROADCAST_BT_CONNECTION_STATE = 30; - private static final int MSG_UNLOAD_SOUND_EFFECTS = 31; - private static final int MSG_RCC_NEW_PLAYBACK_STATE = 32; - private static final int MSG_RCC_SEEK_REQUEST = 33; private static final int BTA2DP_DOCK_TIMEOUT_MILLIS = 8000; // Timeout for connection to bluetooth headset service @@ -184,12 +177,10 @@ public class AudioService extends IAudioService.Stub implements OnFinished { private VolumeStreamState[] mStreamStates; private SettingsObserver mSettingsObserver; - private int mMode; + private int mMode = AudioSystem.MODE_NORMAL; // protects mRingerMode private final Object mSettingsLock = new Object(); - private boolean mMediaServerOk; - private SoundPool mSoundPool; private final Object mSoundEffectsLock = new Object(); private static final int NUM_SOUNDPOOL_CHANNELS = 4; @@ -211,7 +202,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { private final int[][] SOUND_EFFECT_FILES_MAP = new int[AudioManager.NUM_SOUND_EFFECTS][2]; /** @hide Maximum volume index values for audio streams */ - private final int[] MAX_STREAM_VOLUME = new int[] { + private static final int[] MAX_STREAM_VOLUME = new int[] { 5, // STREAM_VOICE_CALL 7, // STREAM_SYSTEM 7, // STREAM_RING @@ -228,7 +219,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { * stream types that follow other stream behavior for volume settings * NOTE: do not create loops in aliases! * Some streams alias to different streams according to device category (phone or tablet) or - * use case (in call s off call...).See updateStreamVolumeAlias() for more details + * use case (in call vs off call...). See updateStreamVolumeAlias() for more details. * mStreamVolumeAlias contains the default aliases for a voice capable device (phone) and * STREAM_VOLUME_ALIAS_NON_VOICE for a non voice capable device (tablet).*/ private final int[] STREAM_VOLUME_ALIAS = new int[] { @@ -257,6 +248,23 @@ public class AudioService extends IAudioService.Stub implements OnFinished { }; private int[] mStreamVolumeAlias; + /** + * Map AudioSystem.STREAM_* constants to app ops. This should be used + * after mapping through mStreamVolumeAlias. + */ + private static final int[] STEAM_VOLUME_OPS = new int[] { + AppOpsManager.OP_AUDIO_VOICE_VOLUME, // STREAM_VOICE_CALL + AppOpsManager.OP_AUDIO_MEDIA_VOLUME, // STREAM_SYSTEM + AppOpsManager.OP_AUDIO_RING_VOLUME, // STREAM_RING + AppOpsManager.OP_AUDIO_MEDIA_VOLUME, // STREAM_MUSIC + AppOpsManager.OP_AUDIO_ALARM_VOLUME, // STREAM_ALARM + AppOpsManager.OP_AUDIO_NOTIFICATION_VOLUME, // STREAM_NOTIFICATION + AppOpsManager.OP_AUDIO_BLUETOOTH_VOLUME, // STREAM_BLUETOOTH_SCO + AppOpsManager.OP_AUDIO_MEDIA_VOLUME, // STREAM_SYSTEM_ENFORCED + AppOpsManager.OP_AUDIO_MEDIA_VOLUME, // STREAM_DTMF + AppOpsManager.OP_AUDIO_MEDIA_VOLUME, // STREAM_TTS + }; + private final boolean mUseFixedVolume; // stream names used by dumpStreamStates() @@ -277,23 +285,13 @@ public class AudioService extends IAudioService.Stub implements OnFinished { public void onError(int error) { switch (error) { case AudioSystem.AUDIO_STATUS_SERVER_DIED: - if (mMediaServerOk) { - sendMsg(mAudioHandler, MSG_MEDIA_SERVER_DIED, SENDMSG_NOOP, 0, 0, - null, 1500); - mMediaServerOk = false; - } - break; - case AudioSystem.AUDIO_STATUS_OK: - if (!mMediaServerOk) { - sendMsg(mAudioHandler, MSG_MEDIA_SERVER_STARTED, SENDMSG_NOOP, 0, 0, - null, 0); - mMediaServerOk = true; - } + sendMsg(mAudioHandler, MSG_MEDIA_SERVER_DIED, + SENDMSG_NOOP, 0, 0, null, 0); break; default: break; } - } + } }; /** @@ -305,7 +303,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { private int mRingerMode; /** @see System#MODE_RINGER_STREAMS_AFFECTED */ - private int mRingerModeAffectedStreams; + private int mRingerModeAffectedStreams = 0; // Streams currently muted by ringer mode private int mRingerModeMutedStreams; @@ -326,9 +324,6 @@ public class AudioService extends IAudioService.Stub implements OnFinished { // Broadcast receiver for device connections intent broadcasts private final BroadcastReceiver mReceiver = new AudioServiceBroadcastReceiver(); - // Used to alter media button redirection when the phone is ringing. - private boolean mIsRinging = false; - // Devices currently connected private final HashMap <Integer, String> mConnectedDevices = new HashMap <Integer, String>(); @@ -449,6 +444,16 @@ public class AudioService extends IAudioService.Stub implements OnFinished { // and used later when/if disableSafeMediaVolume() is called. private StreamVolumeCommand mPendingVolumeCommand; + private PowerManager.WakeLock mAudioEventWakeLock; + + private final MediaFocusControl mMediaFocusControl; + + // Reference to BluetoothA2dp to query for AbsoluteVolume. + private BluetoothA2dp mA2dp; + private final Object mA2dpAvrcpLock = new Object(); + // If absolute volume is supported in AVRCP device + private boolean mAvrcpAbsVolSupported = false; + /////////////////////////////////////////////////////////////////////////// // Construction /////////////////////////////////////////////////////////////////////////// @@ -457,11 +462,12 @@ public class AudioService extends IAudioService.Stub implements OnFinished { public AudioService(Context context) { mContext = context; mContentResolver = context.getContentResolver(); + mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE); mVoiceCapable = mContext.getResources().getBoolean( com.android.internal.R.bool.config_voice_capable); PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); - mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent"); + mAudioEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleAudioEvent"); Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); mHasVibrator = vibrator == null ? false : vibrator.hasVibrator(); @@ -475,11 +481,15 @@ public class AudioService extends IAudioService.Stub implements OnFinished { com.android.internal.R.integer.config_soundEffectVolumeDb); mVolumePanel = new VolumePanel(context, this); - mMode = AudioSystem.MODE_NORMAL; mForcedUseForComm = AudioSystem.FORCE_NONE; createAudioSystemThread(); + mMediaFocusControl = new MediaFocusControl(mAudioHandler.getLooper(), + mContext, /*VolumeController*/ mVolumePanel, this); + + AudioSystem.setErrorCallback(mAudioSystemCallback); + boolean cameraSoundForced = mContext.getResources().getBoolean( com.android.internal.R.bool.config_camera_sound_forced); mCameraSoundForced = new Boolean(cameraSoundForced); @@ -503,20 +513,20 @@ public class AudioService extends IAudioService.Stub implements OnFinished { mUseFixedVolume = mContext.getResources().getBoolean( com.android.internal.R.bool.config_useFixedVolume); + // must be called before readPersistedSettings() which needs a valid mStreamVolumeAlias[] + // array initialized by updateStreamVolumeAlias() + updateStreamVolumeAlias(false /*updateVolumes*/); readPersistedSettings(); mSettingsObserver = new SettingsObserver(); - updateStreamVolumeAlias(false /*updateVolumes*/); createStreamStates(); - mMediaServerOk = true; + readAndSetLowRamDevice(); // Call setRingerModeInt() to apply correct mute // state on streams affected by ringer mode. mRingerModeMutedStreams = 0; setRingerModeInt(getRingerMode(), false); - AudioSystem.setErrorCallback(mAudioSystemCallback); - // Register for device connection intent broadcasts. IntentFilter intentFilter = new IntentFilter(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED); @@ -548,20 +558,6 @@ public class AudioService extends IAudioService.Stub implements OnFinished { context.registerReceiver(mReceiver, intentFilter); - // Register for package removal intent broadcasts for media button receiver persistence - IntentFilter pkgFilter = new IntentFilter(); - pkgFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); - pkgFilter.addAction(Intent.ACTION_PACKAGE_ADDED); - pkgFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); - pkgFilter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED); - pkgFilter.addDataScheme("package"); - context.registerReceiver(mReceiver, pkgFilter); - - // Register for phone state monitoring - TelephonyManager tmgr = (TelephonyManager) - context.getSystemService(Context.TELEPHONY_SERVICE); - tmgr.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE); - mUseMasterVolume = context.getResources().getBoolean( com.android.internal.R.bool.config_useMasterVolume); restoreMasterVolume(); @@ -569,11 +565,6 @@ public class AudioService extends IAudioService.Stub implements OnFinished { mMasterVolumeRamp = context.getResources().getIntArray( com.android.internal.R.array.config_masterVolumeRamp); - mMainRemote = new RemotePlaybackState(-1, MAX_STREAM_VOLUME[AudioManager.STREAM_MUSIC], - MAX_STREAM_VOLUME[AudioManager.STREAM_MUSIC]); - mHasRemotePlayback = false; - mMainRemoteIsActive = false; - postReevaluateRemote(); } private void createAudioSystemThread() { @@ -645,10 +636,15 @@ public class AudioService extends IAudioService.Stub implements OnFinished { } if (isInCommunication()) { dtmfStreamAlias = AudioSystem.STREAM_VOICE_CALL; + mRingerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_DTMF); + } else { + mRingerModeAffectedStreams |= (1 << AudioSystem.STREAM_DTMF); } mStreamVolumeAlias[AudioSystem.STREAM_DTMF] = dtmfStreamAlias; if (updateVolumes) { mStreamStates[AudioSystem.STREAM_DTMF].setAllIndexes(mStreamStates[dtmfStreamAlias]); + // apply stream mute states according to new value of mRingerModeAffectedStreams + setRingerModeInt(getRingerMode(), false); sendMsg(mAudioHandler, MSG_SET_ALL_VOLUMES, SENDMSG_QUEUE, @@ -715,37 +711,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { mHasVibrator ? AudioManager.VIBRATE_SETTING_ONLY_SILENT : AudioManager.VIBRATE_SETTING_OFF); - // make sure settings for ringer mode are consistent with device type: non voice capable - // devices (tablets) include media stream in silent mode whereas phones don't. - mRingerModeAffectedStreams = Settings.System.getIntForUser(cr, - Settings.System.MODE_RINGER_STREAMS_AFFECTED, - ((1 << AudioSystem.STREAM_RING)|(1 << AudioSystem.STREAM_NOTIFICATION)| - (1 << AudioSystem.STREAM_SYSTEM)|(1 << AudioSystem.STREAM_SYSTEM_ENFORCED)), - UserHandle.USER_CURRENT); - - // ringtone, notification and system streams are always affected by ringer mode - mRingerModeAffectedStreams |= (1 << AudioSystem.STREAM_RING)| - (1 << AudioSystem.STREAM_NOTIFICATION)| - (1 << AudioSystem.STREAM_SYSTEM); - - if (mVoiceCapable) { - mRingerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_MUSIC); - } else { - mRingerModeAffectedStreams |= (1 << AudioSystem.STREAM_MUSIC); - } - synchronized (mCameraSoundForced) { - if (mCameraSoundForced) { - mRingerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_SYSTEM_ENFORCED); - } else { - mRingerModeAffectedStreams |= (1 << AudioSystem.STREAM_SYSTEM_ENFORCED); - } - } - - Settings.System.putIntForUser(cr, - Settings.System.MODE_RINGER_STREAMS_AFFECTED, - mRingerModeAffectedStreams, - UserHandle.USER_CURRENT); - + updateRingerModeAffectedStreams(); readDockAudioSettings(cr); } @@ -775,7 +741,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { broadcastVibrateSetting(AudioManager.VIBRATE_TYPE_NOTIFICATION); // Restore the default media button receiver from the system settings - restoreMediaButtonReceiver(); + mMediaFocusControl.restoreMediaButtonReceiver(); } private int rescaleIndex(int index, int srcStream, int dstStream) { @@ -785,25 +751,48 @@ public class AudioService extends IAudioService.Stub implements OnFinished { /////////////////////////////////////////////////////////////////////////// // IPC methods /////////////////////////////////////////////////////////////////////////// + /** @see AudioManager#isLocalOrRemoteMusicActive() */ + public boolean isLocalOrRemoteMusicActive() { + if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0)) { + // local / wired / BT playback active + if (DEBUG_VOL) Log.d(TAG, "isLocalOrRemoteMusicActive(): local"); + return true; + } + if (mMediaFocusControl.checkUpdateRemoteStateIfActive(AudioSystem.STREAM_MUSIC)) { + // remote "cast-like" playback active + if (DEBUG_VOL) Log.d(TAG, "isLocalOrRemoteMusicActive(): has PLAYBACK_TYPE_REMOTE"); + return true; + } + if (AudioSystem.isStreamActiveRemotely(AudioSystem.STREAM_MUSIC, 0)) { + // remote submix playback active + if (DEBUG_VOL) Log.d(TAG, "isLocalOrRemoteMusicActive(): remote submix"); + return true; + } + if (DEBUG_VOL) Log.d(TAG, "isLocalOrRemoteMusicActive(): no"); + return false; + } /** @see AudioManager#adjustVolume(int, int) */ - public void adjustVolume(int direction, int flags) { - adjustSuggestedStreamVolume(direction, AudioManager.USE_DEFAULT_STREAM_TYPE, flags); + public void adjustVolume(int direction, int flags, String callingPackage) { + adjustSuggestedStreamVolume(direction, AudioManager.USE_DEFAULT_STREAM_TYPE, flags, + callingPackage); } /** @see AudioManager#adjustLocalOrRemoteStreamVolume(int, int) with current assumption * on streamType: fixed to STREAM_MUSIC */ - public void adjustLocalOrRemoteStreamVolume(int streamType, int direction) { + public void adjustLocalOrRemoteStreamVolume(int streamType, int direction, + String callingPackage) { if (DEBUG_VOL) Log.d(TAG, "adjustLocalOrRemoteStreamVolume(dir="+direction+")"); - if (checkUpdateRemoteStateIfActive(AudioSystem.STREAM_MUSIC)) { - adjustRemoteVolume(AudioSystem.STREAM_MUSIC, direction, 0); - } else if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0)) { - adjustStreamVolume(AudioSystem.STREAM_MUSIC, direction, 0); + if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0)) { + adjustStreamVolume(AudioSystem.STREAM_MUSIC, direction, 0, callingPackage); + } else if (mMediaFocusControl.checkUpdateRemoteStateIfActive(AudioSystem.STREAM_MUSIC)) { + mMediaFocusControl.adjustRemoteVolume(AudioSystem.STREAM_MUSIC, direction, 0); } } /** @see AudioManager#adjustVolume(int, int) */ - public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags) { + public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags, + String callingPackage) { if (DEBUG_VOL) Log.d(TAG, "adjustSuggestedStreamVolume() stream="+suggestedStreamType); int streamType; if (mVolumeControlStream != -1) { @@ -824,14 +813,15 @@ public class AudioService extends IAudioService.Stub implements OnFinished { // don't play sounds for remote flags &= ~(AudioManager.FLAG_PLAY_SOUND|AudioManager.FLAG_FIXED_VOLUME); //if (DEBUG_VOL) Log.i(TAG, "Need to adjust remote volume: calling adjustRemoteVolume()"); - adjustRemoteVolume(AudioSystem.STREAM_MUSIC, direction, flags); + mMediaFocusControl.adjustRemoteVolume(AudioSystem.STREAM_MUSIC, direction, flags); } else { - adjustStreamVolume(streamType, direction, flags); + adjustStreamVolume(streamType, direction, flags, callingPackage); } } /** @see AudioManager#adjustStreamVolume(int, int, int) */ - public void adjustStreamVolume(int streamType, int direction, int flags) { + public void adjustStreamVolume(int streamType, int direction, int flags, + String callingPackage) { if (mUseFixedVolume) { return; } @@ -852,6 +842,18 @@ public class AudioService extends IAudioService.Stub implements OnFinished { boolean adjustVolume = true; int step; + // skip a2dp absolute volume control request when the device + // is not an a2dp device + if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) == 0 && + (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) != 0) { + return; + } + + if (mAppOps.noteOp(STEAM_VOLUME_OPS[streamTypeAlias], Binder.getCallingUid(), + callingPackage) != AppOpsManager.MODE_ALLOWED) { + return; + } + // reset any pending volume command synchronized (mSafeMediaVolumeState) { mPendingVolumeCommand = null; @@ -896,6 +898,18 @@ public class AudioService extends IAudioService.Stub implements OnFinished { int oldIndex = mStreamStates[streamType].getIndex(device); if (adjustVolume && (direction != AudioManager.ADJUST_SAME)) { + + // Check if volume update should be send to AVRCP + if (streamTypeAlias == AudioSystem.STREAM_MUSIC && + (device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && + (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) { + synchronized (mA2dpAvrcpLock) { + if (mA2dp != null && mAvrcpAbsVolSupported) { + mA2dp.adjustAvrcpAbsoluteVolume(direction); + } + } + } + if ((direction == AudioManager.ADJUST_RAISE) && !checkSafeMediaVolume(streamTypeAlias, aliasIndex + step, device)) { Log.e(TAG, "adjustStreamVolume() safe volume index = "+oldIndex); @@ -917,7 +931,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { } /** @see AudioManager#adjustMasterVolume(int, int) */ - public void adjustMasterVolume(int steps, int flags) { + public void adjustMasterVolume(int steps, int flags, String callingPackage) { if (mUseFixedVolume) { return; } @@ -932,7 +946,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { } //Log.d(TAG, "adjustMasterVolume volume: " + volume + " steps: " + steps); - setMasterVolume(volume, flags); + setMasterVolume(volume, flags, callingPackage); } // StreamVolumeCommand contains the information needed to defer the process of @@ -968,27 +982,50 @@ public class AudioService extends IAudioService.Stub implements OnFinished { } /** @see AudioManager#setStreamVolume(int, int, int) */ - public void setStreamVolume(int streamType, int index, int flags) { + public void setStreamVolume(int streamType, int index, int flags, String callingPackage) { if (mUseFixedVolume) { return; } ensureValidStreamType(streamType); - VolumeStreamState streamState = mStreamStates[mStreamVolumeAlias[streamType]]; + int streamTypeAlias = mStreamVolumeAlias[streamType]; + VolumeStreamState streamState = mStreamStates[streamTypeAlias]; final int device = getDeviceForStream(streamType); int oldIndex; + // skip a2dp absolute volume control request when the device + // is not an a2dp device + if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) == 0 && + (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) != 0) { + return; + } + + if (mAppOps.noteOp(STEAM_VOLUME_OPS[streamTypeAlias], Binder.getCallingUid(), + callingPackage) != AppOpsManager.MODE_ALLOWED) { + return; + } + synchronized (mSafeMediaVolumeState) { // reset any pending volume command mPendingVolumeCommand = null; oldIndex = streamState.getIndex(device); - index = rescaleIndex(index * 10, streamType, mStreamVolumeAlias[streamType]); + index = rescaleIndex(index * 10, streamType, streamTypeAlias); + + if (streamTypeAlias == AudioSystem.STREAM_MUSIC && + (device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && + (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) { + synchronized (mA2dpAvrcpLock) { + if (mA2dp != null && mAvrcpAbsVolSupported) { + mA2dp.setAvrcpAbsoluteVolume(index); + } + } + } flags &= ~AudioManager.FLAG_FIXED_VOLUME; - if ((mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC) && + if ((streamTypeAlias == AudioSystem.STREAM_MUSIC) && ((device & mFixedVolumeDevices) != 0)) { flags |= AudioManager.FLAG_FIXED_VOLUME; @@ -1003,7 +1040,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { } } - if (!checkSafeMediaVolume(mStreamVolumeAlias[streamType], index, device)) { + if (!checkSafeMediaVolume(streamTypeAlias, index, device)) { mVolumePanel.postDisplaySafeVolumeWarning(flags); mPendingVolumeCommand = new StreamVolumeCommand( streamType, index, flags, device); @@ -1239,6 +1276,10 @@ public class AudioService extends IAudioService.Stub implements OnFinished { return AudioSystem.getMasterMute(); } + protected static int getMaxStreamVolume(int streamType) { + return MAX_STREAM_VOLUME[streamType]; + } + /** @see AudioManager#getStreamVolume(int) */ public int getStreamVolume(int streamType) { ensureValidStreamType(streamType); @@ -1261,11 +1302,16 @@ public class AudioService extends IAudioService.Stub implements OnFinished { return getLastAudibleMasterVolume(); } - public void setMasterVolume(int volume, int flags) { + public void setMasterVolume(int volume, int flags, String callingPackage) { if (mUseFixedVolume) { return; } + if (mAppOps.noteOp(AppOpsManager.OP_AUDIO_MASTER_VOLUME, Binder.getCallingUid(), + callingPackage) != AppOpsManager.MODE_ALLOWED) { + return; + } + if (volume < 0) { volume = 0; } else if (volume > MAX_MASTER_VOLUME) { @@ -1883,7 +1929,16 @@ public class AudioService extends IAudioService.Stub implements OnFinished { if (!checkAudioSettingsPermission("setSpeakerphoneOn()")) { return; } - mForcedUseForComm = on ? AudioSystem.FORCE_SPEAKER : AudioSystem.FORCE_NONE; + + if (on) { + if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) { + sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE, + AudioSystem.FOR_RECORD, AudioSystem.FORCE_NONE, null, 0); + } + mForcedUseForComm = AudioSystem.FORCE_SPEAKER; + } else if (mForcedUseForComm == AudioSystem.FORCE_SPEAKER){ + mForcedUseForComm = AudioSystem.FORCE_NONE; + } sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE, AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, null, 0); @@ -1899,7 +1954,12 @@ public class AudioService extends IAudioService.Stub implements OnFinished { if (!checkAudioSettingsPermission("setBluetoothScoOn()")) { return; } - mForcedUseForComm = on ? AudioSystem.FORCE_BT_SCO : AudioSystem.FORCE_NONE; + + if (on) { + mForcedUseForComm = AudioSystem.FORCE_BT_SCO; + } else if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) { + mForcedUseForComm = AudioSystem.FORCE_NONE; + } sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE, AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, null, 0); @@ -2248,21 +2308,23 @@ public class AudioService extends IAudioService.Stub implements OnFinished { List<BluetoothDevice> deviceList; switch(profile) { case BluetoothProfile.A2DP: - BluetoothA2dp a2dp = (BluetoothA2dp) proxy; - deviceList = a2dp.getConnectedDevices(); - if (deviceList.size() > 0) { - btDevice = deviceList.get(0); - synchronized (mConnectedDevices) { - int state = a2dp.getConnectionState(btDevice); - int delay = checkSendBecomingNoisyIntent( - AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, - (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0); - queueMsgUnderWakeLock(mAudioHandler, - MSG_SET_A2DP_CONNECTION_STATE, - state, - 0, - btDevice, - delay); + synchronized (mA2dpAvrcpLock) { + mA2dp = (BluetoothA2dp) proxy; + deviceList = mA2dp.getConnectedDevices(); + if (deviceList.size() > 0) { + btDevice = deviceList.get(0); + synchronized (mConnectedDevices) { + int state = mA2dp.getConnectionState(btDevice); + int delay = checkSendBecomingNoisyIntent( + AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, + (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0); + queueMsgUnderWakeLock(mAudioHandler, + MSG_SET_A2DP_CONNECTION_STATE, + state, + 0, + btDevice, + delay); + } } } break; @@ -2324,10 +2386,13 @@ public class AudioService extends IAudioService.Stub implements OnFinished { public void onServiceDisconnected(int profile) { switch(profile) { case BluetoothProfile.A2DP: - synchronized (mConnectedDevices) { - if (mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP)) { - makeA2dpDeviceUnavailableNow( - mConnectedDevices.get(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP)); + synchronized (mA2dpAvrcpLock) { + mA2dp = null; + synchronized (mConnectedDevices) { + if (mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP)) { + makeA2dpDeviceUnavailableNow( + mConnectedDevices.get(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP)); + } } } break; @@ -2344,26 +2409,6 @@ public class AudioService extends IAudioService.Stub implements OnFinished { } }; - /** see AudioManager.setRemoteSubmixOn(boolean on) */ - public void setRemoteSubmixOn(boolean on, int address) { - sendMsg(mAudioHandler, MSG_SET_RSX_CONNECTION_STATE, - SENDMSG_REPLACE /* replace with QUEUE when multiple addresses are supported */, - on ? 1 : 0 /*arg1*/, - address /*arg2*/, - null/*obj*/, 0/*delay*/); - } - - private void onSetRsxConnectionState(int available, int address) { - AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_REMOTE_SUBMIX, - available == 1 ? - AudioSystem.DEVICE_STATE_AVAILABLE : AudioSystem.DEVICE_STATE_UNAVAILABLE, - String.valueOf(address) /*device_address*/); - AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_REMOTE_SUBMIX, - available == 1 ? - AudioSystem.DEVICE_STATE_AVAILABLE : AudioSystem.DEVICE_STATE_UNAVAILABLE, - String.valueOf(address) /*device_address*/); - } - private void onCheckMusicActive() { synchronized (mSafeMediaVolumeState) { if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_INACTIVE) { @@ -2507,6 +2552,50 @@ public class AudioService extends IAudioService.Stub implements OnFinished { return (mRingerModeMutedStreams & (1 << streamType)) != 0; } + boolean updateRingerModeAffectedStreams() { + int ringerModeAffectedStreams; + // make sure settings for ringer mode are consistent with device type: non voice capable + // devices (tablets) include media stream in silent mode whereas phones don't. + ringerModeAffectedStreams = Settings.System.getIntForUser(mContentResolver, + Settings.System.MODE_RINGER_STREAMS_AFFECTED, + ((1 << AudioSystem.STREAM_RING)|(1 << AudioSystem.STREAM_NOTIFICATION)| + (1 << AudioSystem.STREAM_SYSTEM)|(1 << AudioSystem.STREAM_SYSTEM_ENFORCED)), + UserHandle.USER_CURRENT); + + // ringtone, notification and system streams are always affected by ringer mode + ringerModeAffectedStreams |= (1 << AudioSystem.STREAM_RING)| + (1 << AudioSystem.STREAM_NOTIFICATION)| + (1 << AudioSystem.STREAM_SYSTEM); + + if (mVoiceCapable) { + ringerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_MUSIC); + } else { + ringerModeAffectedStreams |= (1 << AudioSystem.STREAM_MUSIC); + } + synchronized (mCameraSoundForced) { + if (mCameraSoundForced) { + ringerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_SYSTEM_ENFORCED); + } else { + ringerModeAffectedStreams |= (1 << AudioSystem.STREAM_SYSTEM_ENFORCED); + } + } + if (mStreamVolumeAlias[AudioSystem.STREAM_DTMF] == AudioSystem.STREAM_RING) { + ringerModeAffectedStreams |= (1 << AudioSystem.STREAM_DTMF); + } else { + ringerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_DTMF); + } + + if (ringerModeAffectedStreams != mRingerModeAffectedStreams) { + Settings.System.putIntForUser(mContentResolver, + Settings.System.MODE_RINGER_STREAMS_AFFECTED, + ringerModeAffectedStreams, + UserHandle.USER_CURRENT); + mRingerModeAffectedStreams = ringerModeAffectedStreams; + return true; + } + return false; + } + public boolean isStreamAffectedByMute(int streamType) { return (mMuteAffectedStreams & (1 << streamType)) != 0; } @@ -2543,6 +2632,17 @@ public class AudioService extends IAudioService.Stub implements OnFinished { return (isOffhook || getMode() == AudioManager.MODE_IN_COMMUNICATION); } + /** + * For code clarity for getActiveStreamType(int) + * @param delay_ms max time since last STREAM_MUSIC activity to consider + * @return true if STREAM_MUSIC is active in streams handled by AudioFlinger now or + * in the last "delay_ms" ms. + */ + private boolean isAfMusicActiveRecently(int delay_ms) { + return AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, delay_ms) + || AudioSystem.isStreamActiveRemotely(AudioSystem.STREAM_MUSIC, delay_ms); + } + private int getActiveStreamType(int suggestedStreamType) { if (mVoiceCapable) { if (isInCommunication()) { @@ -2555,23 +2655,22 @@ public class AudioService extends IAudioService.Stub implements OnFinished { return AudioSystem.STREAM_VOICE_CALL; } } else if (suggestedStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) { - // Having the suggested stream be USE_DEFAULT_STREAM_TYPE is how remote control - // volume can have priority over STREAM_MUSIC - if (checkUpdateRemoteStateIfActive(AudioSystem.STREAM_MUSIC)) { - if (DEBUG_VOL) - Log.v(TAG, "getActiveStreamType: Forcing STREAM_REMOTE_MUSIC"); - return STREAM_REMOTE_MUSIC; - } else if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, - DEFAULT_STREAM_TYPE_OVERRIDE_DELAY_MS)) { + if (isAfMusicActiveRecently(DEFAULT_STREAM_TYPE_OVERRIDE_DELAY_MS)) { if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_MUSIC stream active"); return AudioSystem.STREAM_MUSIC; - } else { - if (DEBUG_VOL) - Log.v(TAG, "getActiveStreamType: Forcing STREAM_RING b/c default"); - return AudioSystem.STREAM_RING; + } else + if (mMediaFocusControl.checkUpdateRemoteStateIfActive(AudioSystem.STREAM_MUSIC)) + { + if (DEBUG_VOL) + Log.v(TAG, "getActiveStreamType: Forcing STREAM_REMOTE_MUSIC"); + return STREAM_REMOTE_MUSIC; + } else { + if (DEBUG_VOL) + Log.v(TAG, "getActiveStreamType: Forcing STREAM_RING b/c default"); + return AudioSystem.STREAM_RING; } - } else if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0)) { + } else if (isAfMusicActiveRecently(0)) { if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_MUSIC stream active"); return AudioSystem.STREAM_MUSIC; @@ -2597,14 +2696,17 @@ public class AudioService extends IAudioService.Stub implements OnFinished { if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_NOTIFICATION"); return AudioSystem.STREAM_NOTIFICATION; } else if (suggestedStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) { - if (checkUpdateRemoteStateIfActive(AudioSystem.STREAM_MUSIC)) { - // Having the suggested stream be USE_DEFAULT_STREAM_TYPE is how remote control - // volume can have priority over STREAM_MUSIC - if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_REMOTE_MUSIC"); - return STREAM_REMOTE_MUSIC; + if (isAfMusicActiveRecently(DEFAULT_STREAM_TYPE_OVERRIDE_DELAY_MS)) { + if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: forcing STREAM_MUSIC"); + return AudioSystem.STREAM_MUSIC; + } else + if (mMediaFocusControl.checkUpdateRemoteStateIfActive(AudioSystem.STREAM_MUSIC)) + { + if (DEBUG_VOL) + Log.v(TAG, "getActiveStreamType: Forcing STREAM_REMOTE_MUSIC"); + return STREAM_REMOTE_MUSIC; } else { - if (DEBUG_VOL) - Log.v(TAG, "getActiveStreamType: using STREAM_MUSIC as default"); + if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: using STREAM_MUSIC as default"); return AudioSystem.STREAM_MUSIC; } } else { @@ -2641,7 +2743,11 @@ public class AudioService extends IAudioService.Stub implements OnFinished { */ private void queueMsgUnderWakeLock(Handler handler, int msg, int arg1, int arg2, Object obj, int delay) { - mMediaEventWakeLock.acquire(); + final long ident = Binder.clearCallingIdentity(); + // Always acquire the wake lock as AudioService because it is released by the + // message handler. + mAudioEventWakeLock.acquire(); + Binder.restoreCallingIdentity(ident); sendMsg(handler, msg, SENDMSG_QUEUE, arg1, arg2, obj, delay); } @@ -2807,7 +2913,12 @@ public class AudioService extends IAudioService.Stub implements OnFinished { int index; if (isMuted()) { index = 0; - } else { + } else if (mStreamVolumeAlias[mStreamType] == AudioSystem.STREAM_MUSIC && + (device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && + mAvrcpAbsVolSupported) { + index = (mIndexMax + 5)/10; + } + else { index = (getIndex(device) + 5)/10; } AudioSystem.setStreamVolumeIndex(mStreamType, index, device); @@ -2893,13 +3004,25 @@ public class AudioService extends IAudioService.Stub implements OnFinished { } public synchronized void setAllIndexes(VolumeStreamState srcStream) { - Set set = srcStream.mIndex.entrySet(); + int srcStreamType = srcStream.getStreamType(); + // apply default device volume from source stream to all devices first in case + // some devices are present in this stream state but not in source stream state + int index = srcStream.getIndex(AudioSystem.DEVICE_OUT_DEFAULT); + index = rescaleIndex(index, srcStreamType, mStreamType); + Set set = mIndex.entrySet(); Iterator i = set.iterator(); while (i.hasNext()) { Map.Entry entry = (Map.Entry)i.next(); + entry.setValue(index); + } + // Now apply actual volume for devices in source stream state + set = srcStream.mIndex.entrySet(); + i = set.iterator(); + while (i.hasNext()) { + Map.Entry entry = (Map.Entry)i.next(); int device = ((Integer)entry.getKey()).intValue(); - int index = ((Integer)entry.getValue()).intValue(); - index = rescaleIndex(index, srcStream.getStreamType(), mStreamType); + index = ((Integer)entry.getValue()).intValue(); + index = rescaleIndex(index, srcStreamType, mStreamType); setIndex(index, device); } @@ -3339,13 +3462,6 @@ public class AudioService extends IAudioService.Stub implements OnFinished { } } - private void onHandlePersistMediaButtonReceiver(ComponentName receiver) { - Settings.System.putStringForUser(mContentResolver, - Settings.System.MEDIA_BUTTON_RECEIVER, - receiver == null ? "" : receiver.flattenToString(), - UserHandle.USER_CURRENT); - } - private void cleanupPlayer(MediaPlayer mp) { if (mp != null) { try { @@ -3411,24 +3527,22 @@ public class AudioService extends IAudioService.Stub implements OnFinished { break; case MSG_MEDIA_SERVER_DIED: - if (!mMediaServerOk) { + if (AudioSystem.checkAudioFlinger() != AudioSystem.AUDIO_STATUS_OK) { Log.e(TAG, "Media server died."); - // Force creation of new IAudioFlinger interface so that we are notified - // when new media_server process is back to life. - AudioSystem.setErrorCallback(mAudioSystemCallback); sendMsg(mAudioHandler, MSG_MEDIA_SERVER_DIED, SENDMSG_NOOP, 0, 0, null, 500); + break; } - break; - - case MSG_MEDIA_SERVER_STARTED: Log.e(TAG, "Media server started."); + // indicate to audio HAL that we start the reconfiguration phase after a media // server crash - // Note that MSG_MEDIA_SERVER_STARTED message is only received when the media server + // Note that we only execute this when the media server // process restarts after a crash, not the first time it is started. AudioSystem.setParameters("restarting=true"); + readAndSetLowRamDevice(); + // Restore device connection states synchronized (mConnectedDevices) { Set set = mConnectedDevices.entrySet(); @@ -3522,31 +3636,18 @@ public class AudioService extends IAudioService.Stub implements OnFinished { setForceUse(msg.arg1, msg.arg2); break; - case MSG_PERSIST_MEDIABUTTONRECEIVER: - onHandlePersistMediaButtonReceiver( (ComponentName) msg.obj ); - break; - - case MSG_RCDISPLAY_CLEAR: - onRcDisplayClear(); - break; - - case MSG_RCDISPLAY_UPDATE: - // msg.obj is guaranteed to be non null - onRcDisplayUpdate( (RemoteControlStackEntry) msg.obj, msg.arg1); - break; - case MSG_BT_HEADSET_CNCT_FAILED: resetBluetoothSco(); break; case MSG_SET_WIRED_DEVICE_CONNECTION_STATE: onSetWiredDeviceConnectionState(msg.arg1, msg.arg2, (String)msg.obj); - mMediaEventWakeLock.release(); + mAudioEventWakeLock.release(); break; case MSG_SET_A2DP_CONNECTION_STATE: onSetA2dpConnectionState((BluetoothDevice)msg.obj, msg.arg1); - mMediaEventWakeLock.release(); + mAudioEventWakeLock.release(); break; case MSG_REPORT_NEW_ROUTES: { @@ -3569,30 +3670,6 @@ public class AudioService extends IAudioService.Stub implements OnFinished { break; } - case MSG_REEVALUATE_REMOTE: - onReevaluateRemote(); - break; - - case MSG_RCC_NEW_PLAYBACK_INFO: - onNewPlaybackInfoForRcc(msg.arg1 /* rccId */, msg.arg2 /* key */, - ((Integer)msg.obj).intValue() /* value */); - break; - case MSG_RCC_NEW_VOLUME_OBS: - onRegisterVolumeObserverForRcc(msg.arg1 /* rccId */, - (IRemoteVolumeObserver)msg.obj /* rvo */); - break; - case MSG_RCC_NEW_PLAYBACK_STATE: - onNewPlaybackStateForRcc(msg.arg1 /* rccId */, msg.arg2 /* state */, - (RccPlaybackState)msg.obj /* newState */); - break; - case MSG_RCC_SEEK_REQUEST: - onSetRemoteControlClientPlaybackPosition(msg.arg1 /* generationId */, - ((Long)msg.obj).longValue() /* timeMs */); - - case MSG_SET_RSX_CONNECTION_STATE: - onSetRsxConnectionState(msg.arg1/*available*/, msg.arg2/*address*/); - break; - case MSG_CHECK_MUSIC_ACTIVE: onCheckMusicActive(); break; @@ -3609,10 +3686,6 @@ public class AudioService extends IAudioService.Stub implements OnFinished { onPersistSafeVolumeState(msg.arg1); break; - case MSG_PROMOTE_RCC: - onPromoteRcc(msg.arg1); - break; - case MSG_BROADCAST_BT_CONNECTION_STATE: onBroadcastScoConnectionState(msg.arg1); break; @@ -3638,29 +3711,11 @@ public class AudioService extends IAudioService.Stub implements OnFinished { // and mRingerModeAffectedStreams, so will leave this synchronized for now. // mRingerModeMutedStreams and mMuteAffectedStreams are safe (only accessed once). synchronized (mSettingsLock) { - int ringerModeAffectedStreams = Settings.System.getIntForUser(mContentResolver, - Settings.System.MODE_RINGER_STREAMS_AFFECTED, - ((1 << AudioSystem.STREAM_RING)|(1 << AudioSystem.STREAM_NOTIFICATION)| - (1 << AudioSystem.STREAM_SYSTEM)|(1 << AudioSystem.STREAM_SYSTEM_ENFORCED)), - UserHandle.USER_CURRENT); - if (mVoiceCapable) { - ringerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_MUSIC); - } else { - ringerModeAffectedStreams |= (1 << AudioSystem.STREAM_MUSIC); - } - synchronized (mCameraSoundForced) { - if (mCameraSoundForced) { - ringerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_SYSTEM_ENFORCED); - } else { - ringerModeAffectedStreams |= (1 << AudioSystem.STREAM_SYSTEM_ENFORCED); - } - } - if (ringerModeAffectedStreams != mRingerModeAffectedStreams) { + if (updateRingerModeAffectedStreams()) { /* * Ensure all stream types that should be affected by ringer mode * are in the proper state. */ - mRingerModeAffectedStreams = ringerModeAffectedStreams; setRingerModeInt(getRingerMode(), false); } readDockAudioSettings(mContentResolver); @@ -3672,6 +3727,9 @@ public class AudioService extends IAudioService.Stub implements OnFinished { private void makeA2dpDeviceAvailable(String address) { // enable A2DP before notifying A2DP connection to avoid unecessary processing in // audio policy manager + VolumeStreamState streamState = mStreamStates[AudioSystem.STREAM_MUSIC]; + sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE, + AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 0, streamState, 0); setBluetoothA2dpOnInt(true); AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, AudioSystem.DEVICE_STATE_AVAILABLE, @@ -3688,6 +3746,9 @@ public class AudioService extends IAudioService.Stub implements OnFinished { // must be called synchronized on mConnectedDevices private void makeA2dpDeviceUnavailableNow(String address) { + synchronized (mA2dpAvrcpLock) { + mAvrcpAbsVolSupported = false; + } AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, AudioSystem.DEVICE_STATE_UNAVAILABLE, address); @@ -3719,6 +3780,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { private void onSetA2dpConnectionState(BluetoothDevice btDevice, int state) { + if (DEBUG_VOL) Log.d(TAG, "onSetA2dpConnectionState btDevice="+btDevice+" state="+state); if (btDevice == null) { return; } @@ -3726,6 +3788,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { if (!BluetoothAdapter.checkBluetoothAddress(address)) { address = ""; } + synchronized (mConnectedDevices) { boolean isConnected = (mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) && @@ -3776,6 +3839,16 @@ public class AudioService extends IAudioService.Stub implements OnFinished { } } + public void avrcpSupportsAbsoluteVolume(String address, boolean support) { + // address is not used for now, but may be used when multiple a2dp devices are supported + synchronized (mA2dpAvrcpLock) { + mAvrcpAbsVolSupported = support; + VolumeStreamState streamState = mStreamStates[AudioSystem.STREAM_MUSIC]; + sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE, + AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 0, streamState, 0); + } + } + private boolean handleDeviceConnection(boolean connected, int device, String params) { synchronized (mConnectedDevices) { boolean isConnected = (mConnectedDevices.containsKey(device) && @@ -4090,21 +4163,6 @@ public class AudioService extends IAudioService.Stub implements OnFinished { 0, null, SAFE_VOLUME_CONFIGURE_TIMEOUT_MS); - } else if (action.equals(Intent.ACTION_PACKAGE_REMOVED) - || action.equals(Intent.ACTION_PACKAGE_DATA_CLEARED)) { - if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { - // a package is being removed, not replaced - String packageName = intent.getData().getSchemeSpecificPart(); - if (packageName != null) { - cleanupMediaButtonReceiverForPackage(packageName, true); - } - } - } else if (action.equals(Intent.ACTION_PACKAGE_ADDED) - || action.equals(Intent.ACTION_PACKAGE_CHANGED)) { - String packageName = intent.getData().getSchemeSpecificPart(); - if (packageName != null) { - cleanupMediaButtonReceiverForPackage(packageName, false); - } } else if (action.equals(Intent.ACTION_SCREEN_ON)) { AudioSystem.setParameters("screen_state=on"); } else if (action.equals(Intent.ACTION_SCREEN_OFF)) { @@ -4121,7 +4179,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { null, 0); // the current audio focus owner is no longer valid - discardAudioFocusOwner(); + mMediaFocusControl.discardAudioFocusOwner(); // load volume settings for new user readAudioSettings(true /*userSwitch*/); @@ -4137,2204 +4195,115 @@ public class AudioService extends IAudioService.Stub implements OnFinished { } //========================================================================================== - // AudioFocus - //========================================================================================== - - /* constant to identify focus stack entry that is used to hold the focus while the phone - * is ringing or during a call. Used by com.android.internal.telephony.CallManager when - * entering and exiting calls. - */ - public final static String IN_VOICE_COMM_FOCUS_ID = "AudioFocus_For_Phone_Ring_And_Calls"; - - private final static Object mAudioFocusLock = new Object(); - - private final static Object mRingingLock = new Object(); - - private PhoneStateListener mPhoneStateListener = new PhoneStateListener() { - @Override - public void onCallStateChanged(int state, String incomingNumber) { - if (state == TelephonyManager.CALL_STATE_RINGING) { - //Log.v(TAG, " CALL_STATE_RINGING"); - synchronized(mRingingLock) { - mIsRinging = true; - } - } else if ((state == TelephonyManager.CALL_STATE_OFFHOOK) - || (state == TelephonyManager.CALL_STATE_IDLE)) { - synchronized(mRingingLock) { - mIsRinging = false; - } - } - } - }; - - /** - * Discard the current audio focus owner. - * Notify top of audio focus stack that it lost focus (regardless of possibility to reassign - * focus), remove it from the stack, and clear the remote control display. - */ - private void discardAudioFocusOwner() { - synchronized(mAudioFocusLock) { - if (!mFocusStack.empty() && (mFocusStack.peek().mFocusDispatcher != null)) { - // notify the current focus owner it lost focus after removing it from stack - FocusStackEntry focusOwner = mFocusStack.pop(); - try { - focusOwner.mFocusDispatcher.dispatchAudioFocusChange( - AudioManager.AUDIOFOCUS_LOSS, focusOwner.mClientId); - } catch (RemoteException e) { - Log.e(TAG, "Failure to signal loss of audio focus due to "+ e); - e.printStackTrace(); - } - focusOwner.unlinkToDeath(); - // clear RCD - synchronized(mRCStack) { - clearRemoteControlDisplay_syncAfRcs(); - } - } - } - } - - private void notifyTopOfAudioFocusStack() { - // notify the top of the stack it gained focus - if (!mFocusStack.empty() && (mFocusStack.peek().mFocusDispatcher != null)) { - if (canReassignAudioFocus()) { - try { - mFocusStack.peek().mFocusDispatcher.dispatchAudioFocusChange( - AudioManager.AUDIOFOCUS_GAIN, mFocusStack.peek().mClientId); - } catch (RemoteException e) { - Log.e(TAG, "Failure to signal gain of audio control focus due to "+ e); - e.printStackTrace(); - } - } - } - } - - private static class FocusStackEntry { - public int mStreamType = -1;// no stream type - public IAudioFocusDispatcher mFocusDispatcher = null; - public IBinder mSourceRef = null; - public String mClientId; - public int mFocusChangeType; - public AudioFocusDeathHandler mHandler; - public String mPackageName; - public int mCallingUid; - - public FocusStackEntry() { - } - - public FocusStackEntry(int streamType, int duration, - IAudioFocusDispatcher afl, IBinder source, String id, AudioFocusDeathHandler hdlr, - String pn, int uid) { - mStreamType = streamType; - mFocusDispatcher = afl; - mSourceRef = source; - mClientId = id; - mFocusChangeType = duration; - mHandler = hdlr; - mPackageName = pn; - mCallingUid = uid; - } - - public void unlinkToDeath() { - try { - if (mSourceRef != null && mHandler != null) { - mSourceRef.unlinkToDeath(mHandler, 0); - mHandler = null; - } - } catch (java.util.NoSuchElementException e) { - Log.e(TAG, "Encountered " + e + " in FocusStackEntry.unlinkToDeath()"); - } - } - - @Override - protected void finalize() throws Throwable { - unlinkToDeath(); // unlink exception handled inside method - super.finalize(); - } - } - - private final Stack<FocusStackEntry> mFocusStack = new Stack<FocusStackEntry>(); - - /** - * Helper function: - * Display in the log the current entries in the audio focus stack - */ - private void dumpFocusStack(PrintWriter pw) { - pw.println("\nAudio Focus stack entries (last is top of stack):"); - synchronized(mAudioFocusLock) { - Iterator<FocusStackEntry> stackIterator = mFocusStack.iterator(); - while(stackIterator.hasNext()) { - FocusStackEntry fse = stackIterator.next(); - pw.println(" source:" + fse.mSourceRef - + " -- pack: " + fse.mPackageName - + " -- client: " + fse.mClientId - + " -- duration: " + fse.mFocusChangeType - + " -- uid: " + fse.mCallingUid - + " -- stream: " + fse.mStreamType); - } - } - } - - /** - * Helper function: - * Called synchronized on mAudioFocusLock - * Remove a focus listener from the focus stack. - * @param clientToRemove the focus listener - * @param signal if true and the listener was at the top of the focus stack, i.e. it was holding - * focus, notify the next item in the stack it gained focus. - */ - private void removeFocusStackEntry(String clientToRemove, boolean signal) { - // is the current top of the focus stack abandoning focus? (because of request, not death) - if (!mFocusStack.empty() && mFocusStack.peek().mClientId.equals(clientToRemove)) - { - //Log.i(TAG, " removeFocusStackEntry() removing top of stack"); - FocusStackEntry fse = mFocusStack.pop(); - fse.unlinkToDeath(); - if (signal) { - // notify the new top of the stack it gained focus - notifyTopOfAudioFocusStack(); - // there's a new top of the stack, let the remote control know - synchronized(mRCStack) { - checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL); - } - } - } else { - // focus is abandoned by a client that's not at the top of the stack, - // no need to update focus. - // (using an iterator on the stack so we can safely remove an entry after having - // evaluated it, traversal order doesn't matter here) - Iterator<FocusStackEntry> stackIterator = mFocusStack.iterator(); - while(stackIterator.hasNext()) { - FocusStackEntry fse = (FocusStackEntry)stackIterator.next(); - if(fse.mClientId.equals(clientToRemove)) { - Log.i(TAG, " AudioFocus abandonAudioFocus(): removing entry for " - + fse.mClientId); - stackIterator.remove(); - fse.unlinkToDeath(); - } - } - } - } - - /** - * Helper function: - * Called synchronized on mAudioFocusLock - * Remove focus listeners from the focus stack for a particular client when it has died. - */ - private void removeFocusStackEntryForClient(IBinder cb) { - // is the owner of the audio focus part of the client to remove? - boolean isTopOfStackForClientToRemove = !mFocusStack.isEmpty() && - mFocusStack.peek().mSourceRef.equals(cb); - // (using an iterator on the stack so we can safely remove an entry after having - // evaluated it, traversal order doesn't matter here) - Iterator<FocusStackEntry> stackIterator = mFocusStack.iterator(); - while(stackIterator.hasNext()) { - FocusStackEntry fse = (FocusStackEntry)stackIterator.next(); - if(fse.mSourceRef.equals(cb)) { - Log.i(TAG, " AudioFocus abandonAudioFocus(): removing entry for " - + fse.mClientId); - stackIterator.remove(); - // the client just died, no need to unlink to its death - } - } - if (isTopOfStackForClientToRemove) { - // we removed an entry at the top of the stack: - // notify the new top of the stack it gained focus. - notifyTopOfAudioFocusStack(); - // there's a new top of the stack, let the remote control know - synchronized(mRCStack) { - checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL); - } - } - } - - /** - * Helper function: - * Returns true if the system is in a state where the focus can be reevaluated, false otherwise. - */ - private boolean canReassignAudioFocus() { - // focus requests are rejected during a phone call or when the phone is ringing - // this is equivalent to IN_VOICE_COMM_FOCUS_ID having the focus - if (!mFocusStack.isEmpty() && IN_VOICE_COMM_FOCUS_ID.equals(mFocusStack.peek().mClientId)) { - return false; - } - return true; - } - - /** - * Inner class to monitor audio focus client deaths, and remove them from the audio focus - * stack if necessary. - */ - private class AudioFocusDeathHandler implements IBinder.DeathRecipient { - private IBinder mCb; // To be notified of client's death - - AudioFocusDeathHandler(IBinder cb) { - mCb = cb; - } - - public void binderDied() { - synchronized(mAudioFocusLock) { - Log.w(TAG, " AudioFocus audio focus client died"); - removeFocusStackEntryForClient(mCb); - } - } - - public IBinder getBinder() { - return mCb; - } - } - - - /** @see AudioManager#requestAudioFocus(AudioManager.OnAudioFocusChangeListener, int, int) */ - public int requestAudioFocus(int mainStreamType, int focusChangeHint, IBinder cb, - IAudioFocusDispatcher fd, String clientId, String callingPackageName) { - Log.i(TAG, " AudioFocus requestAudioFocus() from " + clientId); - // the main stream type for the audio focus request is currently not used. It may - // potentially be used to handle multiple stream type-dependent audio focuses. - - // we need a valid binder callback for clients - if (!cb.pingBinder()) { - Log.e(TAG, " AudioFocus DOA client for requestAudioFocus(), aborting."); - return AudioManager.AUDIOFOCUS_REQUEST_FAILED; - } - - synchronized(mAudioFocusLock) { - if (!canReassignAudioFocus()) { - return AudioManager.AUDIOFOCUS_REQUEST_FAILED; - } - - // handle the potential premature death of the new holder of the focus - // (premature death == death before abandoning focus) - // Register for client death notification - AudioFocusDeathHandler afdh = new AudioFocusDeathHandler(cb); - try { - cb.linkToDeath(afdh, 0); - } catch (RemoteException e) { - // client has already died! - Log.w(TAG, "AudioFocus requestAudioFocus() could not link to "+cb+" binder death"); - return AudioManager.AUDIOFOCUS_REQUEST_FAILED; - } - - if (!mFocusStack.empty() && mFocusStack.peek().mClientId.equals(clientId)) { - // if focus is already owned by this client and the reason for acquiring the focus - // hasn't changed, don't do anything - if (mFocusStack.peek().mFocusChangeType == focusChangeHint) { - // unlink death handler so it can be gc'ed. - // linkToDeath() creates a JNI global reference preventing collection. - cb.unlinkToDeath(afdh, 0); - return AudioManager.AUDIOFOCUS_REQUEST_GRANTED; - } - // the reason for the audio focus request has changed: remove the current top of - // stack and respond as if we had a new focus owner - FocusStackEntry fse = mFocusStack.pop(); - fse.unlinkToDeath(); - } - - // notify current top of stack it is losing focus - if (!mFocusStack.empty() && (mFocusStack.peek().mFocusDispatcher != null)) { - try { - mFocusStack.peek().mFocusDispatcher.dispatchAudioFocusChange( - -1 * focusChangeHint, // loss and gain codes are inverse of each other - mFocusStack.peek().mClientId); - } catch (RemoteException e) { - Log.e(TAG, " Failure to signal loss of focus due to "+ e); - e.printStackTrace(); - } - } - - // focus requester might already be somewhere below in the stack, remove it - removeFocusStackEntry(clientId, false /* signal */); - - // push focus requester at the top of the audio focus stack - mFocusStack.push(new FocusStackEntry(mainStreamType, focusChangeHint, fd, cb, - clientId, afdh, callingPackageName, Binder.getCallingUid())); - - // there's a new top of the stack, let the remote control know - synchronized(mRCStack) { - checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL); - } - }//synchronized(mAudioFocusLock) - - return AudioManager.AUDIOFOCUS_REQUEST_GRANTED; - } - - /** @see AudioManager#abandonAudioFocus(AudioManager.OnAudioFocusChangeListener) */ - public int abandonAudioFocus(IAudioFocusDispatcher fl, String clientId) { - Log.i(TAG, " AudioFocus abandonAudioFocus() from " + clientId); - try { - // this will take care of notifying the new focus owner if needed - synchronized(mAudioFocusLock) { - removeFocusStackEntry(clientId, true); - } - } catch (java.util.ConcurrentModificationException cme) { - // Catching this exception here is temporary. It is here just to prevent - // a crash seen when the "Silent" notification is played. This is believed to be fixed - // but this try catch block is left just to be safe. - Log.e(TAG, "FATAL EXCEPTION AudioFocus abandonAudioFocus() caused " + cme); - cme.printStackTrace(); - } - - return AudioManager.AUDIOFOCUS_REQUEST_GRANTED; - } - - - public void unregisterAudioFocusClient(String clientId) { - synchronized(mAudioFocusLock) { - removeFocusStackEntry(clientId, false); - } - } - - - //========================================================================================== - // RemoteControl - //========================================================================================== - public void dispatchMediaKeyEvent(KeyEvent keyEvent) { - filterMediaKeyEvent(keyEvent, false /*needWakeLock*/); - } - - public void dispatchMediaKeyEventUnderWakelock(KeyEvent keyEvent) { - filterMediaKeyEvent(keyEvent, true /*needWakeLock*/); - } - - private void filterMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) { - // sanity check on the incoming key event - if (!isValidMediaKeyEvent(keyEvent)) { - Log.e(TAG, "not dispatching invalid media key event " + keyEvent); - return; - } - // event filtering for telephony - synchronized(mRingingLock) { - synchronized(mRCStack) { - if ((mMediaReceiverForCalls != null) && - (mIsRinging || (getMode() == AudioSystem.MODE_IN_CALL))) { - dispatchMediaKeyEventForCalls(keyEvent, needWakeLock); - return; - } - } - } - // event filtering based on voice-based interactions - if (isValidVoiceInputKeyCode(keyEvent.getKeyCode())) { - filterVoiceInputKeyEvent(keyEvent, needWakeLock); - } else { - dispatchMediaKeyEvent(keyEvent, needWakeLock); - } - } - - /** - * Handles the dispatching of the media button events to the telephony package. - * Precondition: mMediaReceiverForCalls != null - * @param keyEvent a non-null KeyEvent whose key code is one of the supported media buttons - * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held while this key event - * is dispatched. - */ - private void dispatchMediaKeyEventForCalls(KeyEvent keyEvent, boolean needWakeLock) { - Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null); - keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent); - keyIntent.setPackage(mMediaReceiverForCalls.getPackageName()); - if (needWakeLock) { - mMediaEventWakeLock.acquire(); - keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED, WAKELOCK_RELEASE_ON_FINISHED); - } - final long ident = Binder.clearCallingIdentity(); - try { - mContext.sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL, - null, mKeyEventDone, mAudioHandler, Activity.RESULT_OK, null, null); - } finally { - Binder.restoreCallingIdentity(ident); - } - } - - /** - * Handles the dispatching of the media button events to one of the registered listeners, - * or if there was none, broadcast an ACTION_MEDIA_BUTTON intent to the rest of the system. - * @param keyEvent a non-null KeyEvent whose key code is one of the supported media buttons - * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held while this key event - * is dispatched. - */ - private void dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) { - if (needWakeLock) { - mMediaEventWakeLock.acquire(); - } - Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null); - keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent); - synchronized(mRCStack) { - if (!mRCStack.empty()) { - // send the intent that was registered by the client - try { - mRCStack.peek().mMediaIntent.send(mContext, - needWakeLock ? WAKELOCK_RELEASE_ON_FINISHED : 0 /*code*/, - keyIntent, AudioService.this, mAudioHandler); - } catch (CanceledException e) { - Log.e(TAG, "Error sending pending intent " + mRCStack.peek()); - e.printStackTrace(); - } - } else { - // legacy behavior when nobody registered their media button event receiver - // through AudioManager - if (needWakeLock) { - keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED, WAKELOCK_RELEASE_ON_FINISHED); - } - final long ident = Binder.clearCallingIdentity(); - try { - mContext.sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL, - null, mKeyEventDone, - mAudioHandler, Activity.RESULT_OK, null, null); - } finally { - Binder.restoreCallingIdentity(ident); - } - } - } - } - - /** - * The different actions performed in response to a voice button key event. - */ - private final static int VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS = 1; - private final static int VOICEBUTTON_ACTION_START_VOICE_INPUT = 2; - private final static int VOICEBUTTON_ACTION_SIMULATE_KEY_PRESS = 3; - - private final Object mVoiceEventLock = new Object(); - private boolean mVoiceButtonDown; - private boolean mVoiceButtonHandled; - - /** - * Filter key events that may be used for voice-based interactions - * @param keyEvent a non-null KeyEvent whose key code is that of one of the supported - * media buttons that can be used to trigger voice-based interactions. - * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held while this key event - * is dispatched. - */ - private void filterVoiceInputKeyEvent(KeyEvent keyEvent, boolean needWakeLock) { - if (DEBUG_RC) { - Log.v(TAG, "voice input key event: " + keyEvent + ", needWakeLock=" + needWakeLock); - } - - int voiceButtonAction = VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS; - int keyAction = keyEvent.getAction(); - synchronized (mVoiceEventLock) { - if (keyAction == KeyEvent.ACTION_DOWN) { - if (keyEvent.getRepeatCount() == 0) { - // initial down - mVoiceButtonDown = true; - mVoiceButtonHandled = false; - } else if (mVoiceButtonDown && !mVoiceButtonHandled - && (keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) { - // long-press, start voice-based interactions - mVoiceButtonHandled = true; - voiceButtonAction = VOICEBUTTON_ACTION_START_VOICE_INPUT; - } - } else if (keyAction == KeyEvent.ACTION_UP) { - if (mVoiceButtonDown) { - // voice button up - mVoiceButtonDown = false; - if (!mVoiceButtonHandled && !keyEvent.isCanceled()) { - voiceButtonAction = VOICEBUTTON_ACTION_SIMULATE_KEY_PRESS; - } - } - } - }//synchronized (mVoiceEventLock) - - // take action after media button event filtering for voice-based interactions - switch (voiceButtonAction) { - case VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS: - if (DEBUG_RC) Log.v(TAG, " ignore key event"); - break; - case VOICEBUTTON_ACTION_START_VOICE_INPUT: - if (DEBUG_RC) Log.v(TAG, " start voice-based interactions"); - // then start the voice-based interactions - startVoiceBasedInteractions(needWakeLock); - break; - case VOICEBUTTON_ACTION_SIMULATE_KEY_PRESS: - if (DEBUG_RC) Log.v(TAG, " send simulated key event, wakelock=" + needWakeLock); - sendSimulatedMediaButtonEvent(keyEvent, needWakeLock); - break; - } - } - - private void sendSimulatedMediaButtonEvent(KeyEvent originalKeyEvent, boolean needWakeLock) { - // send DOWN event - KeyEvent keyEvent = KeyEvent.changeAction(originalKeyEvent, KeyEvent.ACTION_DOWN); - dispatchMediaKeyEvent(keyEvent, needWakeLock); - // send UP event - keyEvent = KeyEvent.changeAction(originalKeyEvent, KeyEvent.ACTION_UP); - dispatchMediaKeyEvent(keyEvent, needWakeLock); - - } - - - private static boolean isValidMediaKeyEvent(KeyEvent keyEvent) { - if (keyEvent == null) { - return false; - } - final int keyCode = keyEvent.getKeyCode(); - switch (keyCode) { - case KeyEvent.KEYCODE_MUTE: - case KeyEvent.KEYCODE_HEADSETHOOK: - case KeyEvent.KEYCODE_MEDIA_PLAY: - case KeyEvent.KEYCODE_MEDIA_PAUSE: - case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: - case KeyEvent.KEYCODE_MEDIA_STOP: - case KeyEvent.KEYCODE_MEDIA_NEXT: - case KeyEvent.KEYCODE_MEDIA_PREVIOUS: - case KeyEvent.KEYCODE_MEDIA_REWIND: - case KeyEvent.KEYCODE_MEDIA_RECORD: - case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: - case KeyEvent.KEYCODE_MEDIA_CLOSE: - case KeyEvent.KEYCODE_MEDIA_EJECT: - break; - default: - return false; - } - return true; - } - - /** - * Checks whether the given key code is one that can trigger the launch of voice-based - * interactions. - * @param keyCode the key code associated with the key event - * @return true if the key is one of the supported voice-based interaction triggers - */ - private static boolean isValidVoiceInputKeyCode(int keyCode) { - if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK) { - return true; - } else { - return false; - } - } - - /** - * Tell the system to start voice-based interactions / voice commands - */ - private void startVoiceBasedInteractions(boolean needWakeLock) { - Intent voiceIntent = null; - // select which type of search to launch: - // - screen on and device unlocked: action is ACTION_WEB_SEARCH - // - device locked or screen off: action is ACTION_VOICE_SEARCH_HANDS_FREE - // with EXTRA_SECURE set to true if the device is securely locked - PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE); - boolean isLocked = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked(); - if (!isLocked && pm.isScreenOn()) { - voiceIntent = new Intent(android.speech.RecognizerIntent.ACTION_WEB_SEARCH); - Log.i(TAG, "voice-based interactions: about to use ACTION_WEB_SEARCH"); - } else { - voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE); - voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE, - isLocked && mKeyguardManager.isKeyguardSecure()); - Log.i(TAG, "voice-based interactions: about to use ACTION_VOICE_SEARCH_HANDS_FREE"); - } - // start the search activity - if (needWakeLock) { - mMediaEventWakeLock.acquire(); - } - try { - if (voiceIntent != null) { - voiceIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); - mContext.startActivity(voiceIntent); - } - } catch (ActivityNotFoundException e) { - Log.w(TAG, "No activity for search: " + e); - } finally { - if (needWakeLock) { - mMediaEventWakeLock.release(); - } - } - } - - private PowerManager.WakeLock mMediaEventWakeLock; - - private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; //magic number - - // only set when wakelock was acquired, no need to check value when received - private static final String EXTRA_WAKELOCK_ACQUIRED = - "android.media.AudioService.WAKELOCK_ACQUIRED"; - - public void onSendFinished(PendingIntent pendingIntent, Intent intent, - int resultCode, String resultData, Bundle resultExtras) { - if (resultCode == WAKELOCK_RELEASE_ON_FINISHED) { - mMediaEventWakeLock.release(); - } - } - - BroadcastReceiver mKeyEventDone = new BroadcastReceiver() { - public void onReceive(Context context, Intent intent) { - if (intent == null) { - return; - } - Bundle extras = intent.getExtras(); - if (extras == null) { - return; - } - if (extras.containsKey(EXTRA_WAKELOCK_ACQUIRED)) { - mMediaEventWakeLock.release(); - } - } - }; - - /** - * Synchronization on mCurrentRcLock always inside a block synchronized on mRCStack - */ - private final Object mCurrentRcLock = new Object(); - /** - * The one remote control client which will receive a request for display information. - * This object may be null. - * Access protected by mCurrentRcLock. - */ - private IRemoteControlClient mCurrentRcClient = null; - - private final static int RC_INFO_NONE = 0; - private final static int RC_INFO_ALL = - RemoteControlClient.FLAG_INFORMATION_REQUEST_ALBUM_ART | - RemoteControlClient.FLAG_INFORMATION_REQUEST_KEY_MEDIA | - RemoteControlClient.FLAG_INFORMATION_REQUEST_METADATA | - RemoteControlClient.FLAG_INFORMATION_REQUEST_PLAYSTATE; - - /** - * A monotonically increasing generation counter for mCurrentRcClient. - * Only accessed with a lock on mCurrentRcLock. - * No value wrap-around issues as we only act on equal values. - */ - private int mCurrentRcClientGen = 0; - - /** - * Inner class to monitor remote control client deaths, and remove the client for the - * remote control stack if necessary. - */ - private class RcClientDeathHandler implements IBinder.DeathRecipient { - final private IBinder mCb; // To be notified of client's death - final private PendingIntent mMediaIntent; - - RcClientDeathHandler(IBinder cb, PendingIntent pi) { - mCb = cb; - mMediaIntent = pi; - } - - public void binderDied() { - Log.w(TAG, " RemoteControlClient died"); - // remote control client died, make sure the displays don't use it anymore - // by setting its remote control client to null - registerRemoteControlClient(mMediaIntent, null/*rcClient*/, null/*ignored*/); - // the dead client was maybe handling remote playback, reevaluate - postReevaluateRemote(); - } - - public IBinder getBinder() { - return mCb; - } - } - - /** - * A global counter for RemoteControlClient identifiers - */ - private static int sLastRccId = 0; - - private class RemotePlaybackState { - int mRccId; - int mVolume; - int mVolumeMax; - int mVolumeHandling; - - private RemotePlaybackState(int id, int vol, int volMax) { - mRccId = id; - mVolume = vol; - mVolumeMax = volMax; - mVolumeHandling = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME_HANDLING; - } - } - - /** - * Internal cache for the playback information of the RemoteControlClient whose volume gets to - * be controlled by the volume keys ("main"), so we don't have to iterate over the RC stack - * every time we need this info. - */ - private RemotePlaybackState mMainRemote; - /** - * Indicates whether the "main" RemoteControlClient is considered active. - * Use synchronized on mMainRemote. - */ - private boolean mMainRemoteIsActive; - /** - * Indicates whether there is remote playback going on. True even if there is no "active" - * remote playback (mMainRemoteIsActive is false), but a RemoteControlClient has declared it - * handles remote playback. - * Use synchronized on mMainRemote. - */ - private boolean mHasRemotePlayback; - - private static class RccPlaybackState { - public int mState; - public long mPositionMs; - public float mSpeed; - - public RccPlaybackState(int state, long positionMs, float speed) { - mState = state; - mPositionMs = positionMs; - mSpeed = speed; - } - - public void reset() { - mState = RemoteControlClient.PLAYSTATE_STOPPED; - mPositionMs = RemoteControlClient.PLAYBACK_POSITION_INVALID; - mSpeed = RemoteControlClient.PLAYBACK_SPEED_1X; - } - - @Override - public String toString() { - return stateToString() + ", " - + ((mPositionMs == RemoteControlClient.PLAYBACK_POSITION_INVALID) ? - "PLAYBACK_POSITION_INVALID ," : String.valueOf(mPositionMs)) + "ms ," - + mSpeed + "X"; - } - - private String stateToString() { - switch (mState) { - case RemoteControlClient.PLAYSTATE_NONE: - return "PLAYSTATE_NONE"; - case RemoteControlClient.PLAYSTATE_STOPPED: - return "PLAYSTATE_STOPPED"; - case RemoteControlClient.PLAYSTATE_PAUSED: - return "PLAYSTATE_PAUSED"; - case RemoteControlClient.PLAYSTATE_PLAYING: - return "PLAYSTATE_PLAYING"; - case RemoteControlClient.PLAYSTATE_FAST_FORWARDING: - return "PLAYSTATE_FAST_FORWARDING"; - case RemoteControlClient.PLAYSTATE_REWINDING: - return "PLAYSTATE_REWINDING"; - case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS: - return "PLAYSTATE_SKIPPING_FORWARDS"; - case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS: - return "PLAYSTATE_SKIPPING_BACKWARDS"; - case RemoteControlClient.PLAYSTATE_BUFFERING: - return "PLAYSTATE_BUFFERING"; - case RemoteControlClient.PLAYSTATE_ERROR: - return "PLAYSTATE_ERROR"; - default: - return "[invalid playstate]"; - } - } - } - - private static class RemoteControlStackEntry implements DeathRecipient { - public int mRccId = RemoteControlClient.RCSE_ID_UNREGISTERED; - final public AudioService mService; - /** - * The target for the ACTION_MEDIA_BUTTON events. - * Always non null. - */ - final public PendingIntent mMediaIntent; - /** - * The registered media button event receiver. - * Always non null. - */ - final public ComponentName mReceiverComponent; - public IBinder mToken; - public String mCallingPackageName; - public int mCallingUid; - /** - * Provides access to the information to display on the remote control. - * May be null (when a media button event receiver is registered, - * but no remote control client has been registered) */ - public IRemoteControlClient mRcClient; - public RcClientDeathHandler mRcClientDeathHandler; - /** - * Information only used for non-local playback - */ - public int mPlaybackType; - public int mPlaybackVolume; - public int mPlaybackVolumeMax; - public int mPlaybackVolumeHandling; - public int mPlaybackStream; - public RccPlaybackState mPlaybackState; - public IRemoteVolumeObserver mRemoteVolumeObs; - - public void resetPlaybackInfo() { - mPlaybackType = RemoteControlClient.PLAYBACK_TYPE_LOCAL; - mPlaybackVolume = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME; - mPlaybackVolumeMax = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME; - mPlaybackVolumeHandling = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME_HANDLING; - mPlaybackStream = AudioManager.STREAM_MUSIC; - mPlaybackState.reset(); - mRemoteVolumeObs = null; - } - - /** precondition: mediaIntent != null */ - public RemoteControlStackEntry(AudioService service, PendingIntent mediaIntent, - ComponentName eventReceiver, IBinder token) { - mService = service; - mMediaIntent = mediaIntent; - mReceiverComponent = eventReceiver; - mToken = token; - mCallingUid = -1; - mRcClient = null; - mRccId = ++sLastRccId; - mPlaybackState = new RccPlaybackState( - RemoteControlClient.PLAYSTATE_STOPPED, - RemoteControlClient.PLAYBACK_POSITION_INVALID, - RemoteControlClient.PLAYBACK_SPEED_1X); - - resetPlaybackInfo(); - if (mToken != null) { - try { - mToken.linkToDeath(this, 0); - } catch (RemoteException e) { - mService.mAudioHandler.post(new Runnable() { - @Override public void run() { - mService.unregisterMediaButtonIntent(mMediaIntent); - } - }); - } - } - } - - public void unlinkToRcClientDeath() { - if ((mRcClientDeathHandler != null) && (mRcClientDeathHandler.mCb != null)) { - try { - mRcClientDeathHandler.mCb.unlinkToDeath(mRcClientDeathHandler, 0); - mRcClientDeathHandler = null; - } catch (java.util.NoSuchElementException e) { - // not much we can do here - Log.e(TAG, "Encountered " + e + " in unlinkToRcClientDeath()"); - e.printStackTrace(); - } - } - } - - public void destroy() { - unlinkToRcClientDeath(); - if (mToken != null) { - mToken.unlinkToDeath(this, 0); - mToken = null; - } - } - - @Override - public void binderDied() { - mService.unregisterMediaButtonIntent(mMediaIntent); - } - - @Override - protected void finalize() throws Throwable { - destroy(); // unlink exception handled inside method - super.finalize(); - } - } - - /** - * The stack of remote control event receivers. - * Code sections and methods that modify the remote control event receiver stack are - * synchronized on mRCStack, but also BEFORE on mFocusLock as any change in either - * stack, audio focus or RC, can lead to a change in the remote control display - */ - private final Stack<RemoteControlStackEntry> mRCStack = new Stack<RemoteControlStackEntry>(); - - /** - * The component the telephony package can register so telephony calls have priority to - * handle media button events - */ - private ComponentName mMediaReceiverForCalls = null; - - /** - * Helper function: - * Display in the log the current entries in the remote control focus stack - */ - private void dumpRCStack(PrintWriter pw) { - pw.println("\nRemote Control stack entries (last is top of stack):"); - synchronized(mRCStack) { - Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator(); - while(stackIterator.hasNext()) { - RemoteControlStackEntry rcse = stackIterator.next(); - pw.println(" pi: " + rcse.mMediaIntent + - " -- pack: " + rcse.mCallingPackageName + - " -- ercvr: " + rcse.mReceiverComponent + - " -- client: " + rcse.mRcClient + - " -- uid: " + rcse.mCallingUid + - " -- type: " + rcse.mPlaybackType + - " state: " + rcse.mPlaybackState); - } - } - } - - /** - * Helper function: - * Display in the log the current entries in the remote control stack, focusing - * on RemoteControlClient data - */ - private void dumpRCCStack(PrintWriter pw) { - pw.println("\nRemote Control Client stack entries (last is top of stack):"); - synchronized(mRCStack) { - Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator(); - while(stackIterator.hasNext()) { - RemoteControlStackEntry rcse = stackIterator.next(); - pw.println(" uid: " + rcse.mCallingUid + - " -- id: " + rcse.mRccId + - " -- type: " + rcse.mPlaybackType + - " -- state: " + rcse.mPlaybackState + - " -- vol handling: " + rcse.mPlaybackVolumeHandling + - " -- vol: " + rcse.mPlaybackVolume + - " -- volMax: " + rcse.mPlaybackVolumeMax + - " -- volObs: " + rcse.mRemoteVolumeObs); - } - synchronized(mCurrentRcLock) { - pw.println("\nCurrent remote control generation ID = " + mCurrentRcClientGen); - } - } - synchronized (mMainRemote) { - pw.println("\nRemote Volume State:"); - pw.println(" has remote: " + mHasRemotePlayback); - pw.println(" is remote active: " + mMainRemoteIsActive); - pw.println(" rccId: " + mMainRemote.mRccId); - pw.println(" volume handling: " - + ((mMainRemote.mVolumeHandling == RemoteControlClient.PLAYBACK_VOLUME_FIXED) ? - "PLAYBACK_VOLUME_FIXED(0)" : "PLAYBACK_VOLUME_VARIABLE(1)")); - pw.println(" volume: " + mMainRemote.mVolume); - pw.println(" volume steps: " + mMainRemote.mVolumeMax); - } - } - - /** - * Helper function: - * Display in the log the current entries in the list of remote control displays - */ - private void dumpRCDList(PrintWriter pw) { - pw.println("\nRemote Control Display list entries:"); - synchronized(mRCStack) { - final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator(); - while (displayIterator.hasNext()) { - final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next(); - pw.println(" IRCD: " + di.mRcDisplay + - " -- w:" + di.mArtworkExpectedWidth + - " -- h:" + di.mArtworkExpectedHeight+ - " -- wantsPosSync:" + di.mWantsPositionSync); - } - } - } - - /** - * Helper function: - * Remove any entry in the remote control stack that has the same package name as packageName - * Pre-condition: packageName != null - */ - private void cleanupMediaButtonReceiverForPackage(String packageName, boolean removeAll) { - synchronized(mRCStack) { - if (mRCStack.empty()) { - return; - } else { - final PackageManager pm = mContext.getPackageManager(); - RemoteControlStackEntry oldTop = mRCStack.peek(); - Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator(); - // iterate over the stack entries - // (using an iterator on the stack so we can safely remove an entry after having - // evaluated it, traversal order doesn't matter here) - while(stackIterator.hasNext()) { - RemoteControlStackEntry rcse = (RemoteControlStackEntry)stackIterator.next(); - if (removeAll && packageName.equals(rcse.mMediaIntent.getCreatorPackage())) { - // a stack entry is from the package being removed, remove it from the stack - stackIterator.remove(); - rcse.destroy(); - } else if (rcse.mReceiverComponent != null) { - try { - // Check to see if this receiver still exists. - pm.getReceiverInfo(rcse.mReceiverComponent, 0); - } catch (PackageManager.NameNotFoundException e) { - // Not found -- remove it! - stackIterator.remove(); - rcse.destroy(); - } - } - } - if (mRCStack.empty()) { - // no saved media button receiver - mAudioHandler.sendMessage( - mAudioHandler.obtainMessage(MSG_PERSIST_MEDIABUTTONRECEIVER, 0, 0, - null)); - } else if (oldTop != mRCStack.peek()) { - // the top of the stack has changed, save it in the system settings - // by posting a message to persist it; only do this however if it has - // a concrete component name (is not a transient registration) - RemoteControlStackEntry rcse = mRCStack.peek(); - if (rcse.mReceiverComponent != null) { - mAudioHandler.sendMessage( - mAudioHandler.obtainMessage(MSG_PERSIST_MEDIABUTTONRECEIVER, 0, 0, - rcse.mReceiverComponent)); - } - } - } - } - } - - /** - * Helper function: - * Restore remote control receiver from the system settings. - */ - private void restoreMediaButtonReceiver() { - String receiverName = Settings.System.getStringForUser(mContentResolver, - Settings.System.MEDIA_BUTTON_RECEIVER, UserHandle.USER_CURRENT); - if ((null != receiverName) && !receiverName.isEmpty()) { - ComponentName eventReceiver = ComponentName.unflattenFromString(receiverName); - if (eventReceiver == null) { - // an invalid name was persisted - return; - } - // construct a PendingIntent targeted to the restored component name - // for the media button and register it - Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); - // the associated intent will be handled by the component being registered - mediaButtonIntent.setComponent(eventReceiver); - PendingIntent pi = PendingIntent.getBroadcast(mContext, - 0/*requestCode, ignored*/, mediaButtonIntent, 0/*flags*/); - registerMediaButtonIntent(pi, eventReceiver, null); - } - } - - /** - * Helper function: - * Set the new remote control receiver at the top of the RC focus stack. - * Called synchronized on mAudioFocusLock, then mRCStack - * precondition: mediaIntent != null - */ - private void pushMediaButtonReceiver_syncAfRcs(PendingIntent mediaIntent, ComponentName target, - IBinder token) { - // already at top of stack? - if (!mRCStack.empty() && mRCStack.peek().mMediaIntent.equals(mediaIntent)) { - return; - } - RemoteControlStackEntry rcse = null; - boolean wasInsideStack = false; - try { - for (int index = mRCStack.size()-1; index >= 0; index--) { - rcse = mRCStack.elementAt(index); - if(rcse.mMediaIntent.equals(mediaIntent)) { - // ok to remove element while traversing the stack since we're leaving the loop - mRCStack.removeElementAt(index); - wasInsideStack = true; - break; - } - } - } catch (ArrayIndexOutOfBoundsException e) { - // not expected to happen, indicates improper concurrent modification - Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e); - } - if (!wasInsideStack) { - rcse = new RemoteControlStackEntry(this, mediaIntent, target, token); - } - mRCStack.push(rcse); // rcse is never null - - // post message to persist the default media button receiver - if (target != null) { - mAudioHandler.sendMessage( mAudioHandler.obtainMessage( - MSG_PERSIST_MEDIABUTTONRECEIVER, 0, 0, target/*obj*/) ); - } - } - - /** - * Helper function: - * Remove the remote control receiver from the RC focus stack. - * Called synchronized on mAudioFocusLock, then mRCStack - * precondition: pi != null - */ - private void removeMediaButtonReceiver_syncAfRcs(PendingIntent pi) { - try { - for (int index = mRCStack.size()-1; index >= 0; index--) { - final RemoteControlStackEntry rcse = mRCStack.elementAt(index); - if (rcse.mMediaIntent.equals(pi)) { - rcse.destroy(); - // ok to remove element while traversing the stack since we're leaving the loop - mRCStack.removeElementAt(index); - break; - } - } - } catch (ArrayIndexOutOfBoundsException e) { - // not expected to happen, indicates improper concurrent modification - Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e); - } - } - - /** - * Helper function: - * Called synchronized on mRCStack - */ - private boolean isCurrentRcController(PendingIntent pi) { - if (!mRCStack.empty() && mRCStack.peek().mMediaIntent.equals(pi)) { - return true; - } - return false; - } - - //========================================================================================== - // Remote control display / client + // RemoteControlDisplay / RemoteControlClient / Remote info //========================================================================================== - /** - * Update the remote control displays with the new "focused" client generation - */ - private void setNewRcClientOnDisplays_syncRcsCurrc(int newClientGeneration, - PendingIntent newMediaIntent, boolean clearing) { - synchronized(mRCStack) { - if (mRcDisplays.size() > 0) { - final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator(); - while (displayIterator.hasNext()) { - final DisplayInfoForServer di = displayIterator.next(); - try { - di.mRcDisplay.setCurrentClientId( - newClientGeneration, newMediaIntent, clearing); - } catch (RemoteException e) { - Log.e(TAG, "Dead display in setNewRcClientOnDisplays_syncRcsCurrc()",e); - di.release(); - displayIterator.remove(); - } - } - } - } - } - - /** - * Update the remote control clients with the new "focused" client generation - */ - private void setNewRcClientGenerationOnClients_syncRcsCurrc(int newClientGeneration) { - // (using an iterator on the stack so we can safely remove an entry if needed, - // traversal order doesn't matter here as we update all entries) - Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator(); - while(stackIterator.hasNext()) { - RemoteControlStackEntry se = stackIterator.next(); - if ((se != null) && (se.mRcClient != null)) { - try { - se.mRcClient.setCurrentClientGenerationId(newClientGeneration); - } catch (RemoteException e) { - Log.w(TAG, "Dead client in setNewRcClientGenerationOnClients_syncRcsCurrc()",e); - stackIterator.remove(); - se.unlinkToRcClientDeath(); - } - } - } - } - - /** - * Update the displays and clients with the new "focused" client generation and name - * @param newClientGeneration the new generation value matching a client update - * @param newMediaIntent the media button event receiver associated with the client. - * May be null, which implies there is no registered media button event receiver. - * @param clearing true if the new client generation value maps to a remote control update - * where the display should be cleared. - */ - private void setNewRcClient_syncRcsCurrc(int newClientGeneration, - PendingIntent newMediaIntent, boolean clearing) { - // send the new valid client generation ID to all displays - setNewRcClientOnDisplays_syncRcsCurrc(newClientGeneration, newMediaIntent, clearing); - // send the new valid client generation ID to all clients - setNewRcClientGenerationOnClients_syncRcsCurrc(newClientGeneration); - } - - /** - * Called when processing MSG_RCDISPLAY_CLEAR event - */ - private void onRcDisplayClear() { - if (DEBUG_RC) Log.i(TAG, "Clear remote control display"); - - synchronized(mRCStack) { - synchronized(mCurrentRcLock) { - mCurrentRcClientGen++; - // synchronously update the displays and clients with the new client generation - setNewRcClient_syncRcsCurrc(mCurrentRcClientGen, - null /*newMediaIntent*/, true /*clearing*/); - } - } + public boolean registerRemoteController(IRemoteControlDisplay rcd, int w, int h, + ComponentName listenerComp) { + return mMediaFocusControl.registerRemoteController(rcd, w, h, listenerComp); } - /** - * Called when processing MSG_RCDISPLAY_UPDATE event - */ - private void onRcDisplayUpdate(RemoteControlStackEntry rcse, int flags /* USED ?*/) { - synchronized(mRCStack) { - synchronized(mCurrentRcLock) { - if ((mCurrentRcClient != null) && (mCurrentRcClient.equals(rcse.mRcClient))) { - if (DEBUG_RC) Log.i(TAG, "Display/update remote control "); - - mCurrentRcClientGen++; - // synchronously update the displays and clients with - // the new client generation - setNewRcClient_syncRcsCurrc(mCurrentRcClientGen, - rcse.mMediaIntent /*newMediaIntent*/, - false /*clearing*/); - - // tell the current client that it needs to send info - try { - mCurrentRcClient.onInformationRequested(mCurrentRcClientGen, flags); - } catch (RemoteException e) { - Log.e(TAG, "Current valid remote client is dead: "+e); - mCurrentRcClient = null; - } - } else { - // the remote control display owner has changed between the - // the message to update the display was sent, and the time it - // gets to be processed (now) - } - } - } + public boolean registerRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h) { + return mMediaFocusControl.registerRemoteControlDisplay(rcd, w, h); } - - /** - * Helper function: - * Called synchronized on mRCStack - */ - private void clearRemoteControlDisplay_syncAfRcs() { - synchronized(mCurrentRcLock) { - mCurrentRcClient = null; - } - // will cause onRcDisplayClear() to be called in AudioService's handler thread - mAudioHandler.sendMessage( mAudioHandler.obtainMessage(MSG_RCDISPLAY_CLEAR) ); - } - - /** - * Helper function for code readability: only to be called from - * checkUpdateRemoteControlDisplay_syncAfRcs() which checks the preconditions for - * this method. - * Preconditions: - * - called synchronized mAudioFocusLock then on mRCStack - * - mRCStack.isEmpty() is false - */ - private void updateRemoteControlDisplay_syncAfRcs(int infoChangedFlags) { - RemoteControlStackEntry rcse = mRCStack.peek(); - int infoFlagsAboutToBeUsed = infoChangedFlags; - // this is where we enforce opt-in for information display on the remote controls - // with the new AudioManager.registerRemoteControlClient() API - if (rcse.mRcClient == null) { - //Log.w(TAG, "Can't update remote control display with null remote control client"); - clearRemoteControlDisplay_syncAfRcs(); - return; - } - synchronized(mCurrentRcLock) { - if (!rcse.mRcClient.equals(mCurrentRcClient)) { - // new RC client, assume every type of information shall be queried - infoFlagsAboutToBeUsed = RC_INFO_ALL; - } - mCurrentRcClient = rcse.mRcClient; - } - // will cause onRcDisplayUpdate() to be called in AudioService's handler thread - mAudioHandler.sendMessage( mAudioHandler.obtainMessage(MSG_RCDISPLAY_UPDATE, - infoFlagsAboutToBeUsed /* arg1 */, 0, rcse /* obj, != null */) ); - } - - /** - * Helper function: - * Called synchronized on mAudioFocusLock, then mRCStack - * Check whether the remote control display should be updated, triggers the update if required - * @param infoChangedFlags the flags corresponding to the remote control client information - * that has changed, if applicable (checking for the update conditions might trigger a - * clear, rather than an update event). - */ - private void checkUpdateRemoteControlDisplay_syncAfRcs(int infoChangedFlags) { - // determine whether the remote control display should be refreshed - // if either stack is empty, there is a mismatch, so clear the RC display - if (mRCStack.isEmpty() || mFocusStack.isEmpty()) { - clearRemoteControlDisplay_syncAfRcs(); - return; - } - - // determine which entry in the AudioFocus stack to consider, and compare against the - // top of the stack for the media button event receivers : simply using the top of the - // stack would make the entry disappear from the RemoteControlDisplay in conditions such as - // notifications playing during music playback. - // Crawl the AudioFocus stack from the top until an entry is found with the following - // characteristics: - // - focus gain on STREAM_MUSIC stream - // - non-transient focus gain on a stream other than music - FocusStackEntry af = null; - try { - for (int index = mFocusStack.size()-1; index >= 0; index--) { - FocusStackEntry fse = mFocusStack.elementAt(index); - if ((fse.mStreamType == AudioManager.STREAM_MUSIC) - || (fse.mFocusChangeType == AudioManager.AUDIOFOCUS_GAIN)) { - af = fse; - break; - } - } - } catch (ArrayIndexOutOfBoundsException e) { - Log.e(TAG, "Wrong index accessing audio focus stack when updating RCD: " + e); - af = null; - } - if (af == null) { - clearRemoteControlDisplay_syncAfRcs(); - return; - } - - // if the audio focus and RC owners belong to different packages, there is a mismatch, clear - if ((mRCStack.peek().mCallingPackageName != null) - && (af.mPackageName != null) - && !(mRCStack.peek().mCallingPackageName.compareTo( - af.mPackageName) == 0)) { - clearRemoteControlDisplay_syncAfRcs(); - return; - } - // if the audio focus didn't originate from the same Uid as the one in which the remote - // control information will be retrieved, clear - if (mRCStack.peek().mCallingUid != af.mCallingUid) { - clearRemoteControlDisplay_syncAfRcs(); - return; - } - - // refresh conditions were verified: update the remote controls - // ok to call: synchronized mAudioFocusLock then on mRCStack, mRCStack is not empty - updateRemoteControlDisplay_syncAfRcs(infoChangedFlags); + public void unregisterRemoteControlDisplay(IRemoteControlDisplay rcd) { + mMediaFocusControl.unregisterRemoteControlDisplay(rcd); } - /** - * Helper function: - * Post a message to asynchronously move the media button event receiver associated with the - * given remote control client ID to the top of the remote control stack - * @param rccId - */ - private void postPromoteRcc(int rccId) { - sendMsg(mAudioHandler, MSG_PROMOTE_RCC, SENDMSG_REPLACE, - rccId /*arg1*/, 0, null, 0/*delay*/); + public void remoteControlDisplayUsesBitmapSize(IRemoteControlDisplay rcd, int w, int h) { + mMediaFocusControl.remoteControlDisplayUsesBitmapSize(rcd, w, h); } - private void onPromoteRcc(int rccId) { - if (DEBUG_RC) { Log.d(TAG, "Promoting RCC " + rccId); } - synchronized(mAudioFocusLock) { - synchronized(mRCStack) { - // ignore if given RCC ID is already at top of remote control stack - if (!mRCStack.isEmpty() && (mRCStack.peek().mRccId == rccId)) { - return; - } - int indexToPromote = -1; - try { - for (int index = mRCStack.size()-1; index >= 0; index--) { - final RemoteControlStackEntry rcse = mRCStack.elementAt(index); - if (rcse.mRccId == rccId) { - indexToPromote = index; - break; - } - } - if (indexToPromote >= 0) { - if (DEBUG_RC) { Log.d(TAG, " moving RCC from index " + indexToPromote - + " to " + (mRCStack.size()-1)); } - final RemoteControlStackEntry rcse = mRCStack.remove(indexToPromote); - mRCStack.push(rcse); - // the RC stack changed, reevaluate the display - checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL); - } - } catch (ArrayIndexOutOfBoundsException e) { - // not expected to happen, indicates improper concurrent modification - Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e); - } - }//synchronized(mRCStack) - }//synchronized(mAudioFocusLock) + public void remoteControlDisplayWantsPlaybackPositionSync(IRemoteControlDisplay rcd, + boolean wantsSync) { + mMediaFocusControl.remoteControlDisplayWantsPlaybackPositionSync(rcd, wantsSync); } - /** - * see AudioManager.registerMediaButtonIntent(PendingIntent pi, ComponentName c) - * precondition: mediaIntent != null - */ - public void registerMediaButtonIntent(PendingIntent mediaIntent, ComponentName eventReceiver, - IBinder token) { - Log.i(TAG, " Remote Control registerMediaButtonIntent() for " + mediaIntent); - - synchronized(mAudioFocusLock) { - synchronized(mRCStack) { - pushMediaButtonReceiver_syncAfRcs(mediaIntent, eventReceiver, token); - // new RC client, assume every type of information shall be queried - checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL); - } - } + public void registerMediaButtonEventReceiverForCalls(ComponentName c) { + mMediaFocusControl.registerMediaButtonEventReceiverForCalls(c); } - /** - * see AudioManager.unregisterMediaButtonIntent(PendingIntent mediaIntent) - * precondition: mediaIntent != null, eventReceiver != null - */ - public void unregisterMediaButtonIntent(PendingIntent mediaIntent) - { - Log.i(TAG, " Remote Control unregisterMediaButtonIntent() for " + mediaIntent); - - synchronized(mAudioFocusLock) { - synchronized(mRCStack) { - boolean topOfStackWillChange = isCurrentRcController(mediaIntent); - removeMediaButtonReceiver_syncAfRcs(mediaIntent); - if (topOfStackWillChange) { - // current RC client will change, assume every type of info needs to be queried - checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL); - } - } - } + public void unregisterMediaButtonEventReceiverForCalls() { + mMediaFocusControl.unregisterMediaButtonEventReceiverForCalls(); } - /** - * see AudioManager.registerMediaButtonEventReceiverForCalls(ComponentName c) - * precondition: c != null - */ - public void registerMediaButtonEventReceiverForCalls(ComponentName c) { - if (mContext.checkCallingPermission("android.permission.MODIFY_PHONE_STATE") - != PackageManager.PERMISSION_GRANTED) { - Log.e(TAG, "Invalid permissions to register media button receiver for calls"); - return; - } - synchronized(mRCStack) { - mMediaReceiverForCalls = c; - } + public void registerMediaButtonIntent(PendingIntent pi, ComponentName c, IBinder token) { + mMediaFocusControl.registerMediaButtonIntent(pi, c, token); } - /** - * see AudioManager.unregisterMediaButtonEventReceiverForCalls() - */ - public void unregisterMediaButtonEventReceiverForCalls() { - if (mContext.checkCallingPermission("android.permission.MODIFY_PHONE_STATE") - != PackageManager.PERMISSION_GRANTED) { - Log.e(TAG, "Invalid permissions to unregister media button receiver for calls"); - return; - } - synchronized(mRCStack) { - mMediaReceiverForCalls = null; - } + public void unregisterMediaButtonIntent(PendingIntent pi) { + mMediaFocusControl.unregisterMediaButtonIntent(pi); } - /** - * see AudioManager.registerRemoteControlClient(ComponentName eventReceiver, ...) - * @return the unique ID of the RemoteControlStackEntry associated with the RemoteControlClient - * Note: using this method with rcClient == null is a way to "disable" the IRemoteControlClient - * without modifying the RC stack, but while still causing the display to refresh (will - * become blank as a result of this) - */ public int registerRemoteControlClient(PendingIntent mediaIntent, - IRemoteControlClient rcClient, String callingPackageName) { - if (DEBUG_RC) Log.i(TAG, "Register remote control client rcClient="+rcClient); - int rccId = RemoteControlClient.RCSE_ID_UNREGISTERED; - synchronized(mAudioFocusLock) { - synchronized(mRCStack) { - // store the new display information - try { - for (int index = mRCStack.size()-1; index >= 0; index--) { - final RemoteControlStackEntry rcse = mRCStack.elementAt(index); - if(rcse.mMediaIntent.equals(mediaIntent)) { - // already had a remote control client? - if (rcse.mRcClientDeathHandler != null) { - // stop monitoring the old client's death - rcse.unlinkToRcClientDeath(); - } - // save the new remote control client - rcse.mRcClient = rcClient; - rcse.mCallingPackageName = callingPackageName; - rcse.mCallingUid = Binder.getCallingUid(); - if (rcClient == null) { - // here rcse.mRcClientDeathHandler is null; - rcse.resetPlaybackInfo(); - break; - } - rccId = rcse.mRccId; - - // there is a new (non-null) client: - // 1/ give the new client the displays (if any) - if (mRcDisplays.size() > 0) { - plugRemoteControlDisplaysIntoClient_syncRcStack(rcse.mRcClient); - } - // 2/ monitor the new client's death - IBinder b = rcse.mRcClient.asBinder(); - RcClientDeathHandler rcdh = - new RcClientDeathHandler(b, rcse.mMediaIntent); - try { - b.linkToDeath(rcdh, 0); - } catch (RemoteException e) { - // remote control client is DOA, disqualify it - Log.w(TAG, "registerRemoteControlClient() has a dead client " + b); - rcse.mRcClient = null; - } - rcse.mRcClientDeathHandler = rcdh; - break; - } - }//for - } catch (ArrayIndexOutOfBoundsException e) { - // not expected to happen, indicates improper concurrent modification - Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e); - } - - // if the eventReceiver is at the top of the stack - // then check for potential refresh of the remote controls - if (isCurrentRcController(mediaIntent)) { - checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL); - } - }//synchronized(mRCStack) - }//synchronized(mAudioFocusLock) - return rccId; + IRemoteControlClient rcClient, String callingPckg) { + return mMediaFocusControl.registerRemoteControlClient(mediaIntent, rcClient, callingPckg); } - /** - * see AudioManager.unregisterRemoteControlClient(PendingIntent pi, ...) - * rcClient is guaranteed non-null - */ public void unregisterRemoteControlClient(PendingIntent mediaIntent, IRemoteControlClient rcClient) { - if (DEBUG_RC) Log.i(TAG, "Unregister remote control client rcClient="+rcClient); - synchronized(mAudioFocusLock) { - synchronized(mRCStack) { - boolean topRccChange = false; - try { - for (int index = mRCStack.size()-1; index >= 0; index--) { - final RemoteControlStackEntry rcse = mRCStack.elementAt(index); - if ((rcse.mMediaIntent.equals(mediaIntent)) - && rcClient.equals(rcse.mRcClient)) { - // we found the IRemoteControlClient to unregister - // stop monitoring its death - rcse.unlinkToRcClientDeath(); - // reset the client-related fields - rcse.mRcClient = null; - rcse.mCallingPackageName = null; - topRccChange = (index == mRCStack.size()-1); - // there can only be one matching RCC in the RC stack, we're done - break; - } - } - } catch (ArrayIndexOutOfBoundsException e) { - // not expected to happen, indicates improper concurrent modification - Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e); - } - if (topRccChange) { - // no more RCC for the RCD, check for potential refresh of the remote controls - checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL); - } - } - } - } - - - /** - * A class to encapsulate all the information about a remote control display. - * After instanciation, init() must always be called before the object is added in the list - * of displays. - * Before being removed from the list of displays, release() must always be called (otherwise - * it will leak death handlers). - */ - private class DisplayInfoForServer implements IBinder.DeathRecipient { - /** may never be null */ - private IRemoteControlDisplay mRcDisplay; - private IBinder mRcDisplayBinder; - private int mArtworkExpectedWidth = -1; - private int mArtworkExpectedHeight = -1; - private boolean mWantsPositionSync = false; - - public DisplayInfoForServer(IRemoteControlDisplay rcd, int w, int h) { - if (DEBUG_RC) Log.i(TAG, "new DisplayInfoForServer for " + rcd + " w=" + w + " h=" + h); - mRcDisplay = rcd; - mRcDisplayBinder = rcd.asBinder(); - mArtworkExpectedWidth = w; - mArtworkExpectedHeight = h; - } - - public boolean init() { - try { - mRcDisplayBinder.linkToDeath(this, 0); - } catch (RemoteException e) { - // remote control display is DOA, disqualify it - Log.w(TAG, "registerRemoteControlDisplay() has a dead client " + mRcDisplayBinder); - return false; - } - return true; - } - - public void release() { - try { - mRcDisplayBinder.unlinkToDeath(this, 0); - } catch (java.util.NoSuchElementException e) { - // not much we can do here, the display should have been unregistered anyway - Log.e(TAG, "Error in DisplaInfoForServer.relase()", e); - } - } - - public void binderDied() { - synchronized(mRCStack) { - Log.w(TAG, "RemoteControl: display " + mRcDisplay + " died"); - // remove the display from the list - final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator(); - while (displayIterator.hasNext()) { - final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next(); - if (di.mRcDisplay == mRcDisplay) { - if (DEBUG_RC) Log.w(TAG, " RCD removed from list"); - displayIterator.remove(); - return; - } - } - } - } - } - - /** - * The remote control displays. - * Access synchronized on mRCStack - */ - private ArrayList<DisplayInfoForServer> mRcDisplays = new ArrayList<DisplayInfoForServer>(1); - - /** - * Plug each registered display into the specified client - * @param rcc, guaranteed non null - */ - private void plugRemoteControlDisplaysIntoClient_syncRcStack(IRemoteControlClient rcc) { - final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator(); - while (displayIterator.hasNext()) { - final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next(); - try { - rcc.plugRemoteControlDisplay(di.mRcDisplay, di.mArtworkExpectedWidth, - di.mArtworkExpectedHeight); - if (di.mWantsPositionSync) { - rcc.setWantsSyncForDisplay(di.mRcDisplay, true); - } - } catch (RemoteException e) { - Log.e(TAG, "Error connecting RCD to RCC in RCC registration",e); - } - } - } - - /** - * Is the remote control display interface already registered - * @param rcd - * @return true if the IRemoteControlDisplay is already in the list of displays - */ - private boolean rcDisplayIsPluggedIn_syncRcStack(IRemoteControlDisplay rcd) { - final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator(); - while (displayIterator.hasNext()) { - final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next(); - if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) { - return true; - } - } - return false; - } - - /** - * Register an IRemoteControlDisplay. - * Notify all IRemoteControlClient of the new display and cause the RemoteControlClient - * at the top of the stack to update the new display with its information. - * @see android.media.IAudioService#registerRemoteControlDisplay(android.media.IRemoteControlDisplay, int, int) - * @param rcd the IRemoteControlDisplay to register. No effect if null. - * @param w the maximum width of the expected bitmap. Negative or zero values indicate this - * display doesn't need to receive artwork. - * @param h the maximum height of the expected bitmap. Negative or zero values indicate this - * display doesn't need to receive artwork. - */ - public void registerRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h) { - if (DEBUG_RC) Log.d(TAG, ">>> registerRemoteControlDisplay("+rcd+")"); - synchronized(mAudioFocusLock) { - synchronized(mRCStack) { - if ((rcd == null) || rcDisplayIsPluggedIn_syncRcStack(rcd)) { - return; - } - DisplayInfoForServer di = new DisplayInfoForServer(rcd, w, h); - if (!di.init()) { - if (DEBUG_RC) Log.e(TAG, " error registering RCD"); - return; - } - // add RCD to list of displays - mRcDisplays.add(di); - - // let all the remote control clients know there is a new display (so the remote - // control stack traversal order doesn't matter). - Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator(); - while(stackIterator.hasNext()) { - RemoteControlStackEntry rcse = stackIterator.next(); - if(rcse.mRcClient != null) { - try { - rcse.mRcClient.plugRemoteControlDisplay(rcd, w, h); - } catch (RemoteException e) { - Log.e(TAG, "Error connecting RCD to client: ", e); - } - } - } - - // we have a new display, of which all the clients are now aware: have it be updated - checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL); - } - } - } - - /** - * Unregister an IRemoteControlDisplay. - * No effect if the IRemoteControlDisplay hasn't been successfully registered. - * @see android.media.IAudioService#unregisterRemoteControlDisplay(android.media.IRemoteControlDisplay) - * @param rcd the IRemoteControlDisplay to unregister. No effect if null. - */ - public void unregisterRemoteControlDisplay(IRemoteControlDisplay rcd) { - if (DEBUG_RC) Log.d(TAG, "<<< unregisterRemoteControlDisplay("+rcd+")"); - synchronized(mRCStack) { - if (rcd == null) { - return; - } - - boolean displayWasPluggedIn = false; - final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator(); - while (displayIterator.hasNext() && !displayWasPluggedIn) { - final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next(); - if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) { - displayWasPluggedIn = true; - di.release(); - displayIterator.remove(); - } - } - - if (displayWasPluggedIn) { - // disconnect this remote control display from all the clients, so the remote - // control stack traversal order doesn't matter - final Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator(); - while(stackIterator.hasNext()) { - final RemoteControlStackEntry rcse = stackIterator.next(); - if(rcse.mRcClient != null) { - try { - rcse.mRcClient.unplugRemoteControlDisplay(rcd); - } catch (RemoteException e) { - Log.e(TAG, "Error disconnecting remote control display to client: ", e); - } - } - } - } else { - if (DEBUG_RC) Log.w(TAG, " trying to unregister unregistered RCD"); - } - } - } - - /** - * Update the size of the artwork used by an IRemoteControlDisplay. - * @see android.media.IAudioService#remoteControlDisplayUsesBitmapSize(android.media.IRemoteControlDisplay, int, int) - * @param rcd the IRemoteControlDisplay with the new artwork size requirement - * @param w the maximum width of the expected bitmap. Negative or zero values indicate this - * display doesn't need to receive artwork. - * @param h the maximum height of the expected bitmap. Negative or zero values indicate this - * display doesn't need to receive artwork. - */ - public void remoteControlDisplayUsesBitmapSize(IRemoteControlDisplay rcd, int w, int h) { - synchronized(mRCStack) { - final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator(); - boolean artworkSizeUpdate = false; - while (displayIterator.hasNext() && !artworkSizeUpdate) { - final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next(); - if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) { - if ((di.mArtworkExpectedWidth != w) || (di.mArtworkExpectedHeight != h)) { - di.mArtworkExpectedWidth = w; - di.mArtworkExpectedHeight = h; - artworkSizeUpdate = true; - } - } - } - if (artworkSizeUpdate) { - // RCD is currently plugged in and its artwork size has changed, notify all RCCs, - // stack traversal order doesn't matter - final Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator(); - while(stackIterator.hasNext()) { - final RemoteControlStackEntry rcse = stackIterator.next(); - if(rcse.mRcClient != null) { - try { - rcse.mRcClient.setBitmapSizeForDisplay(rcd, w, h); - } catch (RemoteException e) { - Log.e(TAG, "Error setting bitmap size for RCD on RCC: ", e); - } - } - } - } - } - } - - /** - * Controls whether a remote control display needs periodic checks of the RemoteControlClient - * playback position to verify that the estimated position has not drifted from the actual - * position. By default the check is not performed. - * The IRemoteControlDisplay must have been previously registered for this to have any effect. - * @param rcd the IRemoteControlDisplay for which the anti-drift mechanism will be enabled - * or disabled. Not null. - * @param wantsSync if true, RemoteControlClient instances which expose their playback position - * to the framework will regularly compare the estimated playback position with the actual - * position, and will update the IRemoteControlDisplay implementation whenever a drift is - * detected. - */ - public void remoteControlDisplayWantsPlaybackPositionSync(IRemoteControlDisplay rcd, - boolean wantsSync) { - synchronized(mRCStack) { - boolean rcdRegistered = false; - // store the information about this display - // (display stack traversal order doesn't matter). - final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator(); - while (displayIterator.hasNext()) { - final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next(); - if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) { - di.mWantsPositionSync = wantsSync; - rcdRegistered = true; - break; - } - } - if (!rcdRegistered) { - return; - } - // notify all current RemoteControlClients - // (stack traversal order doesn't matter as we notify all RCCs) - final Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator(); - while (stackIterator.hasNext()) { - final RemoteControlStackEntry rcse = stackIterator.next(); - if (rcse.mRcClient != null) { - try { - rcse.mRcClient.setWantsSyncForDisplay(rcd, wantsSync); - } catch (RemoteException e) { - Log.e(TAG, "Error setting position sync flag for RCD on RCC: ", e); - } - } - } - } + mMediaFocusControl.unregisterRemoteControlClient(mediaIntent, rcClient); } public void setRemoteControlClientPlaybackPosition(int generationId, long timeMs) { - // ignore position change requests if invalid generation ID - synchronized(mRCStack) { - synchronized(mCurrentRcLock) { - if (mCurrentRcClientGen != generationId) { - return; - } - } - } - // discard any unprocessed seek request in the message queue, and replace with latest - sendMsg(mAudioHandler, MSG_RCC_SEEK_REQUEST, SENDMSG_REPLACE, generationId /* arg1 */, - 0 /* arg2 ignored*/, new Long(timeMs) /* obj */, 0 /* delay */); + mMediaFocusControl.setRemoteControlClientPlaybackPosition(generationId, timeMs); } - public void onSetRemoteControlClientPlaybackPosition(int generationId, long timeMs) { - if(DEBUG_RC) Log.d(TAG, "onSetRemoteControlClientPlaybackPosition(genId=" + generationId + - ", timeMs=" + timeMs + ")"); - synchronized(mRCStack) { - synchronized(mCurrentRcLock) { - if ((mCurrentRcClient != null) && (mCurrentRcClientGen == generationId)) { - // tell the current client to seek to the requested location - try { - mCurrentRcClient.seekTo(generationId, timeMs); - } catch (RemoteException e) { - Log.e(TAG, "Current valid remote client is dead: "+e); - mCurrentRcClient = null; - } - } - } - } + public void updateRemoteControlClientMetadata(int generationId, int key, Rating value) { + mMediaFocusControl.updateRemoteControlClientMetadata(generationId, key, value); } - public void setPlaybackInfoForRcc(int rccId, int what, int value) { - sendMsg(mAudioHandler, MSG_RCC_NEW_PLAYBACK_INFO, SENDMSG_QUEUE, - rccId /* arg1 */, what /* arg2 */, Integer.valueOf(value) /* obj */, 0 /* delay */); - } - - // handler for MSG_RCC_NEW_PLAYBACK_INFO - private void onNewPlaybackInfoForRcc(int rccId, int key, int value) { - if(DEBUG_RC) Log.d(TAG, "onNewPlaybackInfoForRcc(id=" + rccId + - ", what=" + key + ",val=" + value + ")"); - synchronized(mRCStack) { - // iterating from top of stack as playback information changes are more likely - // on entries at the top of the remote control stack - try { - for (int index = mRCStack.size()-1; index >= 0; index--) { - final RemoteControlStackEntry rcse = mRCStack.elementAt(index); - if (rcse.mRccId == rccId) { - switch (key) { - case RemoteControlClient.PLAYBACKINFO_PLAYBACK_TYPE: - rcse.mPlaybackType = value; - postReevaluateRemote(); - break; - case RemoteControlClient.PLAYBACKINFO_VOLUME: - rcse.mPlaybackVolume = value; - synchronized (mMainRemote) { - if (rccId == mMainRemote.mRccId) { - mMainRemote.mVolume = value; - mVolumePanel.postHasNewRemotePlaybackInfo(); - } - } - break; - case RemoteControlClient.PLAYBACKINFO_VOLUME_MAX: - rcse.mPlaybackVolumeMax = value; - synchronized (mMainRemote) { - if (rccId == mMainRemote.mRccId) { - mMainRemote.mVolumeMax = value; - mVolumePanel.postHasNewRemotePlaybackInfo(); - } - } - break; - case RemoteControlClient.PLAYBACKINFO_VOLUME_HANDLING: - rcse.mPlaybackVolumeHandling = value; - synchronized (mMainRemote) { - if (rccId == mMainRemote.mRccId) { - mMainRemote.mVolumeHandling = value; - mVolumePanel.postHasNewRemotePlaybackInfo(); - } - } - break; - case RemoteControlClient.PLAYBACKINFO_USES_STREAM: - rcse.mPlaybackStream = value; - break; - default: - Log.e(TAG, "unhandled key " + key + " for RCC " + rccId); - break; - } - return; - } - }//for - } catch (ArrayIndexOutOfBoundsException e) { - // not expected to happen, indicates improper concurrent modification - Log.e(TAG, "Wrong index mRCStack on onNewPlaybackInfoForRcc, lock error? ", e); - } - } - } - - public void setPlaybackStateForRcc(int rccId, int state, long timeMs, float speed) { - sendMsg(mAudioHandler, MSG_RCC_NEW_PLAYBACK_STATE, SENDMSG_QUEUE, - rccId /* arg1 */, state /* arg2 */, - new RccPlaybackState(state, timeMs, speed) /* obj */, 0 /* delay */); + public void registerRemoteVolumeObserverForRcc(int rccId, IRemoteVolumeObserver rvo) { + mMediaFocusControl.registerRemoteVolumeObserverForRcc(rccId, rvo); } - public void onNewPlaybackStateForRcc(int rccId, int state, RccPlaybackState newState) { - if(DEBUG_RC) Log.d(TAG, "onNewPlaybackStateForRcc(id=" + rccId + ", state=" + state - + ", time=" + newState.mPositionMs + ", speed=" + newState.mSpeed + ")"); - synchronized(mRCStack) { - // iterating from top of stack as playback information changes are more likely - // on entries at the top of the remote control stack - try { - for (int index = mRCStack.size()-1; index >= 0; index--) { - final RemoteControlStackEntry rcse = mRCStack.elementAt(index); - if (rcse.mRccId == rccId) { - rcse.mPlaybackState = newState; - synchronized (mMainRemote) { - if (rccId == mMainRemote.mRccId) { - mMainRemoteIsActive = isPlaystateActive(state); - postReevaluateRemote(); - } - } - // an RCC moving to a "playing" state should become the media button - // event receiver so it can be controlled, without requiring the - // app to re-register its receiver - if (isPlaystateActive(state)) { - postPromoteRcc(rccId); - } - } - }//for - } catch (ArrayIndexOutOfBoundsException e) { - // not expected to happen, indicates improper concurrent modification - Log.e(TAG, "Wrong index on mRCStack in onNewPlaybackStateForRcc, lock error? ", e); - } - } + public int getRemoteStreamVolume() { + return mMediaFocusControl.getRemoteStreamVolume(); } - public void registerRemoteVolumeObserverForRcc(int rccId, IRemoteVolumeObserver rvo) { - sendMsg(mAudioHandler, MSG_RCC_NEW_VOLUME_OBS, SENDMSG_QUEUE, - rccId /* arg1 */, 0, rvo /* obj */, 0 /* delay */); + public int getRemoteStreamMaxVolume() { + return mMediaFocusControl.getRemoteStreamMaxVolume(); } - // handler for MSG_RCC_NEW_VOLUME_OBS - private void onRegisterVolumeObserverForRcc(int rccId, IRemoteVolumeObserver rvo) { - synchronized(mRCStack) { - // The stack traversal order doesn't matter because there is only one stack entry - // with this RCC ID, but the matching ID is more likely at the top of the stack, so - // start iterating from the top. - try { - for (int index = mRCStack.size()-1; index >= 0; index--) { - final RemoteControlStackEntry rcse = mRCStack.elementAt(index); - if (rcse.mRccId == rccId) { - rcse.mRemoteVolumeObs = rvo; - break; - } - } - } catch (ArrayIndexOutOfBoundsException e) { - // not expected to happen, indicates improper concurrent modification - Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e); - } - } + public void setRemoteStreamVolume(int index) { + mMediaFocusControl.setRemoteStreamVolume(index); } - /** - * Checks if a remote client is active on the supplied stream type. Update the remote stream - * volume state if found and playing - * @param streamType - * @return false if no remote playing is currently playing - */ - private boolean checkUpdateRemoteStateIfActive(int streamType) { - synchronized(mRCStack) { - // iterating from top of stack as active playback is more likely on entries at the top - try { - for (int index = mRCStack.size()-1; index >= 0; index--) { - final RemoteControlStackEntry rcse = mRCStack.elementAt(index); - if ((rcse.mPlaybackType == RemoteControlClient.PLAYBACK_TYPE_REMOTE) - && isPlaystateActive(rcse.mPlaybackState.mState) - && (rcse.mPlaybackStream == streamType)) { - if (DEBUG_RC) Log.d(TAG, "remote playback active on stream " + streamType - + ", vol =" + rcse.mPlaybackVolume); - synchronized (mMainRemote) { - mMainRemote.mRccId = rcse.mRccId; - mMainRemote.mVolume = rcse.mPlaybackVolume; - mMainRemote.mVolumeMax = rcse.mPlaybackVolumeMax; - mMainRemote.mVolumeHandling = rcse.mPlaybackVolumeHandling; - mMainRemoteIsActive = true; - } - return true; - } - } - } catch (ArrayIndexOutOfBoundsException e) { - // not expected to happen, indicates improper concurrent modification - Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e); - } - } - synchronized (mMainRemote) { - mMainRemoteIsActive = false; - } - return false; + public void setPlaybackStateForRcc(int rccId, int state, long timeMs, float speed) { + mMediaFocusControl.setPlaybackStateForRcc(rccId, state, timeMs, speed); } - /** - * Returns true if the given playback state is considered "active", i.e. it describes a state - * where playback is happening, or about to - * @param playState the playback state to evaluate - * @return true if active, false otherwise (inactive or unknown) - */ - private static boolean isPlaystateActive(int playState) { - switch (playState) { - case RemoteControlClient.PLAYSTATE_PLAYING: - case RemoteControlClient.PLAYSTATE_BUFFERING: - case RemoteControlClient.PLAYSTATE_FAST_FORWARDING: - case RemoteControlClient.PLAYSTATE_REWINDING: - case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS: - case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS: - return true; - default: - return false; - } + public void setPlaybackInfoForRcc(int rccId, int what, int value) { + mMediaFocusControl.setPlaybackInfoForRcc(rccId, what, value); } - private void adjustRemoteVolume(int streamType, int direction, int flags) { - int rccId = RemoteControlClient.RCSE_ID_UNREGISTERED; - boolean volFixed = false; - synchronized (mMainRemote) { - if (!mMainRemoteIsActive) { - if (DEBUG_VOL) Log.w(TAG, "adjustRemoteVolume didn't find an active client"); - return; - } - rccId = mMainRemote.mRccId; - volFixed = (mMainRemote.mVolumeHandling == - RemoteControlClient.PLAYBACK_VOLUME_FIXED); - } - // unlike "local" stream volumes, we can't compute the new volume based on the direction, - // we can only notify the remote that volume needs to be updated, and we'll get an async' - // update through setPlaybackInfoForRcc() - if (!volFixed) { - sendVolumeUpdateToRemote(rccId, direction); - } - - // fire up the UI - mVolumePanel.postRemoteVolumeChanged(streamType, flags); + public void dispatchMediaKeyEvent(KeyEvent keyEvent) { + mMediaFocusControl.dispatchMediaKeyEvent(keyEvent); } - private void sendVolumeUpdateToRemote(int rccId, int direction) { - if (DEBUG_VOL) { Log.d(TAG, "sendVolumeUpdateToRemote(rccId="+rccId+" , dir="+direction); } - if (direction == 0) { - // only handling discrete events - return; - } - IRemoteVolumeObserver rvo = null; - synchronized (mRCStack) { - // The stack traversal order doesn't matter because there is only one stack entry - // with this RCC ID, but the matching ID is more likely at the top of the stack, so - // start iterating from the top. - try { - for (int index = mRCStack.size()-1; index >= 0; index--) { - final RemoteControlStackEntry rcse = mRCStack.elementAt(index); - //FIXME OPTIMIZE store this info in mMainRemote so we don't have to iterate? - if (rcse.mRccId == rccId) { - rvo = rcse.mRemoteVolumeObs; - break; - } - } - } catch (ArrayIndexOutOfBoundsException e) { - // not expected to happen, indicates improper concurrent modification - Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e); - } - } - if (rvo != null) { - try { - rvo.dispatchRemoteVolumeUpdate(direction, -1); - } catch (RemoteException e) { - Log.e(TAG, "Error dispatching relative volume update", e); - } - } + public void dispatchMediaKeyEventUnderWakelock(KeyEvent keyEvent) { + mMediaFocusControl.dispatchMediaKeyEventUnderWakelock(keyEvent); } - public int getRemoteStreamMaxVolume() { - synchronized (mMainRemote) { - if (mMainRemote.mRccId == RemoteControlClient.RCSE_ID_UNREGISTERED) { - return 0; - } - return mMainRemote.mVolumeMax; - } + //========================================================================================== + // Audio Focus + //========================================================================================== + public int requestAudioFocus(int mainStreamType, int durationHint, IBinder cb, + IAudioFocusDispatcher fd, String clientId, String callingPackageName) { + return mMediaFocusControl.requestAudioFocus(mainStreamType, durationHint, cb, fd, + clientId, callingPackageName); } - public int getRemoteStreamVolume() { - synchronized (mMainRemote) { - if (mMainRemote.mRccId == RemoteControlClient.RCSE_ID_UNREGISTERED) { - return 0; - } - return mMainRemote.mVolume; - } + public int abandonAudioFocus(IAudioFocusDispatcher fd, String clientId) { + return mMediaFocusControl.abandonAudioFocus(fd, clientId); } - public void setRemoteStreamVolume(int vol) { - if (DEBUG_VOL) { Log.d(TAG, "setRemoteStreamVolume(vol="+vol+")"); } - int rccId = RemoteControlClient.RCSE_ID_UNREGISTERED; - synchronized (mMainRemote) { - if (mMainRemote.mRccId == RemoteControlClient.RCSE_ID_UNREGISTERED) { - return; - } - rccId = mMainRemote.mRccId; - } - IRemoteVolumeObserver rvo = null; - synchronized (mRCStack) { - // The stack traversal order doesn't matter because there is only one stack entry - // with this RCC ID, but the matching ID is more likely at the top of the stack, so - // start iterating from the top. - try { - for (int index = mRCStack.size()-1; index >= 0; index--) { - final RemoteControlStackEntry rcse = mRCStack.elementAt(index); - //FIXME OPTIMIZE store this info in mMainRemote so we don't have to iterate? - if (rcse.mRccId == rccId) { - rvo = rcse.mRemoteVolumeObs; - break; - } - } - } catch (ArrayIndexOutOfBoundsException e) { - // not expected to happen, indicates improper concurrent modification - Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e); - } - } - if (rvo != null) { - try { - rvo.dispatchRemoteVolumeUpdate(0, vol); - } catch (RemoteException e) { - Log.e(TAG, "Error dispatching absolute volume update", e); - } - } + public void unregisterAudioFocusClient(String clientId) { + mMediaFocusControl.unregisterAudioFocusClient(clientId); } - /** - * Call to make AudioService reevaluate whether it's in a mode where remote players should - * have their volume controlled. In this implementation this is only to reset whether - * VolumePanel should display remote volumes - */ - private void postReevaluateRemote() { - sendMsg(mAudioHandler, MSG_REEVALUATE_REMOTE, SENDMSG_QUEUE, 0, 0, null, 0); - } - - private void onReevaluateRemote() { - if (DEBUG_VOL) { Log.w(TAG, "onReevaluateRemote()"); } - // is there a registered RemoteControlClient that is handling remote playback - boolean hasRemotePlayback = false; - synchronized (mRCStack) { - // iteration stops when PLAYBACK_TYPE_REMOTE is found, so remote control stack - // traversal order doesn't matter - Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator(); - while(stackIterator.hasNext()) { - RemoteControlStackEntry rcse = stackIterator.next(); - if (rcse.mPlaybackType == RemoteControlClient.PLAYBACK_TYPE_REMOTE) { - hasRemotePlayback = true; - break; - } - } - } - synchronized (mMainRemote) { - if (mHasRemotePlayback != hasRemotePlayback) { - mHasRemotePlayback = hasRemotePlayback; - mVolumePanel.postRemoteSliderVisibility(hasRemotePlayback); - } - } + public int getCurrentAudioFocus() { + return mMediaFocusControl.getCurrentAudioFocus(); } //========================================================================================== @@ -6638,14 +4607,20 @@ public class AudioService extends IAudioService.Stub implements OnFinished { protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG); - dumpFocusStack(pw); - dumpRCStack(pw); - dumpRCCStack(pw); - dumpRCDList(pw); + mMediaFocusControl.dump(pw); dumpStreamStates(pw); dumpRingerMode(pw); pw.println("\nAudio routes:"); pw.print(" mMainType=0x"); pw.println(Integer.toHexString(mCurAudioRoutes.mMainType)); pw.print(" mBluetoothName="); pw.println(mCurAudioRoutes.mBluetoothName); } + + // Inform AudioFlinger of our device's low RAM attribute + private static void readAndSetLowRamDevice() + { + int status = AudioSystem.setLowRamDevice(ActivityManager.isLowRamDeviceStatic()); + if (status != 0) { + Log.w(TAG, "AudioFlinger informed of device's low RAM attribute; status " + status); + } + } } diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index d42bfd4..661b0fd 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -177,12 +177,10 @@ public class AudioSystem { synchronized (AudioSystem.class) { mErrorCallback = cb; + if (cb != null) { + cb.onError(checkAudioFlinger()); + } } - // Calling a method on AudioFlinger here makes sure that we bind to IAudioFlinger - // binder interface death. Not doing that would result in not being notified of - // media_server process death if no other method is called on AudioSystem that reaches - // to AudioFlinger. - isMicrophoneMuted(); } private static void errorCallbackFromNative(int error) @@ -403,4 +401,6 @@ public class AudioSystem public static native int getPrimaryOutputFrameCount(); public static native int getOutputLatency(int stream); + public static native int setLowRamDevice(boolean isLowRamDevice); + public static native int checkAudioFlinger(); } diff --git a/media/java/android/media/AudioTimestamp.java b/media/java/android/media/AudioTimestamp.java new file mode 100644 index 0000000..965ba85 --- /dev/null +++ b/media/java/android/media/AudioTimestamp.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2013 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; + +/** + * Structure that groups a position in frame units relative to an assumed audio stream, + * together with the estimated time when that frame was presented or is committed to be + * presented. + * In the case of audio output, "present" means that audio produced on device + * is detectable by an external observer off device. + * The time is based on the implementation's best effort, using whatever knowledge + * is available to the system, but cannot account for any delay unknown to the implementation. + * + * @see AudioTrack#getTimestamp + */ +public final class AudioTimestamp +{ + /** + * Position in frames relative to start of an assumed audio stream. + * The low-order 32 bits of position is in wrapping frame units similar to + * {@link AudioTrack#getPlaybackHeadPosition}. + */ + public long framePosition; + + /** + * The estimated time when frame was presented or is committed to be presented, + * in the same units and timebase as {@link java.lang.System#nanoTime}. + */ + public long nanoTime; +} diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java index 9768a78..78a37c5 100644 --- a/media/java/android/media/AudioTrack.java +++ b/media/java/android/media/AudioTrack.java @@ -26,7 +26,7 @@ import android.util.Log; /** * The AudioTrack class manages and plays a single audio resource for Java applications. - * It allows streaming PCM audio buffers to the audio hardware for playback. This is + * It allows streaming of PCM audio buffers to the audio sink for playback. This is * achieved by "pushing" the data to the AudioTrack object using one of the * {@link #write(byte[], int, int)} and {@link #write(short[], int, int)} methods. * @@ -53,8 +53,10 @@ import android.util.Log; * can play before running out of data.<br> * For an AudioTrack using the static mode, this size is the maximum size of the sound that can * be played from it.<br> - * For the streaming mode, data will be written to the hardware in chunks of + * For the streaming mode, data will be written to the audio sink in chunks of * sizes less than or equal to the total buffer size. + * + * AudioTrack is not final and thus permits subclasses, but such use is not recommended. */ public class AudioTrack { @@ -130,7 +132,7 @@ public class AudioTrack private static final int ERROR_NATIVESETUP_NATIVEINITFAILED = -20; // Events: - // to keep in sync with frameworks/base/include/media/AudioTrack.h + // to keep in sync with frameworks/av/include/media/AudioTrack.h /** * Event id denotes when playback head has reached a previously set marker. */ @@ -159,11 +161,12 @@ public class AudioTrack */ private final Object mPlayStateLock = new Object(); /** - * Size of the native audio buffer. + * Sizes of the native audio buffer. */ private int mNativeBufferSizeInBytes = 0; + private int mNativeBufferSizeInFrames = 0; /** - * Handler for marker events coming from the native code. + * Handler for events coming from the native code. */ private NativeEventHandlerDelegate mEventHandlerDelegate; /** @@ -171,7 +174,7 @@ public class AudioTrack */ private final Looper mInitializationLooper; /** - * The audio data sampling rate in Hz. + * The audio data source sampling rate in Hz. */ private int mSampleRate; // initialized by all constructors /** @@ -192,7 +195,7 @@ public class AudioTrack */ private int mStreamType = AudioManager.STREAM_MUSIC; /** - * The way audio is consumed by the hardware, streaming or static. + * The way audio is consumed by the audio sink, streaming or static. */ private int mDataLoadMode = MODE_STREAM; /** @@ -236,17 +239,20 @@ public class AudioTrack * {@link AudioManager#STREAM_VOICE_CALL}, {@link AudioManager#STREAM_SYSTEM}, * {@link AudioManager#STREAM_RING}, {@link AudioManager#STREAM_MUSIC}, * {@link AudioManager#STREAM_ALARM}, and {@link AudioManager#STREAM_NOTIFICATION}. - * @param sampleRateInHz the sample rate expressed in Hertz. + * @param sampleRateInHz the initial source sample rate expressed in Hz. * @param channelConfig describes the configuration of the audio channels. * See {@link AudioFormat#CHANNEL_OUT_MONO} and * {@link AudioFormat#CHANNEL_OUT_STEREO} * @param audioFormat the format in which the audio data is represented. * See {@link AudioFormat#ENCODING_PCM_16BIT} and * {@link AudioFormat#ENCODING_PCM_8BIT} - * @param bufferSizeInBytes the total size (in bytes) of the buffer where audio data is read - * from for playback. If using the AudioTrack in streaming mode, you can write data into - * this buffer in smaller chunks than this size. If using the AudioTrack in static mode, - * this is the maximum size of the sound that will be played for this instance. + * @param bufferSizeInBytes the total size (in bytes) of the internal buffer where audio data is + * read from for playback. + * If track's creation mode is {@link #MODE_STREAM}, you can write data into + * this buffer in chunks less than or equal to this size, and it is typical to use + * chunks of 1/2 of the total size to permit double-buffering. + * If the track's creation mode is {@link #MODE_STATIC}, + * this is the maximum length sample, or audio clip, that can be played by this instance. * See {@link #getMinBufferSize(int, int, int)} to determine the minimum required buffer size * for the successful creation of an AudioTrack instance in streaming mode. Using values * smaller than getMinBufferSize() will result in an initialization failure. @@ -257,7 +263,7 @@ public class AudioTrack int bufferSizeInBytes, int mode) throws IllegalArgumentException { this(streamType, sampleRateInHz, channelConfig, audioFormat, - bufferSizeInBytes, mode, 0); + bufferSizeInBytes, mode, 0 /*session*/); } /** @@ -267,7 +273,7 @@ public class AudioTrack * is provided when creating an AudioEffect, this effect will be applied only to audio tracks * and media players in the same session and not to the output mix. * When an AudioTrack is created without specifying a session, it will create its own session - * which can be retreived by calling the {@link #getAudioSessionId()} method. + * which can be retrieved by calling the {@link #getAudioSessionId()} method. * If a non-zero session ID is provided, this AudioTrack will share effects attached to this * session * with all other media players or audio tracks in the same session, otherwise a new session @@ -276,7 +282,7 @@ public class AudioTrack * {@link AudioManager#STREAM_VOICE_CALL}, {@link AudioManager#STREAM_SYSTEM}, * {@link AudioManager#STREAM_RING}, {@link AudioManager#STREAM_MUSIC}, * {@link AudioManager#STREAM_ALARM}, and {@link AudioManager#STREAM_NOTIFICATION}. - * @param sampleRateInHz the sample rate expressed in Hertz. + * @param sampleRateInHz the initial source sample rate expressed in Hz. * @param channelConfig describes the configuration of the audio channels. * See {@link AudioFormat#CHANNEL_OUT_MONO} and * {@link AudioFormat#CHANNEL_OUT_STEREO} @@ -365,18 +371,16 @@ public class AudioTrack && (streamType != AudioManager.STREAM_BLUETOOTH_SCO) && (streamType != AudioManager.STREAM_DTMF)) { throw new IllegalArgumentException("Invalid stream type."); - } else { - mStreamType = streamType; } + mStreamType = streamType; //-------------- // sample rate, note these values are subject to change if ( (sampleRateInHz < 4000) || (sampleRateInHz > 48000) ) { throw new IllegalArgumentException(sampleRateInHz + "Hz is not a supported sample rate."); - } else { - mSampleRate = sampleRateInHz; } + mSampleRate = sampleRateInHz; //-------------- // channel config @@ -397,14 +401,10 @@ public class AudioTrack default: if (!isMultichannelConfigSupported(channelConfig)) { // input channel configuration features unsupported channels - mChannelCount = 0; - mChannels = AudioFormat.CHANNEL_INVALID; - mChannelConfiguration = AudioFormat.CHANNEL_INVALID; throw new IllegalArgumentException("Unsupported channel configuration."); - } else { - mChannels = channelConfig; - mChannelCount = Integer.bitCount(channelConfig); } + mChannels = channelConfig; + mChannelCount = Integer.bitCount(channelConfig); } //-------------- @@ -418,7 +418,6 @@ public class AudioTrack mAudioFormat = audioFormat; break; default: - mAudioFormat = AudioFormat.ENCODING_INVALID; throw new IllegalArgumentException("Unsupported sample encoding." + " Should be ENCODING_PCM_8BIT or ENCODING_PCM_16BIT."); } @@ -427,9 +426,8 @@ public class AudioTrack // audio load mode if ( (mode != MODE_STREAM) && (mode != MODE_STATIC) ) { throw new IllegalArgumentException("Invalid mode."); - } else { - mDataLoadMode = mode; } + mDataLoadMode = mode; } /** @@ -464,7 +462,7 @@ public class AudioTrack } - // Convenience method for the contructor's audio buffer size check. + // Convenience method for the constructor's audio buffer size check. // preconditions: // mChannelCount is valid // mAudioFormat is valid @@ -480,6 +478,7 @@ public class AudioTrack } mNativeBufferSizeInBytes = audioBufferSize; + mNativeBufferSizeInFrames = audioBufferSize / frameSizeInBytes; } @@ -559,7 +558,6 @@ public class AudioTrack /** * Returns the configured channel configuration. - * See {@link AudioFormat#CHANNEL_OUT_MONO} * and {@link AudioFormat#CHANNEL_OUT_STEREO}. */ @@ -577,8 +575,7 @@ public class AudioTrack /** * Returns the state of the AudioTrack instance. This is useful after the * AudioTrack instance has been created to check if it was initialized - * properly. This ensures that the appropriate hardware resources have been - * acquired. + * properly. This ensures that the appropriate resources have been acquired. * @see #STATE_INITIALIZED * @see #STATE_NO_STATIC_DATA * @see #STATE_UNINITIALIZED @@ -600,14 +597,26 @@ public class AudioTrack } /** - * Returns the native frame count used by the hardware. + * Returns the "native frame count", derived from the bufferSizeInBytes specified at + * creation time and converted to frame units. + * If track's creation mode is {@link #MODE_STATIC}, + * it is equal to the specified bufferSizeInBytes converted to frame units. + * If track's creation mode is {@link #MODE_STREAM}, + * it is typically greater than or equal to the specified bufferSizeInBytes converted to frame + * units; it may be rounded up to a larger value if needed by the target device implementation. + * @deprecated Only accessible by subclasses, which are not recommended for AudioTrack. + * See {@link AudioManager#getProperty(String)} for key + * {@link AudioManager#PROPERTY_OUTPUT_FRAMES_PER_BUFFER}. */ + @Deprecated protected int getNativeFrameCount() { return native_get_native_frame_count(); } /** * Returns marker position expressed in frames. + * @return marker position in wrapping frame units similar to {@link #getPlaybackHeadPosition}, + * or zero if marker is disabled. */ public int getNotificationMarkerPosition() { return native_get_marker_pos(); @@ -615,13 +624,19 @@ public class AudioTrack /** * Returns the notification update period expressed in frames. + * Zero means that no position update notifications are being delivered. */ public int getPositionNotificationPeriod() { return native_get_pos_update_period(); } /** - * Returns the playback head position expressed in frames + * Returns the playback head position expressed in frames. + * Though the "int" type is signed 32-bits, the value should be reinterpreted as if it is + * unsigned 32-bits. That is, the next position after 0x7FFFFFFF is (int) 0x80000000. + * This is a continuously advancing counter. It will wrap (overflow) periodically, + * for example approximately once every 27:03:11 hours:minutes:seconds at 44.1 kHz. + * It is reset to zero by flush(), reload(), and stop(). */ public int getPlaybackHeadPosition() { return native_get_position(); @@ -640,7 +655,7 @@ public class AudioTrack } /** - * Returns the hardware output sample rate + * Returns the output sample rate in Hz for the specified stream type. */ static public int getNativeOutputSampleRate(int streamType) { return native_get_output_sample_rate(streamType); @@ -651,7 +666,10 @@ public class AudioTrack * object to be created in the {@link #MODE_STREAM} mode. Note that this size doesn't * guarantee a smooth playback under load, and higher values should be chosen according to * the expected frequency at which the buffer will be refilled with additional data to play. - * @param sampleRateInHz the sample rate expressed in Hertz. + * For example, if you intend to dynamically set the source sample rate of an AudioTrack + * to a higher value than the initial source sample rate, be sure to configure the buffer size + * based on the highest planned sample rate. + * @param sampleRateInHz the source sample rate expressed in Hz. * @param channelConfig describes the configuration of the audio channels. * See {@link AudioFormat#CHANNEL_OUT_MONO} and * {@link AudioFormat#CHANNEL_OUT_STEREO} @@ -659,8 +677,7 @@ public class AudioTrack * See {@link AudioFormat#ENCODING_PCM_16BIT} and * {@link AudioFormat#ENCODING_PCM_8BIT} * @return {@link #ERROR_BAD_VALUE} if an invalid parameter was passed, - * or {@link #ERROR} if the implementation was unable to query the hardware for its output - * properties, + * or {@link #ERROR} if unable to query for output properties, * or the minimum buffer size expressed in bytes. */ static public int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat) { @@ -715,6 +732,45 @@ public class AudioTrack return mSessionId; } + /** + * Poll for a timestamp on demand. + * + * Use if you need to get the most recent timestamp outside of the event callback handler. + * Calling this method too often may be inefficient; + * if you need a high-resolution mapping between frame position and presentation time, + * consider implementing that at application level, based on low-resolution timestamps. + * The audio data at the returned position may either already have been + * presented, or may have not yet been presented but is committed to be presented. + * It is not possible to request the time corresponding to a particular position, + * or to request the (fractional) position corresponding to a particular time. + * If you need such features, consider implementing them at application level. + * + * @param timestamp a reference to a non-null AudioTimestamp instance allocated + * and owned by caller. + * @return true if a timestamp is available, or false if no timestamp is available. + * If a timestamp if available, + * the AudioTimestamp instance is filled in with a position in frame units, together + * with the estimated time when that frame was presented or is committed to + * be presented. + * In the case that no timestamp is available, any supplied instance is left unaltered. + */ + public boolean getTimestamp(AudioTimestamp timestamp) + { + if (timestamp == null) { + throw new IllegalArgumentException(); + } + // It's unfortunate, but we have to either create garbage every time or use synchronized + long[] longArray = new long[2]; + int ret = native_get_timestamp(longArray); + if (ret != SUCCESS) { + return false; + } + timestamp.framePosition = longArray[0]; + timestamp.nanoTime = longArray[1]; + return true; + } + + //-------------------------------------------------------------------------- // Initialization / configuration //-------------------- @@ -793,10 +849,13 @@ public class AudioTrack /** * Sets the playback sample rate for this track. This sets the sampling rate at which - * the audio data will be consumed and played back, not the original sampling rate of the - * content. Setting it to half the sample rate of the content will cause the playback to - * last twice as long, but will also result in a negative pitch shift. - * The valid sample rate range is from 1Hz to twice the value returned by + * the audio data will be consumed and played back + * (as set by the sampleRateInHz parameter in the + * {@link #AudioTrack(int, int, int, int, int, int)} constructor), + * not the original sampling rate of the + * content. For example, setting it to half the sample rate of the content will cause the + * playback to last twice as long, but will also result in a pitch shift down by one octave. + * The valid sample rate range is from 1 Hz to twice the value returned by * {@link #getNativeOutputSampleRate(int)}. * @param sampleRateInHz the sample rate expressed in Hz * @return error code or success, see {@link #SUCCESS}, {@link #ERROR_BAD_VALUE}, @@ -814,8 +873,11 @@ public class AudioTrack /** - * Sets the position of the notification marker. - * @param markerInFrames marker in frames + * Sets the position of the notification marker. At most one marker can be active. + * @param markerInFrames marker position in wrapping frame units similar to + * {@link #getPlaybackHeadPosition}, or zero to disable the marker. + * To set a marker at a position which would appear as zero due to wraparound, + * a workaround is to use a non-zero position near zero, such as -1 or 1. * @return error code or success, see {@link #SUCCESS}, {@link #ERROR_BAD_VALUE}, * {@link #ERROR_INVALID_OPERATION} */ @@ -845,6 +907,8 @@ public class AudioTrack * The track must be stopped or paused for the position to be changed, * and must use the {@link #MODE_STATIC} mode. * @param positionInFrames playback head position expressed in frames + * Zero corresponds to start of buffer. + * The position must not be greater than the buffer size in frames, or negative. * @return error code or success, see {@link #SUCCESS}, {@link #ERROR_BAD_VALUE}, * {@link #ERROR_INVALID_OPERATION} */ @@ -853,18 +917,28 @@ public class AudioTrack getPlayState() == PLAYSTATE_PLAYING) { return ERROR_INVALID_OPERATION; } + if (!(0 <= positionInFrames && positionInFrames <= mNativeBufferSizeInFrames)) { + return ERROR_BAD_VALUE; + } return native_set_position(positionInFrames); } /** * Sets the loop points and the loop count. The loop can be infinite. * Similarly to setPlaybackHeadPosition, - * the track must be stopped or paused for the position to be changed, + * the track must be stopped or paused for the loop points to be changed, * and must use the {@link #MODE_STATIC} mode. * @param startInFrames loop start marker expressed in frames + * Zero corresponds to start of buffer. + * The start marker must not be greater than or equal to the buffer size in frames, or negative. * @param endInFrames loop end marker expressed in frames + * The total buffer size in frames corresponds to end of buffer. + * The end marker must not be greater than the buffer size in frames. + * For looping, the end marker must not be less than or equal to the start marker, + * but to disable looping + * it is permitted for start marker, end marker, and loop count to all be 0. * @param loopCount the number of times the loop is looped. - * A value of -1 means infinite looping. + * A value of -1 means infinite looping, and 0 disables looping. * @return error code or success, see {@link #SUCCESS}, {@link #ERROR_BAD_VALUE}, * {@link #ERROR_INVALID_OPERATION} */ @@ -873,14 +947,23 @@ public class AudioTrack getPlayState() == PLAYSTATE_PLAYING) { return ERROR_INVALID_OPERATION; } + if (loopCount == 0) { + ; // explicitly allowed as an exception to the loop region range check + } else if (!(0 <= startInFrames && startInFrames < mNativeBufferSizeInFrames && + startInFrames < endInFrames && endInFrames <= mNativeBufferSizeInFrames)) { + return ERROR_BAD_VALUE; + } return native_set_loop(startInFrames, endInFrames, loopCount); } /** - * Sets the initialization state of the instance. To be used in an AudioTrack subclass - * constructor to set a subclass-specific post-initialization state. + * Sets the initialization state of the instance. This method was originally intended to be used + * in an AudioTrack subclass constructor to set a subclass-specific post-initialization state. + * However, subclasses of AudioTrack are no longer recommended, so this method is obsolete. * @param state the state of the AudioTrack instance + * @deprecated Only accessible by subclasses, which are not recommended for AudioTrack. */ + @Deprecated protected void setState(int state) { mState = state; } @@ -891,6 +974,7 @@ public class AudioTrack //-------------------- /** * Starts playing an AudioTrack. + * If track's creation mode is {@link #MODE_STATIC}, you must have called write() prior. * * @throws IllegalStateException */ @@ -955,7 +1039,8 @@ public class AudioTrack /** * Flushes the audio data currently queued for playback. Any data that has - * not been played back will be discarded. + * not been played back will be discarded. No-op if not stopped or paused, + * or if the track's creation mode is not {@link #MODE_STREAM}. */ public void flush() { if (mState == STATE_INITIALIZED) { @@ -966,11 +1051,13 @@ public class AudioTrack } /** - * Writes the audio data to the audio hardware for playback. Will block until - * all data has been written to the audio mixer. + * Writes the audio data to the audio sink for playback (streaming mode), + * or copies audio data for later playback (static buffer mode). + * In streaming mode, will block until all data has been written to the audio sink. + * In static buffer mode, copies the data to the buffer starting at offset 0. * Note that the actual playback of this data might occur after this function * returns. This function is thread safe with respect to {@link #stop} calls, - * in which case all of the specified data might not be written to the mixer. + * in which case all of the specified data might not be written to the audio sink. * * @param audioData the array that holds the data to play. * @param offsetInBytes the offset expressed in bytes in audioData where the data to play @@ -1007,16 +1094,18 @@ public class AudioTrack /** - * Writes the audio data to the audio hardware for playback. Will block until - * all data has been written to the audio mixer. + * Writes the audio data to the audio sink for playback (streaming mode), + * or copies audio data for later playback (static buffer mode). + * In streaming mode, will block until all data has been written to the audio sink. + * In static buffer mode, copies the data to the buffer starting at offset 0. * Note that the actual playback of this data might occur after this function * returns. This function is thread safe with respect to {@link #stop} calls, - * in which case all of the specified data might not be written to the mixer. + * in which case all of the specified data might not be written to the audio sink. * * @param audioData the array that holds the data to play. * @param offsetInShorts the offset expressed in shorts in audioData where the data to play * starts. - * @param sizeInShorts the number of bytes to read in audioData after the offset. + * @param sizeInShorts the number of shorts to read in audioData after the offset. * @return the number of shorts that were written or {@link #ERROR_INVALID_OPERATION} * if the object wasn't properly initialized, or {@link #ERROR_BAD_VALUE} if * the parameters don't resolve to valid data and indexes. @@ -1049,8 +1138,8 @@ public class AudioTrack /** * Notifies the native resource to reuse the audio data already loaded in the native - * layer. This call is only valid with AudioTrack instances that don't use the streaming - * model. + * layer, that is to rewind to start of buffer. + * The track's creation mode must be {@link #MODE_STATIC}. * @return error code or success, see {@link #SUCCESS}, {@link #ERROR_BAD_VALUE}, * {@link #ERROR_INVALID_OPERATION} */ @@ -1091,8 +1180,9 @@ public class AudioTrack /** * Sets the send level of the audio track to the attached auxiliary effect - * {@link #attachAuxEffect(int)}. The level value range is 0 to 1.0. - * <p>By default the send level is 0, so even if an effect is attached to the player + * {@link #attachAuxEffect(int)}. The level value range is 0.0f to 1.0f. + * Values are clamped to the (0.0f, 1.0f) interval if outside this range. + * <p>By default the send level is 0.0f, so even if an effect is attached to the player * this method must be called for the effect to be applied. * <p>Note that the passed level value is a raw scalar. UI controls should be scaled * logarithmically: the gain applied by audio framework ranges from -72dB to 0dB, @@ -1270,6 +1360,11 @@ public class AudioTrack private native final int native_get_latency(); + // longArray must be a non-null array of length >= 2 + // [0] is assigned the frame position + // [1] is assigned the time in CLOCK_MONOTONIC nanoseconds + private native final int native_get_timestamp(long[] longArray); + private native final int native_set_loop(int start, int end, int loopCount); static private native final int native_get_output_sample_rate(int streamType); diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java index 4cd3e37..20eb356 100644 --- a/media/java/android/media/ExifInterface.java +++ b/media/java/android/media/ExifInterface.java @@ -291,6 +291,20 @@ public class ExifInterface { } /** + * Returns the offset and length of thumbnail inside the JPEG file, or + * {@code null} if there is no thumbnail. + * + * @return two-element array, the offset in the first value, and length in + * the second, or {@code null} if no thumbnail was found. + * @hide + */ + public long[] getThumbnailRange() { + synchronized (sLock) { + return getThumbnailRangeNative(mFilename); + } + } + + /** * Stores the latitude and longitude value in a float array. The first element is * the latitude, and the second element is the longitude. Returns false if the * Exif tags are not available. @@ -416,4 +430,6 @@ public class ExifInterface { private native void commitChangesNative(String fileName); private native byte[] getThumbnailNative(String fileName); + + private native long[] getThumbnailRangeNative(String fileName); } diff --git a/media/java/android/media/FocusRequester.java b/media/java/android/media/FocusRequester.java new file mode 100644 index 0000000..9a39994 --- /dev/null +++ b/media/java/android/media/FocusRequester.java @@ -0,0 +1,244 @@ +/* + * Copyright (C) 2013 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.MediaFocusControl.AudioFocusDeathHandler; +import android.os.IBinder; +import android.util.Log; + +import java.io.PrintWriter; + +/** + * @hide + * Class to handle all the information about a user of audio focus. The lifecycle of each + * instance is managed by android.media.MediaFocusControl, from its addition to the audio focus + * stack to its release. + */ +class FocusRequester { + + // on purpose not using this classe's name, as it will only be used from MediaFocusControl + private static final String TAG = "MediaFocusControl"; + private static final boolean DEBUG = false; + + private AudioFocusDeathHandler mDeathHandler; + private final IAudioFocusDispatcher mFocusDispatcher; // may be null + private final IBinder mSourceRef; + private final String mClientId; + private final String mPackageName; + private final int mCallingUid; + /** + * the audio focus gain request that caused the addition of this object in the focus stack. + */ + private final int mFocusGainRequest; + /** + * the audio focus loss received my mFocusDispatcher, is AudioManager.AUDIOFOCUS_NONE if + * it never lost focus. + */ + private int mFocusLossReceived; + /** + * the stream type associated with the focus request + */ + private final int mStreamType; + + FocusRequester(int streamType, int focusRequest, + IAudioFocusDispatcher afl, IBinder source, String id, AudioFocusDeathHandler hdlr, + String pn, int uid) { + mStreamType = streamType; + mFocusDispatcher = afl; + mSourceRef = source; + mClientId = id; + mDeathHandler = hdlr; + mPackageName = pn; + mCallingUid = uid; + mFocusGainRequest = focusRequest; + mFocusLossReceived = AudioManager.AUDIOFOCUS_NONE; + } + + + boolean hasSameClient(String otherClient) { + try { + return mClientId.compareTo(otherClient) == 0; + } catch (NullPointerException e) { + return false; + } + } + + boolean hasSameBinder(IBinder ib) { + return (mSourceRef != null) && mSourceRef.equals(ib); + } + + boolean hasSamePackage(String pack) { + try { + return mPackageName.compareTo(pack) == 0; + } catch (NullPointerException e) { + return false; + } + } + + boolean hasSameUid(int uid) { + return mCallingUid == uid; + } + + + int getGainRequest() { + return mFocusGainRequest; + } + + int getStreamType() { + return mStreamType; + } + + + private static String focusChangeToString(int focus) { + switch(focus) { + case AudioManager.AUDIOFOCUS_NONE: + return "none"; + case AudioManager.AUDIOFOCUS_GAIN: + return "GAIN"; + case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT: + return "GAIN_TRANSIENT"; + case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK: + return "GAIN_TRANSIENT_MAY_DUCK"; + case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE: + return "GAIN_TRANSIENT_EXCLUSIVE"; + case AudioManager.AUDIOFOCUS_LOSS: + return "LOSS"; + case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: + return "LOSS_TRANSIENT"; + case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: + return "LOSS_TRANSIENT_CAN_DUCK"; + default: + return "[invalid focus change" + focus + "]"; + } + } + + private String focusGainToString() { + return focusChangeToString(mFocusGainRequest); + } + + private String focusLossToString() { + return focusChangeToString(mFocusLossReceived); + } + + void dump(PrintWriter pw) { + pw.println(" source:" + mSourceRef + + " -- pack: " + mPackageName + + " -- client: " + mClientId + + " -- gain: " + focusGainToString() + + " -- loss: " + focusLossToString() + + " -- uid: " + mCallingUid + + " -- stream: " + mStreamType); + } + + + void release() { + try { + if (mSourceRef != null && mDeathHandler != null) { + mSourceRef.unlinkToDeath(mDeathHandler, 0); + mDeathHandler = null; + } + } catch (java.util.NoSuchElementException e) { + Log.e(TAG, "FocusRequester.release() hit ", e); + } + } + + @Override + protected void finalize() throws Throwable { + release(); + super.finalize(); + } + + /** + * For a given audio focus gain request, return the audio focus loss type that will result + * from it, taking into account any previous focus loss. + * @param gainRequest + * @return the audio focus loss type that matches the gain request + */ + private int focusLossForGainRequest(int gainRequest) { + switch(gainRequest) { + case AudioManager.AUDIOFOCUS_GAIN: + switch(mFocusLossReceived) { + case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: + case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: + case AudioManager.AUDIOFOCUS_LOSS: + case AudioManager.AUDIOFOCUS_NONE: + return AudioManager.AUDIOFOCUS_LOSS; + } + case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE: + case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT: + switch(mFocusLossReceived) { + case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: + case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: + case AudioManager.AUDIOFOCUS_NONE: + return AudioManager.AUDIOFOCUS_LOSS_TRANSIENT; + case AudioManager.AUDIOFOCUS_LOSS: + return AudioManager.AUDIOFOCUS_LOSS; + } + case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK: + switch(mFocusLossReceived) { + case AudioManager.AUDIOFOCUS_NONE: + case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: + return AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK; + case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: + return AudioManager.AUDIOFOCUS_LOSS_TRANSIENT; + case AudioManager.AUDIOFOCUS_LOSS: + return AudioManager.AUDIOFOCUS_LOSS; + } + default: + Log.e(TAG, "focusLossForGainRequest() for invalid focus request "+ gainRequest); + return AudioManager.AUDIOFOCUS_NONE; + } + } + + void handleExternalFocusGain(int focusGain) { + int focusLoss = focusLossForGainRequest(focusGain); + handleFocusLoss(focusLoss); + } + + void handleFocusGain(int focusGain) { + try { + if (mFocusDispatcher != null) { + if (DEBUG) { + Log.v(TAG, "dispatching " + focusChangeToString(focusGain) + " to " + + mClientId); + } + mFocusDispatcher.dispatchAudioFocusChange(focusGain, mClientId); + } + mFocusLossReceived = AudioManager.AUDIOFOCUS_NONE; + } catch (android.os.RemoteException e) { + Log.e(TAG, "Failure to signal gain of audio focus due to: ", e); + } + } + + void handleFocusLoss(int focusLoss) { + try { + if (focusLoss != mFocusLossReceived) { + if (mFocusDispatcher != null) { + if (DEBUG) { + Log.v(TAG, "dispatching " + focusChangeToString(focusLoss) + " to " + + mClientId); + } + mFocusDispatcher.dispatchAudioFocusChange(focusLoss, mClientId); + } + mFocusLossReceived = focusLoss; + } + } catch (android.os.RemoteException e) { + Log.e(TAG, "Failure to signal loss of audio focus due to:", e); + } + } + +} diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index fda8c1b..2f08325 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -26,6 +26,7 @@ import android.media.IRemoteControlClient; import android.media.IRemoteControlDisplay; import android.media.IRemoteVolumeObserver; import android.media.IRingtonePlayer; +import android.media.Rating; import android.net.Uri; import android.view.KeyEvent; @@ -33,25 +34,29 @@ import android.view.KeyEvent; * {@hide} */ interface IAudioService { - - void adjustVolume(int direction, int flags); - oneway void adjustLocalOrRemoteStreamVolume(int streamType, int direction); + void adjustVolume(int direction, int flags, String callingPackage); - void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags); + boolean isLocalOrRemoteMusicActive(); - void adjustStreamVolume(int streamType, int direction, int flags); + oneway void adjustLocalOrRemoteStreamVolume(int streamType, int direction, + String callingPackage); - void adjustMasterVolume(int direction, int flags); + void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags, + String callingPackage); - void setStreamVolume(int streamType, int index, int flags); + void adjustStreamVolume(int streamType, int direction, int flags, String callingPackage); + + void adjustMasterVolume(int direction, int flags, String callingPackage); + + void setStreamVolume(int streamType, int index, int flags, String callingPackage); oneway void setRemoteStreamVolume(int index); - void setMasterVolume(int index, int flags); - + void setMasterVolume(int index, int flags, String callingPackage); + void setStreamSolo(int streamType, boolean state, IBinder cb); - + void setStreamMute(int streamType, boolean state, IBinder cb); boolean isStreamMute(int streamType); @@ -67,19 +72,19 @@ interface IAudioService { int getStreamMaxVolume(int streamType); int getMasterMaxVolume(); - + int getLastAudibleStreamVolume(int streamType); int getLastAudibleMasterVolume(); void setRingerMode(int ringerMode); - + int getRingerMode(); void setVibrateSetting(int vibrateType, int vibrateSetting); - + int getVibrateSetting(int vibrateType); - + boolean shouldVibrate(int vibrateType); void setMode(int mode, IBinder cb); @@ -87,15 +92,17 @@ interface IAudioService { int getMode(); oneway void playSoundEffect(int effectType); - + oneway void playSoundEffectVolume(int effectType, float volume); boolean loadSoundEffects(); - + oneway void unloadSoundEffects(); oneway void reloadAudioSettings(); + oneway void avrcpSupportsAbsoluteVolume(String address, boolean support); + void setSpeakerphoneOn(boolean on); boolean isSpeakerphoneOn(); @@ -108,15 +115,15 @@ interface IAudioService { boolean isBluetoothA2dpOn(); - oneway void setRemoteSubmixOn(boolean on, int address); + int requestAudioFocus(int mainStreamType, int durationHint, IBinder cb, + IAudioFocusDispatcher fd, String clientId, String callingPackageName); - int requestAudioFocus(int mainStreamType, int durationHint, IBinder cb, IAudioFocusDispatcher l, - String clientId, String callingPackageName); + int abandonAudioFocus(IAudioFocusDispatcher fd, String clientId); - int abandonAudioFocus(IAudioFocusDispatcher l, String clientId); - void unregisterAudioFocusClient(String clientId); + int getCurrentAudioFocus(); + oneway void dispatchMediaKeyEvent(in KeyEvent keyEvent); void dispatchMediaKeyEventUnderWakelock(in KeyEvent keyEvent); @@ -128,6 +135,8 @@ interface IAudioService { /** * Register an IRemoteControlDisplay. + * Success of registration is subject to a check on + * the android.Manifest.permission.MEDIA_CONTENT_CONTROL permission. * Notify all IRemoteControlClient of the new display and cause the RemoteControlClient * at the top of the stack to update the new display with its information. * @param rcd the IRemoteControlDisplay to register. No effect if null. @@ -136,7 +145,17 @@ interface IAudioService { * @param h the maximum height of the expected bitmap. Negative or zero values indicate this * display doesn't need to receive artwork. */ - oneway void registerRemoteControlDisplay(in IRemoteControlDisplay rcd, int w, int h); + boolean registerRemoteControlDisplay(in IRemoteControlDisplay rcd, int w, int h); + + /** + * Like registerRemoteControlDisplay, but with success being subject to a check on + * the android.Manifest.permission.MEDIA_CONTENT_CONTROL permission, and if it fails, + * success is subject to listenerComp being one of the ENABLED_NOTIFICATION_LISTENERS + * components. + */ + boolean registerRemoteController(in IRemoteControlDisplay rcd, int w, int h, + in ComponentName listenerComp); + /** * Unregister an IRemoteControlDisplay. * No effect if the IRemoteControlDisplay hasn't been successfully registered. @@ -173,6 +192,15 @@ interface IAudioService { * @param timeMs the time in ms to seek to, must be positive. */ void setRemoteControlClientPlaybackPosition(int generationId, long timeMs); + /** + * Notify the user of a RemoteControlClient that it should update its metadata with the + * new value for the given key. + * @param generationId the RemoteControlClient generation counter for which this request is + * issued. Requests for an older generation than current one will be ignored. + * @param key the metadata key for which a new value exists + * @param value the new metadata value + */ + void updateRemoteControlClientMetadata(int generationId, int key, in Rating value); /** * Do not use directly, use instead diff --git a/media/java/android/media/IRemoteControlClient.aidl b/media/java/android/media/IRemoteControlClient.aidl index 2236129..aa142d6 100644 --- a/media/java/android/media/IRemoteControlClient.aidl +++ b/media/java/android/media/IRemoteControlClient.aidl @@ -17,6 +17,7 @@ package android.media; import android.graphics.Bitmap; import android.media.IRemoteControlDisplay; +import android.media.Rating; /** * @hide @@ -40,6 +41,13 @@ oneway interface IRemoteControlClient void onInformationRequested(int generationId, int infoFlags); /** + * Notifies a remote control client that information for the given generation ID is + * requested for the given IRemoteControlDisplay alone. + * @param rcd the display to which current info should be sent + */ + void informationRequestForDisplay(IRemoteControlDisplay rcd, int w, int h); + + /** * Sets the generation counter of the current client that is displayed on the remote control. */ void setCurrentClientGenerationId(int clientGeneration); @@ -48,5 +56,7 @@ oneway interface IRemoteControlClient void unplugRemoteControlDisplay(IRemoteControlDisplay rcd); void setBitmapSizeForDisplay(IRemoteControlDisplay rcd, int w, int h); void setWantsSyncForDisplay(IRemoteControlDisplay rcd, boolean wantsSync); + void enableRemoteControlDisplay(IRemoteControlDisplay rcd, boolean enabled); void seekTo(int clientGeneration, long timeMs); + void updateMetadata(int clientGeneration, int key, in Rating value); }
\ No newline at end of file diff --git a/media/java/android/media/IRemoteControlDisplay.aidl b/media/java/android/media/IRemoteControlDisplay.aidl index 583f436..1609030 100644 --- a/media/java/android/media/IRemoteControlDisplay.aidl +++ b/media/java/android/media/IRemoteControlDisplay.aidl @@ -41,6 +41,12 @@ oneway interface IRemoteControlDisplay boolean clearing); /** + * Sets whether the controls of this display are enabled + * @param if false, the display shouldn't any commands + */ + void setEnabled(boolean enabled); + + /** * Sets the playback information (state, position and speed) of a client. * @param generationId the current generation ID as known by this client * @param state the current playback state, one of the following values: diff --git a/media/java/android/media/Image.java b/media/java/android/media/Image.java new file mode 100644 index 0000000..23abce7 --- /dev/null +++ b/media/java/android/media/Image.java @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media; + +import java.nio.ByteBuffer; +import java.lang.AutoCloseable; + +/** + * <p>A single complete image buffer to use with a media source such as a + * {@link MediaCodec}.</p> + * + * <p>This class allows for efficient direct application access to the pixel + * data of the Image through one or more + * {@link java.nio.ByteBuffer ByteBuffers}. Each buffer is encapsulated in a + * {@link Plane} that describes the layout of the pixel data in that plane. Due + * to this direct access, and unlike the {@link android.graphics.Bitmap Bitmap} class, + * Images are not directly usable as as UI resources.</p> + * + * <p>Since Images are often directly produced or consumed by hardware + * components, they are a limited resource shared across the system, and should + * be closed as soon as they are no longer needed.</p> + * + * <p>For example, when using the {@link ImageReader} class to read out Images + * from various media sources, not closing old Image objects will prevent the + * availability of new Images once + * {@link ImageReader#getMaxImages the maximum outstanding image count} is + * reached. When this happens, the function acquiring new Images will typically + * throw an {@link IllegalStateException}.</p> + * + * @see ImageReader + */ +public abstract class Image implements AutoCloseable { + /** + * @hide + */ + protected Image() { + } + + /** + * Get the format for this image. This format determines the number of + * ByteBuffers needed to represent the image, and the general layout of the + * pixel data in each in ByteBuffer. + * + * <p> + * The format is one of the values from + * {@link android.graphics.ImageFormat ImageFormat}. The mapping between the + * formats and the planes is as follows: + * </p> + * + * <table> + * <tr> + * <th>Format</th> + * <th>Plane count</th> + * <th>Layout details</th> + * </tr> + * <tr> + * <td>{@link android.graphics.ImageFormat#JPEG JPEG}</td> + * <td>1</td> + * <td>Compressed data, so row and pixel strides are 0. To uncompress, use + * {@link android.graphics.BitmapFactory#decodeByteArray BitmapFactory#decodeByteArray}. + * </td> + * </tr> + * <tr> + * <td>{@link android.graphics.ImageFormat#YUV_420_888 YUV_420_888}</td> + * <td>3</td> + * <td>A luminance plane followed by the Cb and Cr chroma planes. + * The chroma planes have half the width and height of the luminance + * plane (4:2:0 subsampling). Each pixel sample in each plane has 8 bits. + * Each plane has its own row stride and pixel stride.</td> + * </tr> + * </table> + * + * @see android.graphics.ImageFormat + */ + public abstract int getFormat(); + + /** + * The width of the image in pixels. For formats where some color channels + * are subsampled, this is the width of the largest-resolution plane. + */ + public abstract int getWidth(); + + /** + * The height of the image in pixels. For formats where some color channels + * are subsampled, this is the height of the largest-resolution plane. + */ + public abstract int getHeight(); + + /** + * Get the timestamp associated with this frame. + * <p> + * The timestamp is measured in nanoseconds, and is monotonically + * increasing. However, the zero point and whether the timestamp can be + * compared against other sources of time or images depend on the source of + * this image. + * </p> + */ + public abstract long getTimestamp(); + + /** + * Get the array of pixel planes for this Image. The number of planes is + * determined by the format of the Image. + */ + public abstract Plane[] getPlanes(); + + /** + * Free up this frame for reuse. + * <p> + * After calling this method, calling any methods on this {@code Image} will + * result in an {@link IllegalStateException}, and attempting to read from + * {@link ByteBuffer ByteBuffers} returned by an earlier + * {@link Plane#getBuffer} call will have undefined behavior. + * </p> + */ + @Override + public abstract void close(); + + /** + * <p>A single color plane of image data.</p> + * + * <p>The number and meaning of the planes in an Image are determined by the + * format of the Image.</p> + * + * <p>Once the Image has been closed, any access to the the plane's + * ByteBuffer will fail.</p> + * + * @see #getFormat + */ + public static abstract class Plane { + /** + * @hide + */ + protected Plane() { + } + + /** + * <p>The row stride for this color plane, in bytes.</p> + * + * <p>This is the distance between the start of two consecutive rows of + * pixels in the image. The row stride is always greater than 0.</p> + */ + public abstract int getRowStride(); + /** + * <p>The distance between adjacent pixel samples, in bytes.</p> + * + * <p>This is the distance between two consecutive pixel values in a row + * of pixels. It may be larger than the size of a single pixel to + * account for interleaved image data or padded formats. + * The pixel stride is always greater than 0.</p> + */ + public abstract int getPixelStride(); + /** + * <p>Get a direct {@link java.nio.ByteBuffer ByteBuffer} + * containing the frame data.</p> + * + * <p>In particular, the buffer returned will always have + * {@link java.nio.ByteBuffer#isDirect isDirect} return {@code true}, so + * the underlying data could be mapped as a pointer in JNI without doing + * any copies with {@code GetDirectBufferAddress}.</p> + * + * @return the byte buffer containing the image data for this plane. + */ + public abstract ByteBuffer getBuffer(); + } + +} diff --git a/media/java/android/media/ImageReader.java b/media/java/android/media/ImageReader.java new file mode 100644 index 0000000..d454c42 --- /dev/null +++ b/media/java/android/media/ImageReader.java @@ -0,0 +1,736 @@ +/* + * Copyright (C) 2013 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.ImageFormat; +import android.graphics.PixelFormat; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.view.Surface; + +import java.lang.ref.WeakReference; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * <p>The ImageReader class allows direct application access to image data + * rendered into a {@link android.view.Surface}</p> + * + * <p>Several Android media API classes accept Surface objects as targets to + * render to, including {@link MediaPlayer}, {@link MediaCodec}, and + * {@link android.renderscript.Allocation RenderScript Allocations}. The image + * sizes and formats that can be used with each source vary, and should be + * checked in the documentation for the specific API.</p> + * + * <p>The image data is encapsulated in {@link Image} objects, and multiple such + * objects can be accessed at the same time, up to the number specified by the + * {@code maxImages} constructor parameter. New images sent to an ImageReader + * through its {@link Surface} are queued until accessed through the {@link #acquireLatestImage} + * or {@link #acquireNextImage} call. Due to memory limits, an image source will + * eventually stall or drop Images in trying to render to the Surface if the + * ImageReader does not obtain and release Images at a rate equal to the + * production rate.</p> + */ +public class ImageReader implements AutoCloseable { + + /** + * Returned by nativeImageSetup when acquiring the image was successful. + */ + private static final int ACQUIRE_SUCCESS = 0; + /** + * Returned by nativeImageSetup when we couldn't acquire the buffer, + * because there were no buffers available to acquire. + */ + private static final int ACQUIRE_NO_BUFS = 1; + /** + * Returned by nativeImageSetup when we couldn't acquire the buffer + * because the consumer has already acquired {@maxImages} and cannot + * acquire more than that. + */ + private static final int ACQUIRE_MAX_IMAGES = 2; + + /** + * <p>Create a new reader for images of the desired size and format.</p> + * + * <p>The {@code maxImages} parameter determines the maximum number of {@link Image} + * objects that can be be acquired from the {@code ImageReader} + * simultaneously. Requesting more buffers will use up more memory, so it is + * important to use only the minimum number necessary for the use case.</p> + * + * <p>The valid sizes and formats depend on the source of the image + * data.</p> + * + * @param width + * The width in pixels of the Images that this reader will produce. + * @param height + * The height in pixels of the Images that this reader will produce. + * @param format + * The format of the Image that this reader will produce. This + * must be one of the {@link android.graphics.ImageFormat} or + * {@link android.graphics.PixelFormat} constants. Note that + * not all formats is supported, like ImageFormat.NV21. + * @param maxImages + * The maximum number of images the user will want to + * access simultaneously. This should be as small as possible to limit + * memory use. Once maxImages Images are obtained by the user, one of them + * has to be released before a new Image will become available for access + * through {@link #acquireLatestImage()} or {@link #acquireNextImage()}. + * Must be greater than 0. + * + * @see Image + */ + public static ImageReader newInstance(int width, int height, int format, int maxImages) { + return new ImageReader(width, height, format, maxImages); + } + + /** + * @hide + */ + protected ImageReader(int width, int height, int format, int maxImages) { + mWidth = width; + mHeight = height; + mFormat = format; + mMaxImages = maxImages; + + if (width < 1 || height < 1) { + throw new IllegalArgumentException( + "The image dimensions must be positive"); + } + if (mMaxImages < 1) { + throw new IllegalArgumentException( + "Maximum outstanding image count must be at least 1"); + } + + if (format == ImageFormat.NV21) { + throw new IllegalArgumentException( + "NV21 format is not supported"); + } + + mNumPlanes = getNumPlanesFromFormat(); + + nativeInit(new WeakReference<ImageReader>(this), width, height, format, maxImages); + + mSurface = nativeGetSurface(); + } + + /** + * The width of each {@link Image}, in pixels. + * + * <p>ImageReader guarantees that all Images acquired from ImageReader (for example, with + * {@link #acquireNextImage}) will have the same dimensions as specified in + * {@link #newInstance}.</p> + * + * @return the width of an Image + */ + public int getWidth() { + return mWidth; + } + + /** + * The height of each {@link Image}, in pixels. + * + * <p>ImageReader guarantees that all Images acquired from ImageReader (for example, with + * {@link #acquireNextImage}) will have the same dimensions as specified in + * {@link #newInstance}.</p> + * + * @return the height of an Image + */ + public int getHeight() { + return mHeight; + } + + /** + * The {@link ImageFormat image format} of each Image. + * + * <p>ImageReader guarantees that all {@link Image Images} acquired from ImageReader + * (for example, with {@link #acquireNextImage}) will have the same format as specified in + * {@link #newInstance}.</p> + * + * @return the format of an Image + * + * @see ImageFormat + */ + public int getImageFormat() { + return mFormat; + } + + /** + * Maximum number of images that can be acquired from the ImageReader by any time (for example, + * with {@link #acquireNextImage}). + * + * <p>An image is considered acquired after it's returned by a function from ImageReader, and + * until the Image is {@link Image#close closed} to release the image back to the ImageReader. + * </p> + * + * <p>Attempting to acquire more than {@code maxImages} concurrently will result in the + * acquire function throwing a {@link IllegalStateException}. Furthermore, + * while the max number of images have been acquired by the ImageReader user, the producer + * enqueueing additional images may stall until at least one image has been released. </p> + * + * @return Maximum number of images for this ImageReader. + * + * @see Image#close + */ + public int getMaxImages() { + return mMaxImages; + } + + /** + * <p>Get a {@link Surface} that can be used to produce {@link Image Images} for this + * {@code ImageReader}.</p> + * + * <p>Until valid image data is rendered into this {@link Surface}, the + * {@link #acquireNextImage} method will return {@code null}. Only one source + * can be producing data into this Surface at the same time, although the + * same {@link Surface} can be reused with a different API once the first source is + * disconnected from the {@link Surface}.</p> + * + * @return A {@link Surface} to use for a drawing target for various APIs. + */ + public Surface getSurface() { + return mSurface; + } + + /** + * <p> + * Acquire the latest {@link Image} from the ImageReader's queue, dropping older + * {@link Image images}. Returns {@code null} if no new image is available. + * </p> + * <p> + * This operation will acquire all the images possible from the ImageReader, + * but {@link #close} all images that aren't the latest. This function is + * recommended to use over {@link #acquireNextImage} for most use-cases, as it's + * more suited for real-time processing. + * </p> + * <p> + * Note that {@link #getMaxImages maxImages} should be at least 2 for + * {@link #acquireLatestImage} to be any different than {@link #acquireNextImage} - + * discarding all-but-the-newest {@link Image} requires temporarily acquiring two + * {@link Image Images} at once. Or more generally, calling {@link #acquireLatestImage} + * with less than two images of margin, that is + * {@code (maxImages - currentAcquiredImages < 2)} will not discard as expected. + * </p> + * <p> + * This operation will fail by throwing an {@link IllegalStateException} if + * {@code maxImages} have been acquired with {@link #acquireLatestImage} or + * {@link #acquireNextImage}. In particular a sequence of {@link #acquireLatestImage} + * calls greater than {@link #getMaxImages} without calling {@link Image#close} in-between + * will exhaust the underlying queue. At such a time, {@link IllegalStateException} + * will be thrown until more images are + * released with {@link Image#close}. + * </p> + * + * @return latest frame of image data, or {@code null} if no image data is available. + * @throws IllegalStateException if too many images are currently acquired + */ + public Image acquireLatestImage() { + Image image = acquireNextImage(); + if (image == null) { + return null; + } + try { + for (;;) { + Image next = acquireNextImageNoThrowISE(); + if (next == null) { + Image result = image; + image = null; + return result; + } + image.close(); + image = next; + } + } finally { + if (image != null) { + image.close(); + } + } + } + + /** + * Don't throw IllegalStateException if there are too many images acquired. + * + * @return Image if acquiring succeeded, or null otherwise. + * + * @hide + */ + public Image acquireNextImageNoThrowISE() { + SurfaceImage si = new SurfaceImage(); + return acquireNextSurfaceImage(si) == ACQUIRE_SUCCESS ? si : null; + } + + /** + * Attempts to acquire the next image from the underlying native implementation. + * + * <p> + * Note that unexpected failures will throw at the JNI level. + * </p> + * + * @param si A blank SurfaceImage. + * @return One of the {@code ACQUIRE_*} codes that determine success or failure. + * + * @see #ACQUIRE_MAX_IMAGES + * @see #ACQUIRE_NO_BUFS + * @see #ACQUIRE_SUCCESS + */ + private int acquireNextSurfaceImage(SurfaceImage si) { + + int status = nativeImageSetup(si); + + switch (status) { + case ACQUIRE_SUCCESS: + si.createSurfacePlanes(); + si.setImageValid(true); + case ACQUIRE_NO_BUFS: + case ACQUIRE_MAX_IMAGES: + break; + default: + throw new AssertionError("Unknown nativeImageSetup return code " + status); + } + + return status; + } + + /** + * <p> + * Acquire the next Image from the ImageReader's queue. Returns {@code null} if + * no new image is available. + * </p> + * + * <p><i>Warning:</i> Consider using {@link #acquireLatestImage()} instead, as it will + * automatically release older images, and allow slower-running processing routines to catch + * up to the newest frame. Usage of {@link #acquireNextImage} is recommended for + * batch/background processing. Incorrectly using this function can cause images to appear + * with an ever-increasing delay, followed by a complete stall where no new images seem to + * appear. + * </p> + * + * <p> + * This operation will fail by throwing an {@link IllegalStateException} if + * {@code maxImages} have been acquired with {@link #acquireNextImage} or + * {@link #acquireLatestImage}. In particular a sequence of {@link #acquireNextImage} or + * {@link #acquireLatestImage} calls greater than {@link #getMaxImages maxImages} without + * calling {@link Image#close} in-between will exhaust the underlying queue. At such a time, + * {@link IllegalStateException} will be thrown until more images are released with + * {@link Image#close}. + * </p> + * + * @return a new frame of image data, or {@code null} if no image data is available. + * @throws IllegalStateException if {@code maxImages} images are currently acquired + * @see #acquireLatestImage + */ + public Image acquireNextImage() { + SurfaceImage si = new SurfaceImage(); + int status = acquireNextSurfaceImage(si); + + switch (status) { + case ACQUIRE_SUCCESS: + return si; + case ACQUIRE_NO_BUFS: + return null; + case ACQUIRE_MAX_IMAGES: + throw new IllegalStateException( + String.format( + "maxImages (%d) has already been acquired, " + + "call #close before acquiring more.", mMaxImages)); + default: + throw new AssertionError("Unknown nativeImageSetup return code " + status); + } + } + + /** + * <p>Return the frame to the ImageReader for reuse.</p> + */ + private void releaseImage(Image i) { + if (! (i instanceof SurfaceImage) ) { + throw new IllegalArgumentException( + "This image was not produced by an ImageReader"); + } + SurfaceImage si = (SurfaceImage) i; + if (si.getReader() != this) { + throw new IllegalArgumentException( + "This image was not produced by this ImageReader"); + } + + si.clearSurfacePlanes(); + nativeReleaseImage(i); + si.setImageValid(false); + } + + /** + * Register a listener to be invoked when a new image becomes available + * from the ImageReader. + * + * @param listener + * The listener that will be run. + * @param handler + * The handler on which the listener should be invoked, or null + * if the listener should be invoked on the calling thread's looper. + * @throws IllegalArgumentException + * If no handler specified and the calling thread has no looper. + */ + public void setOnImageAvailableListener(OnImageAvailableListener listener, Handler handler) { + synchronized (mListenerLock) { + if (listener != null) { + Looper looper = handler != null ? handler.getLooper() : Looper.myLooper(); + if (looper == null) { + throw new IllegalArgumentException( + "handler is null but the current thread is not a looper"); + } + if (mListenerHandler == null || mListenerHandler.getLooper() != looper) { + mListenerHandler = new ListenerHandler(looper); + } + mListener = listener; + } else { + mListener = null; + mListenerHandler = null; + } + } + } + + /** + * Callback interface for being notified that a new image is available. + * + * <p> + * The onImageAvailable is called per image basis, that is, callback fires for every new frame + * available from ImageReader. + * </p> + */ + public interface OnImageAvailableListener { + /** + * Callback that is called when a new image is available from ImageReader. + * + * @param reader the ImageReader the callback is associated with. + * @see ImageReader + * @see Image + */ + void onImageAvailable(ImageReader reader); + } + + /** + * Free up all the resources associated with this ImageReader. + * + * <p> + * After calling this method, this ImageReader can not be used. Calling + * any methods on this ImageReader and Images previously provided by + * {@link #acquireNextImage} or {@link #acquireLatestImage} + * will result in an {@link IllegalStateException}, and attempting to read from + * {@link ByteBuffer ByteBuffers} returned by an earlier + * {@link Image.Plane#getBuffer Plane#getBuffer} call will + * have undefined behavior. + * </p> + */ + @Override + public void close() { + setOnImageAvailableListener(null, null); + nativeClose(); + } + + @Override + protected void finalize() throws Throwable { + try { + close(); + } finally { + super.finalize(); + } + } + + /** + * Only a subset of the formats defined in + * {@link android.graphics.ImageFormat ImageFormat} and + * {@link android.graphics.PixelFormat PixelFormat} are supported by + * ImageReader. When reading RGB data from a surface, the formats defined in + * {@link android.graphics.PixelFormat PixelFormat} can be used, when + * reading YUV, JPEG or raw sensor data (for example, from camera or video + * decoder), formats from {@link android.graphics.ImageFormat ImageFormat} + * are used. + */ + private int getNumPlanesFromFormat() { + switch (mFormat) { + case ImageFormat.YV12: + case ImageFormat.YUV_420_888: + case ImageFormat.NV21: + return 3; + case ImageFormat.NV16: + return 2; + case PixelFormat.RGB_565: + case PixelFormat.RGBA_8888: + case PixelFormat.RGBX_8888: + case PixelFormat.RGB_888: + case ImageFormat.JPEG: + case ImageFormat.YUY2: + case ImageFormat.Y8: + case ImageFormat.Y16: + case ImageFormat.RAW_SENSOR: + return 1; + default: + throw new UnsupportedOperationException( + String.format("Invalid format specified %d", mFormat)); + } + } + + /** + * Called from Native code when an Event happens. + * + * This may be called from an arbitrary Binder thread, so access to the ImageReader must be + * synchronized appropriately. + */ + private static void postEventFromNative(Object selfRef) { + @SuppressWarnings("unchecked") + WeakReference<ImageReader> weakSelf = (WeakReference<ImageReader>)selfRef; + final ImageReader ir = weakSelf.get(); + if (ir == null) { + return; + } + + final Handler handler; + synchronized (ir.mListenerLock) { + handler = ir.mListenerHandler; + } + if (handler != null) { + handler.sendEmptyMessage(0); + } + } + + + private final int mWidth; + private final int mHeight; + private final int mFormat; + private final int mMaxImages; + private final int mNumPlanes; + private final Surface mSurface; + + private final Object mListenerLock = new Object(); + private OnImageAvailableListener mListener; + private ListenerHandler mListenerHandler; + + /** + * This field is used by native code, do not access or modify. + */ + private long mNativeContext; + + /** + * This custom handler runs asynchronously so callbacks don't get queued behind UI messages. + */ + private final class ListenerHandler extends Handler { + public ListenerHandler(Looper looper) { + super(looper, null, true /*async*/); + } + + @Override + public void handleMessage(Message msg) { + OnImageAvailableListener listener; + synchronized (mListenerLock) { + listener = mListener; + } + if (listener != null) { + listener.onImageAvailable(ImageReader.this); + } + } + } + + private class SurfaceImage extends android.media.Image { + public SurfaceImage() { + mIsImageValid = false; + } + + @Override + public void close() { + if (mIsImageValid) { + ImageReader.this.releaseImage(this); + } + } + + public ImageReader getReader() { + return ImageReader.this; + } + + @Override + public int getFormat() { + if (mIsImageValid) { + return ImageReader.this.mFormat; + } else { + throw new IllegalStateException("Image is already released"); + } + } + + @Override + public int getWidth() { + if (mIsImageValid) { + return ImageReader.this.mWidth; + } else { + throw new IllegalStateException("Image is already released"); + } + } + + @Override + public int getHeight() { + if (mIsImageValid) { + return ImageReader.this.mHeight; + } else { + throw new IllegalStateException("Image is already released"); + } + } + + @Override + public long getTimestamp() { + if (mIsImageValid) { + return mTimestamp; + } else { + throw new IllegalStateException("Image is already released"); + } + } + + @Override + public Plane[] getPlanes() { + if (mIsImageValid) { + // Shallow copy is fine. + return mPlanes.clone(); + } else { + throw new IllegalStateException("Image is already released"); + } + } + + @Override + protected final void finalize() throws Throwable { + try { + close(); + } finally { + super.finalize(); + } + } + + private void setImageValid(boolean isValid) { + mIsImageValid = isValid; + } + + private boolean isImageValid() { + return mIsImageValid; + } + + private void clearSurfacePlanes() { + if (mIsImageValid) { + for (int i = 0; i < mPlanes.length; i++) { + if (mPlanes[i] != null) { + mPlanes[i].clearBuffer(); + mPlanes[i] = null; + } + } + } + } + + private void createSurfacePlanes() { + mPlanes = new SurfacePlane[ImageReader.this.mNumPlanes]; + for (int i = 0; i < ImageReader.this.mNumPlanes; i++) { + mPlanes[i] = nativeCreatePlane(i); + } + } + private class SurfacePlane extends android.media.Image.Plane { + // SurfacePlane instance is created by native code when a new SurfaceImage is created + private SurfacePlane(int index, int rowStride, int pixelStride) { + mIndex = index; + mRowStride = rowStride; + mPixelStride = pixelStride; + } + + @Override + public ByteBuffer getBuffer() { + if (SurfaceImage.this.isImageValid() == false) { + throw new IllegalStateException("Image is already released"); + } + if (mBuffer != null) { + return mBuffer; + } else { + mBuffer = SurfaceImage.this.nativeImageGetBuffer(mIndex); + // Set the byteBuffer order according to host endianness (native order), + // otherwise, the byteBuffer order defaults to ByteOrder.BIG_ENDIAN. + return mBuffer.order(ByteOrder.nativeOrder()); + } + } + + @Override + public int getPixelStride() { + if (SurfaceImage.this.isImageValid()) { + return mPixelStride; + } else { + throw new IllegalStateException("Image is already released"); + } + } + + @Override + public int getRowStride() { + if (SurfaceImage.this.isImageValid()) { + return mRowStride; + } else { + throw new IllegalStateException("Image is already released"); + } + } + + private void clearBuffer() { + mBuffer = null; + } + + final private int mIndex; + final private int mPixelStride; + final private int mRowStride; + + private ByteBuffer mBuffer; + } + + /** + * This field is used to keep track of native object and used by native code only. + * Don't modify. + */ + private long mLockedBuffer; + + /** + * This field is set by native code during nativeImageSetup(). + */ + private long mTimestamp; + + private SurfacePlane[] mPlanes; + private boolean mIsImageValid; + + private synchronized native ByteBuffer nativeImageGetBuffer(int idx); + private synchronized native SurfacePlane nativeCreatePlane(int idx); + } + + private synchronized native void nativeInit(Object weakSelf, int w, int h, + int fmt, int maxImgs); + private synchronized native void nativeClose(); + private synchronized native void nativeReleaseImage(Image i); + private synchronized native Surface nativeGetSurface(); + + /** + * @return A return code {@code ACQUIRE_*} + * + * @see #ACQUIRE_SUCCESS + * @see #ACQUIRE_NO_BUFS + * @see #ACQUIRE_MAX_IMAGES + */ + private synchronized native int nativeImageSetup(Image i); + + /** + * We use a class initializer to allow the native code to cache some + * field offsets. + */ + private static native void nativeClassInit(); + static { + System.loadLibrary("media_jni"); + nativeClassInit(); + } +} diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java index d703642..5175830 100644 --- a/media/java/android/media/MediaCodec.java +++ b/media/java/android/media/MediaCodec.java @@ -20,7 +20,9 @@ import android.media.MediaCodecInfo; import android.media.MediaCodecList; import android.media.MediaCrypto; import android.media.MediaFormat; +import android.os.Bundle; import android.view.Surface; + import java.nio.ByteBuffer; import java.util.Arrays; import java.util.Map; @@ -164,7 +166,8 @@ final public class MediaCodec { * * The following is a partial list of defined mime types and their semantics: * <ul> - * <li>"video/x-vnd.on2.vp8" - VPX video (i.e. video in .webm) + * <li>"video/x-vnd.on2.vp8" - VP8 video (i.e. video in .webm) + * <li>"video/x-vnd.on2.vp9" - VP9 video (i.e. video in .webm) * <li>"video/avc" - H.264/AVC video * <li>"video/mp4v-es" - MPEG4 video * <li>"video/3gpp" - H.263 video @@ -293,12 +296,36 @@ final public class MediaCodec { */ public native final void flush(); + /** + * Thrown when a crypto error occurs while queueing a secure input buffer. + */ public final static class CryptoException extends RuntimeException { public CryptoException(int errorCode, String detailMessage) { super(detailMessage); mErrorCode = errorCode; } + /** + * This indicates that no key has been set to perform the requested + * decrypt operation. + */ + public static final int ERROR_NO_KEY = 1; + + /** + * This indicates that the key used for decryption is no longer + * valid due to license term expiration. + */ + public static final int ERROR_KEY_EXPIRED = 2; + + /** + * This indicates that a required crypto resource was not able to be + * allocated while attempting the requested operation. + */ + public static final int ERROR_RESOURCE_BUSY = 3; + + /** + * Retrieve the error code associated with a CryptoException + */ public int getErrorCode() { return mErrorCode; } @@ -430,6 +457,9 @@ final public class MediaCodec { * @param presentationTimeUs The time at which this buffer should be rendered. * @param flags A bitmask of flags {@link #BUFFER_FLAG_SYNC_FRAME}, * {@link #BUFFER_FLAG_CODEC_CONFIG} or {@link #BUFFER_FLAG_END_OF_STREAM}. + * @throws CryptoException if an error occurs while attempting to decrypt the buffer. + * An error code associated with the exception helps identify the + * reason for the failure. */ public native final void queueSecureInputBuffer( int index, @@ -545,6 +575,52 @@ final public class MediaCodec { public native final String getName(); /** + * Change a video encoder's target bitrate on the fly. The value is an + * Integer object containing the new bitrate in bps. + */ + public static final String PARAMETER_KEY_VIDEO_BITRATE = "video-bitrate"; + + /** + * Temporarily suspend/resume encoding of input data. While suspended + * input data is effectively discarded instead of being fed into the + * encoder. This parameter really only makes sense to use with an encoder + * in "surface-input" mode, as the client code has no control over the + * input-side of the encoder in that case. + * The value is an Integer object containing the value 1 to suspend + * or the value 0 to resume. + */ + public static final String PARAMETER_KEY_SUSPEND = "drop-input-frames"; + + /** + * Request that the encoder produce a sync frame "soon". + * Provide an Integer with the value 0. + */ + public static final String PARAMETER_KEY_REQUEST_SYNC_FRAME = "request-sync"; + + /** + * Communicate additional parameter changes to the component instance. + */ + public final void setParameters(Bundle params) { + if (params == null) { + return; + } + + String[] keys = new String[params.size()]; + Object[] values = new Object[params.size()]; + + int i = 0; + for (final String key: params.keySet()) { + keys[i] = key; + values[i] = params.get(key); + ++i; + } + + setParameters(keys, values); + } + + private native final void setParameters(String[] keys, Object[] values); + + /** * Get the codec info. If the codec was created by createDecoderByType * or createEncoderByType, what component is chosen is not known beforehand, * and thus the caller does not have the MediaCodecInfo. diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java index aeed7d4..90c12c6 100644 --- a/media/java/android/media/MediaCodecInfo.java +++ b/media/java/android/media/MediaCodecInfo.java @@ -72,7 +72,8 @@ public final class MediaCodecInfo { /** * Encapsulates the capabilities of a given codec component. * For example, what profile/level combinations it supports and what colorspaces - * it is capable of providing the decoded data in. + * it is capable of providing the decoded data in, as well as some + * codec-type specific capability flags. * <p>You can get an instance for a given {@link MediaCodecInfo} object with * {@link MediaCodecInfo#getCapabilitiesForType getCapabilitiesForType()}, passing a MIME type. */ @@ -139,6 +140,24 @@ public final class MediaCodecInfo { * OMX_COLOR_FORMATTYPE. */ public int[] colorFormats; + + private final static int FLAG_SupportsAdaptivePlayback = (1 << 0); + private int flags; + + /** + * <b>video decoder only</b>: codec supports seamless resolution changes. + */ + public final static String FEATURE_AdaptivePlayback = "adaptive-playback"; + + /** + * Query codec feature capabilities. + */ + public final boolean isFeatureSupported(String name) { + if (name.equals(FEATURE_AdaptivePlayback)) { + return (flags & FLAG_SupportsAdaptivePlayback) != 0; + } + return false; + } }; /** diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java index 7677d8a1..6b278d4 100644 --- a/media/java/android/media/MediaDrm.java +++ b/media/java/android/media/MediaDrm.java @@ -108,7 +108,19 @@ public final class MediaDrm { * @param uuid The UUID of the crypto scheme. */ public static final boolean isCryptoSchemeSupported(UUID uuid) { - return isCryptoSchemeSupportedNative(getByteArrayFromUUID(uuid)); + return isCryptoSchemeSupportedNative(getByteArrayFromUUID(uuid), null); + } + + /** + * Query if the given scheme identified by its UUID is supported on + * this device, and whether the drm plugin is able to handle the + * media container format specified by mimeType. + * @param uuid The UUID of the crypto scheme. + * @param mimeType The MIME type of the media container, e.g. "video/mp4" + * or "video/webm" + */ + public static final boolean isCryptoSchemeSupported(UUID uuid, String mimeType) { + return isCryptoSchemeSupportedNative(getByteArrayFromUUID(uuid), mimeType); } private static final byte[] getByteArrayFromUUID(UUID uuid) { @@ -124,7 +136,8 @@ public final class MediaDrm { return uuidBytes; } - private static final native boolean isCryptoSchemeSupportedNative(byte[] uuid); + private static final native boolean isCryptoSchemeSupportedNative(byte[] uuid, + String mimeType); /** * Instantiate a MediaDrm object @@ -273,6 +286,7 @@ public final class MediaDrm { * Open a new session with the MediaDrm object. A session ID is returned. * * @throws NotProvisionedException if provisioning is needed + * @throws ResourceBusyException if required resources are in use */ public native byte[] openSession() throws NotProvisionedException; @@ -379,6 +393,7 @@ public final class MediaDrm { * reprovisioning is required * @throws DeniedByServerException if the response indicates that the * server rejected the request + * @throws ResourceBusyException if required resources are in use */ public native byte[] provideKeyResponse(byte[] scope, byte[] response) throws NotProvisionedException, DeniedByServerException; diff --git a/media/java/android/media/MediaFocusControl.java b/media/java/android/media/MediaFocusControl.java new file mode 100644 index 0000000..07d91ac --- /dev/null +++ b/media/java/android/media/MediaFocusControl.java @@ -0,0 +1,2724 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media; + +import android.app.Activity; +import android.app.ActivityManager; +import android.app.AppOpsManager; +import android.app.KeyguardManager; +import android.app.PendingIntent; +import android.app.PendingIntent.CanceledException; +import android.app.PendingIntent.OnFinished; +import android.content.ActivityNotFoundException; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Binder; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.PowerManager; +import android.os.RemoteException; +import android.os.UserHandle; +import android.os.IBinder.DeathRecipient; +import android.provider.Settings; +import android.speech.RecognizerIntent; +import android.telephony.PhoneStateListener; +import android.telephony.TelephonyManager; +import android.util.Log; +import android.util.Slog; +import android.view.KeyEvent; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.Stack; + +/** + * @hide + * + */ +public class MediaFocusControl implements OnFinished { + + private static final String TAG = "MediaFocusControl"; + + /** Debug remote control client/display feature */ + protected static final boolean DEBUG_RC = false; + /** Debug volumes */ + protected static final boolean DEBUG_VOL = false; + + /** Used to alter media button redirection when the phone is ringing. */ + private boolean mIsRinging = false; + + private final PowerManager.WakeLock mMediaEventWakeLock; + private final MediaEventHandler mEventHandler; + private final Context mContext; + private final ContentResolver mContentResolver; + private final VolumeController mVolumeController; + private final BroadcastReceiver mReceiver = new PackageIntentsReceiver(); + private final AppOpsManager mAppOps; + private final KeyguardManager mKeyguardManager; + private final AudioService mAudioService; + private final NotificationListenerObserver mNotifListenerObserver; + + protected MediaFocusControl(Looper looper, Context cntxt, + VolumeController volumeCtrl, AudioService as) { + mEventHandler = new MediaEventHandler(looper); + mContext = cntxt; + mContentResolver = mContext.getContentResolver(); + mVolumeController = volumeCtrl; + mAudioService = as; + + PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE); + mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent"); + mMainRemote = new RemotePlaybackState(-1, + AudioService.getMaxStreamVolume(AudioManager.STREAM_MUSIC), + AudioService.getMaxStreamVolume(AudioManager.STREAM_MUSIC)); + + // Register for phone state monitoring + TelephonyManager tmgr = (TelephonyManager) + mContext.getSystemService(Context.TELEPHONY_SERVICE); + tmgr.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE); + + // Register for package addition/removal/change intent broadcasts + // for media button receiver persistence + IntentFilter pkgFilter = new IntentFilter(); + pkgFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); + pkgFilter.addAction(Intent.ACTION_PACKAGE_ADDED); + pkgFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); + pkgFilter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED); + pkgFilter.addDataScheme("package"); + mContext.registerReceiver(mReceiver, pkgFilter); + + mAppOps = (AppOpsManager)mContext.getSystemService(Context.APP_OPS_SERVICE); + mKeyguardManager = + (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); + mNotifListenerObserver = new NotificationListenerObserver(); + + mHasRemotePlayback = false; + mMainRemoteIsActive = false; + postReevaluateRemote(); + } + + protected void dump(PrintWriter pw) { + dumpFocusStack(pw); + dumpRCStack(pw); + dumpRCCStack(pw); + dumpRCDList(pw); + } + + //========================================================================================== + // Management of RemoteControlDisplay registration permissions + //========================================================================================== + private final static Uri ENABLED_NOTIFICATION_LISTENERS_URI = + Settings.Secure.getUriFor(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS); + + private class NotificationListenerObserver extends ContentObserver { + + NotificationListenerObserver() { + super(mEventHandler); + mContentResolver.registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.ENABLED_NOTIFICATION_LISTENERS), false, this); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + if (!ENABLED_NOTIFICATION_LISTENERS_URI.equals(uri) || selfChange) { + return; + } + if (DEBUG_RC) { Log.d(TAG, "NotificationListenerObserver.onChange()"); } + postReevaluateRemoteControlDisplays(); + } + } + + private final static int RCD_REG_FAILURE = 0; + private final static int RCD_REG_SUCCESS_PERMISSION = 1; + private final static int RCD_REG_SUCCESS_ENABLED_NOTIF = 2; + + /** + * Checks a caller's authorization to register an IRemoteControlDisplay. + * Authorization is granted if one of the following is true: + * <ul> + * <li>the caller has android.Manifest.permission.MEDIA_CONTENT_CONTROL permission</li> + * <li>the caller's listener is one of the enabled notification listeners</li> + * </ul> + * @return RCD_REG_FAILURE if it's not safe to proceed with the IRemoteControlDisplay + * registration. + */ + private int checkRcdRegistrationAuthorization(ComponentName listenerComp) { + // MEDIA_CONTENT_CONTROL permission check + if (PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission( + android.Manifest.permission.MEDIA_CONTENT_CONTROL)) { + if (DEBUG_RC) { Log.d(TAG, "ok to register Rcd: has MEDIA_CONTENT_CONTROL permission");} + return RCD_REG_SUCCESS_PERMISSION; + } + + // ENABLED_NOTIFICATION_LISTENERS settings check + if (listenerComp != null) { + // this call is coming from an app, can't use its identity to read secure settings + final long ident = Binder.clearCallingIdentity(); + try { + final int currentUser = ActivityManager.getCurrentUser(); + final String enabledNotifListeners = Settings.Secure.getStringForUser( + mContext.getContentResolver(), + Settings.Secure.ENABLED_NOTIFICATION_LISTENERS, + currentUser); + if (enabledNotifListeners != null) { + final String[] components = enabledNotifListeners.split(":"); + for (int i=0; i<components.length; i++) { + final ComponentName component = + ComponentName.unflattenFromString(components[i]); + if (component != null) { + if (listenerComp.equals(component)) { + if (DEBUG_RC) { Log.d(TAG, "ok to register RCC: " + component + + " is authorized notification listener"); } + return RCD_REG_SUCCESS_ENABLED_NOTIF; + } + } + } + } + if (DEBUG_RC) { Log.d(TAG, "not ok to register RCD, " + listenerComp + + " is not in list of ENABLED_NOTIFICATION_LISTENERS"); } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + return RCD_REG_FAILURE; + } + + protected boolean registerRemoteController(IRemoteControlDisplay rcd, int w, int h, + ComponentName listenerComp) { + int reg = checkRcdRegistrationAuthorization(listenerComp); + if (reg != RCD_REG_FAILURE) { + registerRemoteControlDisplay_int(rcd, w, h, listenerComp); + return true; + } else { + Slog.w(TAG, "Access denied to process: " + Binder.getCallingPid() + + ", must have permission " + android.Manifest.permission.MEDIA_CONTENT_CONTROL + + " or be an enabled NotificationListenerService for registerRemoteController"); + return false; + } + } + + protected boolean registerRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h) { + int reg = checkRcdRegistrationAuthorization(null); + if (reg != RCD_REG_FAILURE) { + registerRemoteControlDisplay_int(rcd, w, h, null); + return true; + } else { + Slog.w(TAG, "Access denied to process: " + Binder.getCallingPid() + + ", must have permission " + android.Manifest.permission.MEDIA_CONTENT_CONTROL + + " to register IRemoteControlDisplay"); + return false; + } + } + + private void postReevaluateRemoteControlDisplays() { + sendMsg(mEventHandler, MSG_REEVALUATE_RCD, SENDMSG_QUEUE, 0, 0, null, 0); + } + + private void onReevaluateRemoteControlDisplays() { + if (DEBUG_RC) { Log.d(TAG, "onReevaluateRemoteControlDisplays()"); } + // read which components are enabled notification listeners + final int currentUser = ActivityManager.getCurrentUser(); + final String enabledNotifListeners = Settings.Secure.getStringForUser( + mContext.getContentResolver(), + Settings.Secure.ENABLED_NOTIFICATION_LISTENERS, + currentUser); + if (DEBUG_RC) { Log.d(TAG, " > enabled list: " + enabledNotifListeners); } + synchronized(mAudioFocusLock) { + synchronized(mRCStack) { + // check whether the "enable" status of each RCD with a notification listener + // has changed + final String[] enabledComponents; + if (enabledNotifListeners == null) { + enabledComponents = null; + } else { + enabledComponents = enabledNotifListeners.split(":"); + } + final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator(); + while (displayIterator.hasNext()) { + final DisplayInfoForServer di = + (DisplayInfoForServer) displayIterator.next(); + if (di.mClientNotifListComp != null) { + boolean wasEnabled = di.mEnabled; + di.mEnabled = isComponentInStringArray(di.mClientNotifListComp, + enabledComponents); + if (wasEnabled != di.mEnabled){ + try { + // tell the RCD whether it's enabled + di.mRcDisplay.setEnabled(di.mEnabled); + // tell the RCCs about the change for this RCD + enableRemoteControlDisplayForClient_syncRcStack( + di.mRcDisplay, di.mEnabled); + // when enabling, refresh the information on the display + if (di.mEnabled) { + sendMsg(mEventHandler, MSG_RCDISPLAY_INIT_INFO, SENDMSG_QUEUE, + di.mArtworkExpectedWidth /*arg1*/, + di.mArtworkExpectedHeight/*arg2*/, + di.mRcDisplay /*obj*/, 0/*delay*/); + } + } catch (RemoteException e) { + Log.e(TAG, "Error en/disabling RCD: ", e); + } + } + } + } + } + } + } + + /** + * @param comp a non-null ComponentName + * @param enabledArray may be null + * @return + */ + private boolean isComponentInStringArray(ComponentName comp, String[] enabledArray) { + if (enabledArray == null || enabledArray.length == 0) { + if (DEBUG_RC) { Log.d(TAG, " > " + comp + " is NOT enabled"); } + return false; + } + final String compString = comp.flattenToString(); + for (int i=0; i<enabledArray.length; i++) { + if (compString.equals(enabledArray[i])) { + if (DEBUG_RC) { Log.d(TAG, " > " + compString + " is enabled"); } + return true; + } + } + if (DEBUG_RC) { Log.d(TAG, " > " + compString + " is NOT enabled"); } + return false; + } + + //========================================================================================== + // Internal event handling + //========================================================================================== + + // event handler messages + private static final int MSG_PERSIST_MEDIABUTTONRECEIVER = 0; + private static final int MSG_RCDISPLAY_CLEAR = 1; + private static final int MSG_RCDISPLAY_UPDATE = 2; + private static final int MSG_REEVALUATE_REMOTE = 3; + private static final int MSG_RCC_NEW_PLAYBACK_INFO = 4; + private static final int MSG_RCC_NEW_VOLUME_OBS = 5; + private static final int MSG_PROMOTE_RCC = 6; + private static final int MSG_RCC_NEW_PLAYBACK_STATE = 7; + private static final int MSG_RCC_SEEK_REQUEST = 8; + private static final int MSG_RCC_UPDATE_METADATA = 9; + private static final int MSG_RCDISPLAY_INIT_INFO = 10; + private static final int MSG_REEVALUATE_RCD = 11; + + // sendMsg() flags + /** 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; + + private static void sendMsg(Handler handler, int msg, + int existingMsgPolicy, int arg1, int arg2, Object obj, int delay) { + + 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); + } + + private class MediaEventHandler extends Handler { + MediaEventHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch(msg.what) { + case MSG_PERSIST_MEDIABUTTONRECEIVER: + onHandlePersistMediaButtonReceiver( (ComponentName) msg.obj ); + break; + + case MSG_RCDISPLAY_CLEAR: + onRcDisplayClear(); + break; + + case MSG_RCDISPLAY_UPDATE: + // msg.obj is guaranteed to be non null + onRcDisplayUpdate( (RemoteControlStackEntry) msg.obj, msg.arg1); + break; + + case MSG_REEVALUATE_REMOTE: + onReevaluateRemote(); + break; + + case MSG_RCC_NEW_PLAYBACK_INFO: + onNewPlaybackInfoForRcc(msg.arg1 /* rccId */, msg.arg2 /* key */, + ((Integer)msg.obj).intValue() /* value */); + break; + + case MSG_RCC_NEW_VOLUME_OBS: + onRegisterVolumeObserverForRcc(msg.arg1 /* rccId */, + (IRemoteVolumeObserver)msg.obj /* rvo */); + break; + + case MSG_RCC_NEW_PLAYBACK_STATE: + onNewPlaybackStateForRcc(msg.arg1 /* rccId */, + msg.arg2 /* state */, + (RccPlaybackState)msg.obj /* newState */); + break; + + case MSG_RCC_SEEK_REQUEST: + onSetRemoteControlClientPlaybackPosition( + msg.arg1 /* generationId */, ((Long)msg.obj).longValue() /* timeMs */); + break; + + case MSG_RCC_UPDATE_METADATA: + onUpdateRemoteControlClientMetadata(msg.arg1 /*genId*/, msg.arg2 /*key*/, + (Rating) msg.obj /* value */); + break; + + case MSG_PROMOTE_RCC: + onPromoteRcc(msg.arg1); + break; + + case MSG_RCDISPLAY_INIT_INFO: + // msg.obj is guaranteed to be non null + onRcDisplayInitInfo((IRemoteControlDisplay)msg.obj /*newRcd*/, + msg.arg1/*w*/, msg.arg2/*h*/); + break; + + case MSG_REEVALUATE_RCD: + onReevaluateRemoteControlDisplays(); + break; + } + } + } + + + //========================================================================================== + // AudioFocus + //========================================================================================== + + /* constant to identify focus stack entry that is used to hold the focus while the phone + * is ringing or during a call. Used by com.android.internal.telephony.CallManager when + * entering and exiting calls. + */ + protected final static String IN_VOICE_COMM_FOCUS_ID = "AudioFocus_For_Phone_Ring_And_Calls"; + + private final static Object mAudioFocusLock = new Object(); + + private final static Object mRingingLock = new Object(); + + private PhoneStateListener mPhoneStateListener = new PhoneStateListener() { + @Override + public void onCallStateChanged(int state, String incomingNumber) { + if (state == TelephonyManager.CALL_STATE_RINGING) { + //Log.v(TAG, " CALL_STATE_RINGING"); + synchronized(mRingingLock) { + mIsRinging = true; + } + } else if ((state == TelephonyManager.CALL_STATE_OFFHOOK) + || (state == TelephonyManager.CALL_STATE_IDLE)) { + synchronized(mRingingLock) { + mIsRinging = false; + } + } + } + }; + + /** + * Discard the current audio focus owner. + * Notify top of audio focus stack that it lost focus (regardless of possibility to reassign + * focus), remove it from the stack, and clear the remote control display. + */ + protected void discardAudioFocusOwner() { + synchronized(mAudioFocusLock) { + if (!mFocusStack.empty()) { + // notify the current focus owner it lost focus after removing it from stack + final FocusRequester exFocusOwner = mFocusStack.pop(); + exFocusOwner.handleFocusLoss(AudioManager.AUDIOFOCUS_LOSS); + exFocusOwner.release(); + // clear RCD + synchronized(mRCStack) { + clearRemoteControlDisplay_syncAfRcs(); + } + } + } + } + + private void notifyTopOfAudioFocusStack() { + // notify the top of the stack it gained focus + if (!mFocusStack.empty()) { + if (canReassignAudioFocus()) { + mFocusStack.peek().handleFocusGain(AudioManager.AUDIOFOCUS_GAIN); + } + } + } + + /** + * Focus is requested, propagate the associated loss throughout the stack. + * @param focusGain the new focus gain that will later be added at the top of the stack + */ + private void propagateFocusLossFromGain_syncAf(int focusGain) { + // going through the audio focus stack to signal new focus, traversing order doesn't + // matter as all entries respond to the same external focus gain + Iterator<FocusRequester> stackIterator = mFocusStack.iterator(); + while(stackIterator.hasNext()) { + stackIterator.next().handleExternalFocusGain(focusGain); + } + } + + private final Stack<FocusRequester> mFocusStack = new Stack<FocusRequester>(); + + /** + * Helper function: + * Display in the log the current entries in the audio focus stack + */ + private void dumpFocusStack(PrintWriter pw) { + pw.println("\nAudio Focus stack entries (last is top of stack):"); + synchronized(mAudioFocusLock) { + Iterator<FocusRequester> stackIterator = mFocusStack.iterator(); + while(stackIterator.hasNext()) { + stackIterator.next().dump(pw); + } + } + } + + /** + * Helper function: + * Called synchronized on mAudioFocusLock + * Remove a focus listener from the focus stack. + * @param clientToRemove the focus listener + * @param signal if true and the listener was at the top of the focus stack, i.e. it was holding + * focus, notify the next item in the stack it gained focus. + */ + private void removeFocusStackEntry(String clientToRemove, boolean signal) { + // is the current top of the focus stack abandoning focus? (because of request, not death) + if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientToRemove)) + { + //Log.i(TAG, " removeFocusStackEntry() removing top of stack"); + FocusRequester fr = mFocusStack.pop(); + fr.release(); + if (signal) { + // notify the new top of the stack it gained focus + notifyTopOfAudioFocusStack(); + // there's a new top of the stack, let the remote control know + synchronized(mRCStack) { + checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL); + } + } + } else { + // focus is abandoned by a client that's not at the top of the stack, + // no need to update focus. + // (using an iterator on the stack so we can safely remove an entry after having + // evaluated it, traversal order doesn't matter here) + Iterator<FocusRequester> stackIterator = mFocusStack.iterator(); + while(stackIterator.hasNext()) { + FocusRequester fr = (FocusRequester)stackIterator.next(); + if(fr.hasSameClient(clientToRemove)) { + Log.i(TAG, "AudioFocus removeFocusStackEntry(): removing entry for " + + clientToRemove); + stackIterator.remove(); + fr.release(); + } + } + } + } + + /** + * Helper function: + * Called synchronized on mAudioFocusLock + * Remove focus listeners from the focus stack for a particular client when it has died. + */ + private void removeFocusStackEntryForClient(IBinder cb) { + // is the owner of the audio focus part of the client to remove? + boolean isTopOfStackForClientToRemove = !mFocusStack.isEmpty() && + mFocusStack.peek().hasSameBinder(cb); + // (using an iterator on the stack so we can safely remove an entry after having + // evaluated it, traversal order doesn't matter here) + Iterator<FocusRequester> stackIterator = mFocusStack.iterator(); + while(stackIterator.hasNext()) { + FocusRequester fr = (FocusRequester)stackIterator.next(); + if(fr.hasSameBinder(cb)) { + Log.i(TAG, "AudioFocus removeFocusStackEntry(): removing entry for " + cb); + stackIterator.remove(); + // the client just died, no need to unlink to its death + } + } + if (isTopOfStackForClientToRemove) { + // we removed an entry at the top of the stack: + // notify the new top of the stack it gained focus. + notifyTopOfAudioFocusStack(); + // there's a new top of the stack, let the remote control know + synchronized(mRCStack) { + checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL); + } + } + } + + /** + * Helper function: + * Returns true if the system is in a state where the focus can be reevaluated, false otherwise. + */ + private boolean canReassignAudioFocus() { + // focus requests are rejected during a phone call or when the phone is ringing + // this is equivalent to IN_VOICE_COMM_FOCUS_ID having the focus + if (!mFocusStack.isEmpty() && mFocusStack.peek().hasSameClient(IN_VOICE_COMM_FOCUS_ID)) { + return false; + } + return true; + } + + /** + * Inner class to monitor audio focus client deaths, and remove them from the audio focus + * stack if necessary. + */ + protected class AudioFocusDeathHandler implements IBinder.DeathRecipient { + private IBinder mCb; // To be notified of client's death + + AudioFocusDeathHandler(IBinder cb) { + mCb = cb; + } + + public void binderDied() { + synchronized(mAudioFocusLock) { + Log.w(TAG, " AudioFocus audio focus client died"); + removeFocusStackEntryForClient(mCb); + } + } + + public IBinder getBinder() { + return mCb; + } + } + + protected int getCurrentAudioFocus() { + synchronized(mAudioFocusLock) { + if (mFocusStack.empty()) { + return AudioManager.AUDIOFOCUS_NONE; + } else { + return mFocusStack.peek().getGainRequest(); + } + } + } + + /** @see AudioManager#requestAudioFocus(AudioManager.OnAudioFocusChangeListener, int, int) */ + protected int requestAudioFocus(int mainStreamType, int focusChangeHint, IBinder cb, + IAudioFocusDispatcher fd, String clientId, String callingPackageName) { + Log.i(TAG, " AudioFocus requestAudioFocus() from " + clientId); + // we need a valid binder callback for clients + if (!cb.pingBinder()) { + Log.e(TAG, " AudioFocus DOA client for requestAudioFocus(), aborting."); + return AudioManager.AUDIOFOCUS_REQUEST_FAILED; + } + + if (mAppOps.noteOp(AppOpsManager.OP_TAKE_AUDIO_FOCUS, Binder.getCallingUid(), + callingPackageName) != AppOpsManager.MODE_ALLOWED) { + return AudioManager.AUDIOFOCUS_REQUEST_FAILED; + } + + synchronized(mAudioFocusLock) { + if (!canReassignAudioFocus()) { + return AudioManager.AUDIOFOCUS_REQUEST_FAILED; + } + + // handle the potential premature death of the new holder of the focus + // (premature death == death before abandoning focus) + // Register for client death notification + AudioFocusDeathHandler afdh = new AudioFocusDeathHandler(cb); + try { + cb.linkToDeath(afdh, 0); + } catch (RemoteException e) { + // client has already died! + Log.w(TAG, "AudioFocus requestAudioFocus() could not link to "+cb+" binder death"); + return AudioManager.AUDIOFOCUS_REQUEST_FAILED; + } + + if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientId)) { + // if focus is already owned by this client and the reason for acquiring the focus + // hasn't changed, don't do anything + if (mFocusStack.peek().getGainRequest() == focusChangeHint) { + // unlink death handler so it can be gc'ed. + // linkToDeath() creates a JNI global reference preventing collection. + cb.unlinkToDeath(afdh, 0); + return AudioManager.AUDIOFOCUS_REQUEST_GRANTED; + } + // the reason for the audio focus request has changed: remove the current top of + // stack and respond as if we had a new focus owner + FocusRequester fr = mFocusStack.pop(); + fr.release(); + } + + // focus requester might already be somewhere below in the stack, remove it + removeFocusStackEntry(clientId, false /* signal */); + + // propagate the focus change through the stack + if (!mFocusStack.empty()) { + propagateFocusLossFromGain_syncAf(focusChangeHint); + } + + // push focus requester at the top of the audio focus stack + mFocusStack.push(new FocusRequester(mainStreamType, focusChangeHint, fd, cb, + clientId, afdh, callingPackageName, Binder.getCallingUid())); + + // there's a new top of the stack, let the remote control know + synchronized(mRCStack) { + checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL); + } + }//synchronized(mAudioFocusLock) + + return AudioManager.AUDIOFOCUS_REQUEST_GRANTED; + } + + /** @see AudioManager#abandonAudioFocus(AudioManager.OnAudioFocusChangeListener) */ + protected int abandonAudioFocus(IAudioFocusDispatcher fl, String clientId) { + Log.i(TAG, " AudioFocus abandonAudioFocus() from " + clientId); + try { + // this will take care of notifying the new focus owner if needed + synchronized(mAudioFocusLock) { + removeFocusStackEntry(clientId, true /*signal*/); + } + } catch (java.util.ConcurrentModificationException cme) { + // Catching this exception here is temporary. It is here just to prevent + // a crash seen when the "Silent" notification is played. This is believed to be fixed + // but this try catch block is left just to be safe. + Log.e(TAG, "FATAL EXCEPTION AudioFocus abandonAudioFocus() caused " + cme); + cme.printStackTrace(); + } + + return AudioManager.AUDIOFOCUS_REQUEST_GRANTED; + } + + + protected void unregisterAudioFocusClient(String clientId) { + synchronized(mAudioFocusLock) { + removeFocusStackEntry(clientId, false); + } + } + + + //========================================================================================== + // RemoteControl + //========================================================================================== + /** + * No-op if the key code for keyEvent is not a valid media key + * (see {@link #isValidMediaKeyEvent(KeyEvent)}) + * @param keyEvent the key event to send + */ + protected void dispatchMediaKeyEvent(KeyEvent keyEvent) { + filterMediaKeyEvent(keyEvent, false /*needWakeLock*/); + } + + /** + * No-op if the key code for keyEvent is not a valid media key + * (see {@link #isValidMediaKeyEvent(KeyEvent)}) + * @param keyEvent the key event to send + */ + protected void dispatchMediaKeyEventUnderWakelock(KeyEvent keyEvent) { + filterMediaKeyEvent(keyEvent, true /*needWakeLock*/); + } + + private void filterMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) { + // sanity check on the incoming key event + if (!isValidMediaKeyEvent(keyEvent)) { + Log.e(TAG, "not dispatching invalid media key event " + keyEvent); + return; + } + // event filtering for telephony + synchronized(mRingingLock) { + synchronized(mRCStack) { + if ((mMediaReceiverForCalls != null) && + (mIsRinging || (mAudioService.getMode() == AudioSystem.MODE_IN_CALL))) { + dispatchMediaKeyEventForCalls(keyEvent, needWakeLock); + return; + } + } + } + // event filtering based on voice-based interactions + if (isValidVoiceInputKeyCode(keyEvent.getKeyCode())) { + filterVoiceInputKeyEvent(keyEvent, needWakeLock); + } else { + dispatchMediaKeyEvent(keyEvent, needWakeLock); + } + } + + /** + * Handles the dispatching of the media button events to the telephony package. + * Precondition: mMediaReceiverForCalls != null + * @param keyEvent a non-null KeyEvent whose key code is one of the supported media buttons + * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held while this key event + * is dispatched. + */ + private void dispatchMediaKeyEventForCalls(KeyEvent keyEvent, boolean needWakeLock) { + Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null); + keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent); + keyIntent.setPackage(mMediaReceiverForCalls.getPackageName()); + if (needWakeLock) { + mMediaEventWakeLock.acquire(); + keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED, WAKELOCK_RELEASE_ON_FINISHED); + } + final long ident = Binder.clearCallingIdentity(); + try { + mContext.sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL, + null, mKeyEventDone, mEventHandler, Activity.RESULT_OK, null, null); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + /** + * Handles the dispatching of the media button events to one of the registered listeners, + * or if there was none, broadcast an ACTION_MEDIA_BUTTON intent to the rest of the system. + * @param keyEvent a non-null KeyEvent whose key code is one of the supported media buttons + * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held while this key event + * is dispatched. + */ + private void dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) { + if (needWakeLock) { + mMediaEventWakeLock.acquire(); + } + Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null); + keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent); + synchronized(mRCStack) { + if (!mRCStack.empty()) { + // send the intent that was registered by the client + try { + mRCStack.peek().mMediaIntent.send(mContext, + needWakeLock ? WAKELOCK_RELEASE_ON_FINISHED : 0 /*code*/, + keyIntent, this, mEventHandler); + } catch (CanceledException e) { + Log.e(TAG, "Error sending pending intent " + mRCStack.peek()); + e.printStackTrace(); + } + } else { + // legacy behavior when nobody registered their media button event receiver + // through AudioManager + if (needWakeLock) { + keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED, WAKELOCK_RELEASE_ON_FINISHED); + } + final long ident = Binder.clearCallingIdentity(); + try { + mContext.sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL, + null, mKeyEventDone, + mEventHandler, Activity.RESULT_OK, null, null); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + } + + /** + * The different actions performed in response to a voice button key event. + */ + private final static int VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS = 1; + private final static int VOICEBUTTON_ACTION_START_VOICE_INPUT = 2; + private final static int VOICEBUTTON_ACTION_SIMULATE_KEY_PRESS = 3; + + private final Object mVoiceEventLock = new Object(); + private boolean mVoiceButtonDown; + private boolean mVoiceButtonHandled; + + /** + * Filter key events that may be used for voice-based interactions + * @param keyEvent a non-null KeyEvent whose key code is that of one of the supported + * media buttons that can be used to trigger voice-based interactions. + * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held while this key event + * is dispatched. + */ + private void filterVoiceInputKeyEvent(KeyEvent keyEvent, boolean needWakeLock) { + if (DEBUG_RC) { + Log.v(TAG, "voice input key event: " + keyEvent + ", needWakeLock=" + needWakeLock); + } + + int voiceButtonAction = VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS; + int keyAction = keyEvent.getAction(); + synchronized (mVoiceEventLock) { + if (keyAction == KeyEvent.ACTION_DOWN) { + if (keyEvent.getRepeatCount() == 0) { + // initial down + mVoiceButtonDown = true; + mVoiceButtonHandled = false; + } else if (mVoiceButtonDown && !mVoiceButtonHandled + && (keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) { + // long-press, start voice-based interactions + mVoiceButtonHandled = true; + voiceButtonAction = VOICEBUTTON_ACTION_START_VOICE_INPUT; + } + } else if (keyAction == KeyEvent.ACTION_UP) { + if (mVoiceButtonDown) { + // voice button up + mVoiceButtonDown = false; + if (!mVoiceButtonHandled && !keyEvent.isCanceled()) { + voiceButtonAction = VOICEBUTTON_ACTION_SIMULATE_KEY_PRESS; + } + } + } + }//synchronized (mVoiceEventLock) + + // take action after media button event filtering for voice-based interactions + switch (voiceButtonAction) { + case VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS: + if (DEBUG_RC) Log.v(TAG, " ignore key event"); + break; + case VOICEBUTTON_ACTION_START_VOICE_INPUT: + if (DEBUG_RC) Log.v(TAG, " start voice-based interactions"); + // then start the voice-based interactions + startVoiceBasedInteractions(needWakeLock); + break; + case VOICEBUTTON_ACTION_SIMULATE_KEY_PRESS: + if (DEBUG_RC) Log.v(TAG, " send simulated key event, wakelock=" + needWakeLock); + sendSimulatedMediaButtonEvent(keyEvent, needWakeLock); + break; + } + } + + private void sendSimulatedMediaButtonEvent(KeyEvent originalKeyEvent, boolean needWakeLock) { + // send DOWN event + KeyEvent keyEvent = KeyEvent.changeAction(originalKeyEvent, KeyEvent.ACTION_DOWN); + dispatchMediaKeyEvent(keyEvent, needWakeLock); + // send UP event + keyEvent = KeyEvent.changeAction(originalKeyEvent, KeyEvent.ACTION_UP); + dispatchMediaKeyEvent(keyEvent, needWakeLock); + + } + + private class PackageIntentsReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action.equals(Intent.ACTION_PACKAGE_REMOVED) + || action.equals(Intent.ACTION_PACKAGE_DATA_CLEARED)) { + if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { + // a package is being removed, not replaced + String packageName = intent.getData().getSchemeSpecificPart(); + if (packageName != null) { + cleanupMediaButtonReceiverForPackage(packageName, true); + } + } + } else if (action.equals(Intent.ACTION_PACKAGE_ADDED) + || action.equals(Intent.ACTION_PACKAGE_CHANGED)) { + String packageName = intent.getData().getSchemeSpecificPart(); + if (packageName != null) { + cleanupMediaButtonReceiverForPackage(packageName, false); + } + } + } + } + + protected static boolean isMediaKeyCode(int keyCode) { + switch (keyCode) { + case KeyEvent.KEYCODE_MUTE: + case KeyEvent.KEYCODE_HEADSETHOOK: + case KeyEvent.KEYCODE_MEDIA_PLAY: + case KeyEvent.KEYCODE_MEDIA_PAUSE: + case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: + case KeyEvent.KEYCODE_MEDIA_STOP: + case KeyEvent.KEYCODE_MEDIA_NEXT: + case KeyEvent.KEYCODE_MEDIA_PREVIOUS: + case KeyEvent.KEYCODE_MEDIA_REWIND: + case KeyEvent.KEYCODE_MEDIA_RECORD: + case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: + case KeyEvent.KEYCODE_MEDIA_CLOSE: + case KeyEvent.KEYCODE_MEDIA_EJECT: + case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: + return true; + default: + return false; + } + } + + private static boolean isValidMediaKeyEvent(KeyEvent keyEvent) { + if (keyEvent == null) { + return false; + } + return MediaFocusControl.isMediaKeyCode(keyEvent.getKeyCode()); + } + + /** + * Checks whether the given key code is one that can trigger the launch of voice-based + * interactions. + * @param keyCode the key code associated with the key event + * @return true if the key is one of the supported voice-based interaction triggers + */ + private static boolean isValidVoiceInputKeyCode(int keyCode) { + if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK) { + return true; + } else { + return false; + } + } + + /** + * Tell the system to start voice-based interactions / voice commands + */ + private void startVoiceBasedInteractions(boolean needWakeLock) { + Intent voiceIntent = null; + // select which type of search to launch: + // - screen on and device unlocked: action is ACTION_WEB_SEARCH + // - device locked or screen off: action is ACTION_VOICE_SEARCH_HANDS_FREE + // with EXTRA_SECURE set to true if the device is securely locked + PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE); + boolean isLocked = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked(); + if (!isLocked && pm.isScreenOn()) { + voiceIntent = new Intent(android.speech.RecognizerIntent.ACTION_WEB_SEARCH); + Log.i(TAG, "voice-based interactions: about to use ACTION_WEB_SEARCH"); + } else { + voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE); + voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE, + isLocked && mKeyguardManager.isKeyguardSecure()); + Log.i(TAG, "voice-based interactions: about to use ACTION_VOICE_SEARCH_HANDS_FREE"); + } + // start the search activity + if (needWakeLock) { + mMediaEventWakeLock.acquire(); + } + final long identity = Binder.clearCallingIdentity(); + try { + if (voiceIntent != null) { + voiceIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); + mContext.startActivityAsUser(voiceIntent, UserHandle.CURRENT); + } + } catch (ActivityNotFoundException e) { + Log.w(TAG, "No activity for search: " + e); + } finally { + Binder.restoreCallingIdentity(identity); + if (needWakeLock) { + mMediaEventWakeLock.release(); + } + } + } + + private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; //magic number + + // only set when wakelock was acquired, no need to check value when received + private static final String EXTRA_WAKELOCK_ACQUIRED = + "android.media.AudioService.WAKELOCK_ACQUIRED"; + + public void onSendFinished(PendingIntent pendingIntent, Intent intent, + int resultCode, String resultData, Bundle resultExtras) { + if (resultCode == WAKELOCK_RELEASE_ON_FINISHED) { + mMediaEventWakeLock.release(); + } + } + + BroadcastReceiver mKeyEventDone = new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + if (intent == null) { + return; + } + Bundle extras = intent.getExtras(); + if (extras == null) { + return; + } + if (extras.containsKey(EXTRA_WAKELOCK_ACQUIRED)) { + mMediaEventWakeLock.release(); + } + } + }; + + /** + * Synchronization on mCurrentRcLock always inside a block synchronized on mRCStack + */ + private final Object mCurrentRcLock = new Object(); + /** + * The one remote control client which will receive a request for display information. + * This object may be null. + * Access protected by mCurrentRcLock. + */ + private IRemoteControlClient mCurrentRcClient = null; + /** + * The PendingIntent associated with mCurrentRcClient. Its value is irrelevant + * if mCurrentRcClient is null + */ + private PendingIntent mCurrentRcClientIntent = null; + + private final static int RC_INFO_NONE = 0; + private final static int RC_INFO_ALL = + RemoteControlClient.FLAG_INFORMATION_REQUEST_ALBUM_ART | + RemoteControlClient.FLAG_INFORMATION_REQUEST_KEY_MEDIA | + RemoteControlClient.FLAG_INFORMATION_REQUEST_METADATA | + RemoteControlClient.FLAG_INFORMATION_REQUEST_PLAYSTATE; + + /** + * A monotonically increasing generation counter for mCurrentRcClient. + * Only accessed with a lock on mCurrentRcLock. + * No value wrap-around issues as we only act on equal values. + */ + private int mCurrentRcClientGen = 0; + + /** + * Inner class to monitor remote control client deaths, and remove the client for the + * remote control stack if necessary. + */ + private class RcClientDeathHandler implements IBinder.DeathRecipient { + final private IBinder mCb; // To be notified of client's death + final private PendingIntent mMediaIntent; + + RcClientDeathHandler(IBinder cb, PendingIntent pi) { + mCb = cb; + mMediaIntent = pi; + } + + public void binderDied() { + Log.w(TAG, " RemoteControlClient died"); + // remote control client died, make sure the displays don't use it anymore + // by setting its remote control client to null + registerRemoteControlClient(mMediaIntent, null/*rcClient*/, null/*ignored*/); + // the dead client was maybe handling remote playback, reevaluate + postReevaluateRemote(); + } + + public IBinder getBinder() { + return mCb; + } + } + + /** + * A global counter for RemoteControlClient identifiers + */ + private static int sLastRccId = 0; + + private class RemotePlaybackState { + int mRccId; + int mVolume; + int mVolumeMax; + int mVolumeHandling; + + private RemotePlaybackState(int id, int vol, int volMax) { + mRccId = id; + mVolume = vol; + mVolumeMax = volMax; + mVolumeHandling = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME_HANDLING; + } + } + + /** + * Internal cache for the playback information of the RemoteControlClient whose volume gets to + * be controlled by the volume keys ("main"), so we don't have to iterate over the RC stack + * every time we need this info. + */ + private RemotePlaybackState mMainRemote; + /** + * Indicates whether the "main" RemoteControlClient is considered active. + * Use synchronized on mMainRemote. + */ + private boolean mMainRemoteIsActive; + /** + * Indicates whether there is remote playback going on. True even if there is no "active" + * remote playback (mMainRemoteIsActive is false), but a RemoteControlClient has declared it + * handles remote playback. + * Use synchronized on mMainRemote. + */ + private boolean mHasRemotePlayback; + + private static class RccPlaybackState { + public int mState; + public long mPositionMs; + public float mSpeed; + + public RccPlaybackState(int state, long positionMs, float speed) { + mState = state; + mPositionMs = positionMs; + mSpeed = speed; + } + + public void reset() { + mState = RemoteControlClient.PLAYSTATE_STOPPED; + mPositionMs = RemoteControlClient.PLAYBACK_POSITION_INVALID; + mSpeed = RemoteControlClient.PLAYBACK_SPEED_1X; + } + + @Override + public String toString() { + return stateToString() + ", " + posToString() + ", " + mSpeed + "X"; + } + + private String posToString() { + if (mPositionMs == RemoteControlClient.PLAYBACK_POSITION_INVALID) { + return "PLAYBACK_POSITION_INVALID"; + } else if (mPositionMs == RemoteControlClient.PLAYBACK_POSITION_ALWAYS_UNKNOWN) { + return "PLAYBACK_POSITION_ALWAYS_UNKNOWN"; + } else { + return (String.valueOf(mPositionMs) + "ms"); + } + } + + private String stateToString() { + switch (mState) { + case RemoteControlClient.PLAYSTATE_NONE: + return "PLAYSTATE_NONE"; + case RemoteControlClient.PLAYSTATE_STOPPED: + return "PLAYSTATE_STOPPED"; + case RemoteControlClient.PLAYSTATE_PAUSED: + return "PLAYSTATE_PAUSED"; + case RemoteControlClient.PLAYSTATE_PLAYING: + return "PLAYSTATE_PLAYING"; + case RemoteControlClient.PLAYSTATE_FAST_FORWARDING: + return "PLAYSTATE_FAST_FORWARDING"; + case RemoteControlClient.PLAYSTATE_REWINDING: + return "PLAYSTATE_REWINDING"; + case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS: + return "PLAYSTATE_SKIPPING_FORWARDS"; + case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS: + return "PLAYSTATE_SKIPPING_BACKWARDS"; + case RemoteControlClient.PLAYSTATE_BUFFERING: + return "PLAYSTATE_BUFFERING"; + case RemoteControlClient.PLAYSTATE_ERROR: + return "PLAYSTATE_ERROR"; + default: + return "[invalid playstate]"; + } + } + } + + protected static class RemoteControlStackEntry implements DeathRecipient { + public int mRccId = RemoteControlClient.RCSE_ID_UNREGISTERED; + final public MediaFocusControl mController; + /** + * The target for the ACTION_MEDIA_BUTTON events. + * Always non null. + */ + final public PendingIntent mMediaIntent; + /** + * The registered media button event receiver. + * Always non null. + */ + final public ComponentName mReceiverComponent; + public IBinder mToken; + public String mCallingPackageName; + public int mCallingUid; + /** + * Provides access to the information to display on the remote control. + * May be null (when a media button event receiver is registered, + * but no remote control client has been registered) */ + public IRemoteControlClient mRcClient; + public RcClientDeathHandler mRcClientDeathHandler; + /** + * Information only used for non-local playback + */ + public int mPlaybackType; + public int mPlaybackVolume; + public int mPlaybackVolumeMax; + public int mPlaybackVolumeHandling; + public int mPlaybackStream; + public RccPlaybackState mPlaybackState; + public IRemoteVolumeObserver mRemoteVolumeObs; + + public void resetPlaybackInfo() { + mPlaybackType = RemoteControlClient.PLAYBACK_TYPE_LOCAL; + mPlaybackVolume = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME; + mPlaybackVolumeMax = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME; + mPlaybackVolumeHandling = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME_HANDLING; + mPlaybackStream = AudioManager.STREAM_MUSIC; + mPlaybackState.reset(); + mRemoteVolumeObs = null; + } + + /** precondition: mediaIntent != null */ + public RemoteControlStackEntry(MediaFocusControl controller, PendingIntent mediaIntent, + ComponentName eventReceiver, IBinder token) { + mController = controller; + mMediaIntent = mediaIntent; + mReceiverComponent = eventReceiver; + mToken = token; + mCallingUid = -1; + mRcClient = null; + mRccId = ++sLastRccId; + mPlaybackState = new RccPlaybackState( + RemoteControlClient.PLAYSTATE_STOPPED, + RemoteControlClient.PLAYBACK_POSITION_INVALID, + RemoteControlClient.PLAYBACK_SPEED_1X); + + resetPlaybackInfo(); + if (mToken != null) { + try { + mToken.linkToDeath(this, 0); + } catch (RemoteException e) { + mController.mEventHandler.post(new Runnable() { + @Override public void run() { + mController.unregisterMediaButtonIntent(mMediaIntent); + } + }); + } + } + } + + public void unlinkToRcClientDeath() { + if ((mRcClientDeathHandler != null) && (mRcClientDeathHandler.mCb != null)) { + try { + mRcClientDeathHandler.mCb.unlinkToDeath(mRcClientDeathHandler, 0); + mRcClientDeathHandler = null; + } catch (java.util.NoSuchElementException e) { + // not much we can do here + Log.e(TAG, "Encountered " + e + " in unlinkToRcClientDeath()"); + e.printStackTrace(); + } + } + } + + public void destroy() { + unlinkToRcClientDeath(); + if (mToken != null) { + mToken.unlinkToDeath(this, 0); + mToken = null; + } + } + + @Override + public void binderDied() { + mController.unregisterMediaButtonIntent(mMediaIntent); + } + + @Override + protected void finalize() throws Throwable { + destroy(); // unlink exception handled inside method + super.finalize(); + } + } + + /** + * The stack of remote control event receivers. + * Code sections and methods that modify the remote control event receiver stack are + * synchronized on mRCStack, but also BEFORE on mFocusLock as any change in either + * stack, audio focus or RC, can lead to a change in the remote control display + */ + private final Stack<RemoteControlStackEntry> mRCStack = new Stack<RemoteControlStackEntry>(); + + /** + * The component the telephony package can register so telephony calls have priority to + * handle media button events + */ + private ComponentName mMediaReceiverForCalls = null; + + /** + * Helper function: + * Display in the log the current entries in the remote control focus stack + */ + private void dumpRCStack(PrintWriter pw) { + pw.println("\nRemote Control stack entries (last is top of stack):"); + synchronized(mRCStack) { + Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator(); + while(stackIterator.hasNext()) { + RemoteControlStackEntry rcse = stackIterator.next(); + pw.println(" pi: " + rcse.mMediaIntent + + " -- pack: " + rcse.mCallingPackageName + + " -- ercvr: " + rcse.mReceiverComponent + + " -- client: " + rcse.mRcClient + + " -- uid: " + rcse.mCallingUid + + " -- type: " + rcse.mPlaybackType + + " state: " + rcse.mPlaybackState); + } + } + } + + /** + * Helper function: + * Display in the log the current entries in the remote control stack, focusing + * on RemoteControlClient data + */ + private void dumpRCCStack(PrintWriter pw) { + pw.println("\nRemote Control Client stack entries (last is top of stack):"); + synchronized(mRCStack) { + Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator(); + while(stackIterator.hasNext()) { + RemoteControlStackEntry rcse = stackIterator.next(); + pw.println(" uid: " + rcse.mCallingUid + + " -- id: " + rcse.mRccId + + " -- type: " + rcse.mPlaybackType + + " -- state: " + rcse.mPlaybackState + + " -- vol handling: " + rcse.mPlaybackVolumeHandling + + " -- vol: " + rcse.mPlaybackVolume + + " -- volMax: " + rcse.mPlaybackVolumeMax + + " -- volObs: " + rcse.mRemoteVolumeObs); + } + synchronized(mCurrentRcLock) { + pw.println("\nCurrent remote control generation ID = " + mCurrentRcClientGen); + } + } + synchronized (mMainRemote) { + pw.println("\nRemote Volume State:"); + pw.println(" has remote: " + mHasRemotePlayback); + pw.println(" is remote active: " + mMainRemoteIsActive); + pw.println(" rccId: " + mMainRemote.mRccId); + pw.println(" volume handling: " + + ((mMainRemote.mVolumeHandling == RemoteControlClient.PLAYBACK_VOLUME_FIXED) ? + "PLAYBACK_VOLUME_FIXED(0)" : "PLAYBACK_VOLUME_VARIABLE(1)")); + pw.println(" volume: " + mMainRemote.mVolume); + pw.println(" volume steps: " + mMainRemote.mVolumeMax); + } + } + + /** + * Helper function: + * Display in the log the current entries in the list of remote control displays + */ + private void dumpRCDList(PrintWriter pw) { + pw.println("\nRemote Control Display list entries:"); + synchronized(mRCStack) { + final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator(); + while (displayIterator.hasNext()) { + final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next(); + pw.println(" IRCD: " + di.mRcDisplay + + " -- w:" + di.mArtworkExpectedWidth + + " -- h:" + di.mArtworkExpectedHeight + + " -- wantsPosSync:" + di.mWantsPositionSync + + " -- " + (di.mEnabled ? "enabled" : "disabled")); + } + } + } + + /** + * Helper function: + * Remove any entry in the remote control stack that has the same package name as packageName + * Pre-condition: packageName != null + */ + private void cleanupMediaButtonReceiverForPackage(String packageName, boolean removeAll) { + synchronized(mRCStack) { + if (mRCStack.empty()) { + return; + } else { + final PackageManager pm = mContext.getPackageManager(); + RemoteControlStackEntry oldTop = mRCStack.peek(); + Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator(); + // iterate over the stack entries + // (using an iterator on the stack so we can safely remove an entry after having + // evaluated it, traversal order doesn't matter here) + while(stackIterator.hasNext()) { + RemoteControlStackEntry rcse = (RemoteControlStackEntry)stackIterator.next(); + if (removeAll && packageName.equals(rcse.mMediaIntent.getCreatorPackage())) { + // a stack entry is from the package being removed, remove it from the stack + stackIterator.remove(); + rcse.destroy(); + } else if (rcse.mReceiverComponent != null) { + try { + // Check to see if this receiver still exists. + pm.getReceiverInfo(rcse.mReceiverComponent, 0); + } catch (PackageManager.NameNotFoundException e) { + // Not found -- remove it! + stackIterator.remove(); + rcse.destroy(); + } + } + } + if (mRCStack.empty()) { + // no saved media button receiver + mEventHandler.sendMessage( + mEventHandler.obtainMessage(MSG_PERSIST_MEDIABUTTONRECEIVER, 0, 0, + null)); + } else if (oldTop != mRCStack.peek()) { + // the top of the stack has changed, save it in the system settings + // by posting a message to persist it; only do this however if it has + // a concrete component name (is not a transient registration) + RemoteControlStackEntry rcse = mRCStack.peek(); + if (rcse.mReceiverComponent != null) { + mEventHandler.sendMessage( + mEventHandler.obtainMessage(MSG_PERSIST_MEDIABUTTONRECEIVER, 0, 0, + rcse.mReceiverComponent)); + } + } + } + } + } + + /** + * Helper function: + * Restore remote control receiver from the system settings. + */ + protected void restoreMediaButtonReceiver() { + String receiverName = Settings.System.getStringForUser(mContentResolver, + Settings.System.MEDIA_BUTTON_RECEIVER, UserHandle.USER_CURRENT); + if ((null != receiverName) && !receiverName.isEmpty()) { + ComponentName eventReceiver = ComponentName.unflattenFromString(receiverName); + if (eventReceiver == null) { + // an invalid name was persisted + return; + } + // construct a PendingIntent targeted to the restored component name + // for the media button and register it + Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); + // the associated intent will be handled by the component being registered + mediaButtonIntent.setComponent(eventReceiver); + PendingIntent pi = PendingIntent.getBroadcast(mContext, + 0/*requestCode, ignored*/, mediaButtonIntent, 0/*flags*/); + registerMediaButtonIntent(pi, eventReceiver, null); + } + } + + /** + * Helper function: + * Set the new remote control receiver at the top of the RC focus stack. + * Called synchronized on mAudioFocusLock, then mRCStack + * precondition: mediaIntent != null + */ + private void pushMediaButtonReceiver_syncAfRcs(PendingIntent mediaIntent, ComponentName target, + IBinder token) { + // already at top of stack? + if (!mRCStack.empty() && mRCStack.peek().mMediaIntent.equals(mediaIntent)) { + return; + } + if (mAppOps.noteOp(AppOpsManager.OP_TAKE_MEDIA_BUTTONS, Binder.getCallingUid(), + mediaIntent.getCreatorPackage()) != AppOpsManager.MODE_ALLOWED) { + return; + } + RemoteControlStackEntry rcse = null; + boolean wasInsideStack = false; + try { + for (int index = mRCStack.size()-1; index >= 0; index--) { + rcse = mRCStack.elementAt(index); + if(rcse.mMediaIntent.equals(mediaIntent)) { + // ok to remove element while traversing the stack since we're leaving the loop + mRCStack.removeElementAt(index); + wasInsideStack = true; + break; + } + } + } catch (ArrayIndexOutOfBoundsException e) { + // not expected to happen, indicates improper concurrent modification + Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e); + } + if (!wasInsideStack) { + rcse = new RemoteControlStackEntry(this, mediaIntent, target, token); + } + mRCStack.push(rcse); // rcse is never null + + // post message to persist the default media button receiver + if (target != null) { + mEventHandler.sendMessage( mEventHandler.obtainMessage( + MSG_PERSIST_MEDIABUTTONRECEIVER, 0, 0, target/*obj*/) ); + } + } + + /** + * Helper function: + * Remove the remote control receiver from the RC focus stack. + * Called synchronized on mAudioFocusLock, then mRCStack + * precondition: pi != null + */ + private void removeMediaButtonReceiver_syncAfRcs(PendingIntent pi) { + try { + for (int index = mRCStack.size()-1; index >= 0; index--) { + final RemoteControlStackEntry rcse = mRCStack.elementAt(index); + if (rcse.mMediaIntent.equals(pi)) { + rcse.destroy(); + // ok to remove element while traversing the stack since we're leaving the loop + mRCStack.removeElementAt(index); + break; + } + } + } catch (ArrayIndexOutOfBoundsException e) { + // not expected to happen, indicates improper concurrent modification + Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e); + } + } + + /** + * Helper function: + * Called synchronized on mRCStack + */ + private boolean isCurrentRcController(PendingIntent pi) { + if (!mRCStack.empty() && mRCStack.peek().mMediaIntent.equals(pi)) { + return true; + } + return false; + } + + private void onHandlePersistMediaButtonReceiver(ComponentName receiver) { + Settings.System.putStringForUser(mContentResolver, + Settings.System.MEDIA_BUTTON_RECEIVER, + receiver == null ? "" : receiver.flattenToString(), + UserHandle.USER_CURRENT); + } + + //========================================================================================== + // Remote control display / client + //========================================================================================== + /** + * Update the remote control displays with the new "focused" client generation + */ + private void setNewRcClientOnDisplays_syncRcsCurrc(int newClientGeneration, + PendingIntent newMediaIntent, boolean clearing) { + synchronized(mRCStack) { + if (mRcDisplays.size() > 0) { + final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator(); + while (displayIterator.hasNext()) { + final DisplayInfoForServer di = displayIterator.next(); + try { + di.mRcDisplay.setCurrentClientId( + newClientGeneration, newMediaIntent, clearing); + } catch (RemoteException e) { + Log.e(TAG, "Dead display in setNewRcClientOnDisplays_syncRcsCurrc()",e); + di.release(); + displayIterator.remove(); + } + } + } + } + } + + /** + * Update the remote control clients with the new "focused" client generation + */ + private void setNewRcClientGenerationOnClients_syncRcsCurrc(int newClientGeneration) { + // (using an iterator on the stack so we can safely remove an entry if needed, + // traversal order doesn't matter here as we update all entries) + Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator(); + while(stackIterator.hasNext()) { + RemoteControlStackEntry se = stackIterator.next(); + if ((se != null) && (se.mRcClient != null)) { + try { + se.mRcClient.setCurrentClientGenerationId(newClientGeneration); + } catch (RemoteException e) { + Log.w(TAG, "Dead client in setNewRcClientGenerationOnClients_syncRcsCurrc()",e); + stackIterator.remove(); + se.unlinkToRcClientDeath(); + } + } + } + } + + /** + * Update the displays and clients with the new "focused" client generation and name + * @param newClientGeneration the new generation value matching a client update + * @param newMediaIntent the media button event receiver associated with the client. + * May be null, which implies there is no registered media button event receiver. + * @param clearing true if the new client generation value maps to a remote control update + * where the display should be cleared. + */ + private void setNewRcClient_syncRcsCurrc(int newClientGeneration, + PendingIntent newMediaIntent, boolean clearing) { + // send the new valid client generation ID to all displays + setNewRcClientOnDisplays_syncRcsCurrc(newClientGeneration, newMediaIntent, clearing); + // send the new valid client generation ID to all clients + setNewRcClientGenerationOnClients_syncRcsCurrc(newClientGeneration); + } + + /** + * Called when processing MSG_RCDISPLAY_CLEAR event + */ + private void onRcDisplayClear() { + if (DEBUG_RC) Log.i(TAG, "Clear remote control display"); + + synchronized(mRCStack) { + synchronized(mCurrentRcLock) { + mCurrentRcClientGen++; + // synchronously update the displays and clients with the new client generation + setNewRcClient_syncRcsCurrc(mCurrentRcClientGen, + null /*newMediaIntent*/, true /*clearing*/); + } + } + } + + /** + * Called when processing MSG_RCDISPLAY_UPDATE event + */ + private void onRcDisplayUpdate(RemoteControlStackEntry rcse, int flags /* USED ?*/) { + synchronized(mRCStack) { + synchronized(mCurrentRcLock) { + if ((mCurrentRcClient != null) && (mCurrentRcClient.equals(rcse.mRcClient))) { + if (DEBUG_RC) Log.i(TAG, "Display/update remote control "); + + mCurrentRcClientGen++; + // synchronously update the displays and clients with + // the new client generation + setNewRcClient_syncRcsCurrc(mCurrentRcClientGen, + rcse.mMediaIntent /*newMediaIntent*/, + false /*clearing*/); + + // tell the current client that it needs to send info + try { + //TODO change name to informationRequestForAllDisplays() + mCurrentRcClient.onInformationRequested(mCurrentRcClientGen, flags); + } catch (RemoteException e) { + Log.e(TAG, "Current valid remote client is dead: "+e); + mCurrentRcClient = null; + } + } else { + // the remote control display owner has changed between the + // the message to update the display was sent, and the time it + // gets to be processed (now) + } + } + } + } + + /** + * Called when processing MSG_RCDISPLAY_INIT_INFO event + * Causes the current RemoteControlClient to send its info (metadata, playstate...) to + * a single RemoteControlDisplay, NOT all of them, as with MSG_RCDISPLAY_UPDATE. + */ + private void onRcDisplayInitInfo(IRemoteControlDisplay newRcd, int w, int h) { + synchronized(mRCStack) { + synchronized(mCurrentRcLock) { + if (mCurrentRcClient != null) { + if (DEBUG_RC) { Log.i(TAG, "Init RCD with current info"); } + try { + // synchronously update the new RCD with the current client generation + // and matching PendingIntent + newRcd.setCurrentClientId(mCurrentRcClientGen, mCurrentRcClientIntent, + false); + + // tell the current RCC that it needs to send info, but only to the new RCD + try { + mCurrentRcClient.informationRequestForDisplay(newRcd, w, h); + } catch (RemoteException e) { + Log.e(TAG, "Current valid remote client is dead: ", e); + mCurrentRcClient = null; + } + } catch (RemoteException e) { + Log.e(TAG, "Dead display in onRcDisplayInitInfo()", e); + } + } + } + } + } + + /** + * Helper function: + * Called synchronized on mRCStack + */ + private void clearRemoteControlDisplay_syncAfRcs() { + synchronized(mCurrentRcLock) { + mCurrentRcClient = null; + } + // will cause onRcDisplayClear() to be called in AudioService's handler thread + mEventHandler.sendMessage( mEventHandler.obtainMessage(MSG_RCDISPLAY_CLEAR) ); + } + + /** + * Helper function for code readability: only to be called from + * checkUpdateRemoteControlDisplay_syncAfRcs() which checks the preconditions for + * this method. + * Preconditions: + * - called synchronized mAudioFocusLock then on mRCStack + * - mRCStack.isEmpty() is false + */ + private void updateRemoteControlDisplay_syncAfRcs(int infoChangedFlags) { + RemoteControlStackEntry rcse = mRCStack.peek(); + int infoFlagsAboutToBeUsed = infoChangedFlags; + // this is where we enforce opt-in for information display on the remote controls + // with the new AudioManager.registerRemoteControlClient() API + if (rcse.mRcClient == null) { + //Log.w(TAG, "Can't update remote control display with null remote control client"); + clearRemoteControlDisplay_syncAfRcs(); + return; + } + synchronized(mCurrentRcLock) { + if (!rcse.mRcClient.equals(mCurrentRcClient)) { + // new RC client, assume every type of information shall be queried + infoFlagsAboutToBeUsed = RC_INFO_ALL; + } + mCurrentRcClient = rcse.mRcClient; + mCurrentRcClientIntent = rcse.mMediaIntent; + } + // will cause onRcDisplayUpdate() to be called in AudioService's handler thread + mEventHandler.sendMessage( mEventHandler.obtainMessage(MSG_RCDISPLAY_UPDATE, + infoFlagsAboutToBeUsed /* arg1 */, 0, rcse /* obj, != null */) ); + } + + /** + * Helper function: + * Called synchronized on mAudioFocusLock, then mRCStack + * Check whether the remote control display should be updated, triggers the update if required + * @param infoChangedFlags the flags corresponding to the remote control client information + * that has changed, if applicable (checking for the update conditions might trigger a + * clear, rather than an update event). + */ + private void checkUpdateRemoteControlDisplay_syncAfRcs(int infoChangedFlags) { + // determine whether the remote control display should be refreshed + // if either stack is empty, there is a mismatch, so clear the RC display + if (mRCStack.isEmpty() || mFocusStack.isEmpty()) { + clearRemoteControlDisplay_syncAfRcs(); + return; + } + + // determine which entry in the AudioFocus stack to consider, and compare against the + // top of the stack for the media button event receivers : simply using the top of the + // stack would make the entry disappear from the RemoteControlDisplay in conditions such as + // notifications playing during music playback. + // Crawl the AudioFocus stack from the top until an entry is found with the following + // characteristics: + // - focus gain on STREAM_MUSIC stream + // - non-transient focus gain on a stream other than music + FocusRequester af = null; + try { + for (int index = mFocusStack.size()-1; index >= 0; index--) { + FocusRequester fr = mFocusStack.elementAt(index); + if ((fr.getStreamType() == AudioManager.STREAM_MUSIC) + || (fr.getGainRequest() == AudioManager.AUDIOFOCUS_GAIN)) { + af = fr; + break; + } + } + } catch (ArrayIndexOutOfBoundsException e) { + Log.e(TAG, "Wrong index accessing audio focus stack when updating RCD: " + e); + af = null; + } + if (af == null) { + clearRemoteControlDisplay_syncAfRcs(); + return; + } + + // if the audio focus and RC owners belong to different packages, there is a mismatch, clear + if (!af.hasSamePackage(mRCStack.peek().mCallingPackageName)) { + clearRemoteControlDisplay_syncAfRcs(); + return; + } + // if the audio focus didn't originate from the same Uid as the one in which the remote + // control information will be retrieved, clear + if (!af.hasSameUid(mRCStack.peek().mCallingUid)) { + clearRemoteControlDisplay_syncAfRcs(); + return; + } + + // refresh conditions were verified: update the remote controls + // ok to call: synchronized mAudioFocusLock then on mRCStack, mRCStack is not empty + updateRemoteControlDisplay_syncAfRcs(infoChangedFlags); + } + + /** + * Helper function: + * Post a message to asynchronously move the media button event receiver associated with the + * given remote control client ID to the top of the remote control stack + * @param rccId + */ + private void postPromoteRcc(int rccId) { + sendMsg(mEventHandler, MSG_PROMOTE_RCC, SENDMSG_REPLACE, + rccId /*arg1*/, 0, null, 0/*delay*/); + } + + private void onPromoteRcc(int rccId) { + if (DEBUG_RC) { Log.d(TAG, "Promoting RCC " + rccId); } + synchronized(mAudioFocusLock) { + synchronized(mRCStack) { + // ignore if given RCC ID is already at top of remote control stack + if (!mRCStack.isEmpty() && (mRCStack.peek().mRccId == rccId)) { + return; + } + int indexToPromote = -1; + try { + for (int index = mRCStack.size()-1; index >= 0; index--) { + final RemoteControlStackEntry rcse = mRCStack.elementAt(index); + if (rcse.mRccId == rccId) { + indexToPromote = index; + break; + } + } + if (indexToPromote >= 0) { + if (DEBUG_RC) { Log.d(TAG, " moving RCC from index " + indexToPromote + + " to " + (mRCStack.size()-1)); } + final RemoteControlStackEntry rcse = mRCStack.remove(indexToPromote); + mRCStack.push(rcse); + // the RC stack changed, reevaluate the display + checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL); + } + } catch (ArrayIndexOutOfBoundsException e) { + // not expected to happen, indicates improper concurrent modification + Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e); + } + }//synchronized(mRCStack) + }//synchronized(mAudioFocusLock) + } + + /** + * see AudioManager.registerMediaButtonIntent(PendingIntent pi, ComponentName c) + * precondition: mediaIntent != null + */ + protected void registerMediaButtonIntent(PendingIntent mediaIntent, ComponentName eventReceiver, + IBinder token) { + Log.i(TAG, " Remote Control registerMediaButtonIntent() for " + mediaIntent); + + synchronized(mAudioFocusLock) { + synchronized(mRCStack) { + pushMediaButtonReceiver_syncAfRcs(mediaIntent, eventReceiver, token); + // new RC client, assume every type of information shall be queried + checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL); + } + } + } + + /** + * see AudioManager.unregisterMediaButtonIntent(PendingIntent mediaIntent) + * precondition: mediaIntent != null, eventReceiver != null + */ + protected void unregisterMediaButtonIntent(PendingIntent mediaIntent) + { + Log.i(TAG, " Remote Control unregisterMediaButtonIntent() for " + mediaIntent); + + synchronized(mAudioFocusLock) { + synchronized(mRCStack) { + boolean topOfStackWillChange = isCurrentRcController(mediaIntent); + removeMediaButtonReceiver_syncAfRcs(mediaIntent); + if (topOfStackWillChange) { + // current RC client will change, assume every type of info needs to be queried + checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL); + } + } + } + } + + /** + * see AudioManager.registerMediaButtonEventReceiverForCalls(ComponentName c) + * precondition: c != null + */ + protected void registerMediaButtonEventReceiverForCalls(ComponentName c) { + if (mContext.checkCallingPermission("android.permission.MODIFY_PHONE_STATE") + != PackageManager.PERMISSION_GRANTED) { + Log.e(TAG, "Invalid permissions to register media button receiver for calls"); + return; + } + synchronized(mRCStack) { + mMediaReceiverForCalls = c; + } + } + + /** + * see AudioManager.unregisterMediaButtonEventReceiverForCalls() + */ + protected void unregisterMediaButtonEventReceiverForCalls() { + if (mContext.checkCallingPermission("android.permission.MODIFY_PHONE_STATE") + != PackageManager.PERMISSION_GRANTED) { + Log.e(TAG, "Invalid permissions to unregister media button receiver for calls"); + return; + } + synchronized(mRCStack) { + mMediaReceiverForCalls = null; + } + } + + /** + * see AudioManager.registerRemoteControlClient(ComponentName eventReceiver, ...) + * @return the unique ID of the RemoteControlStackEntry associated with the RemoteControlClient + * Note: using this method with rcClient == null is a way to "disable" the IRemoteControlClient + * without modifying the RC stack, but while still causing the display to refresh (will + * become blank as a result of this) + */ + protected int registerRemoteControlClient(PendingIntent mediaIntent, + IRemoteControlClient rcClient, String callingPackageName) { + if (DEBUG_RC) Log.i(TAG, "Register remote control client rcClient="+rcClient); + int rccId = RemoteControlClient.RCSE_ID_UNREGISTERED; + synchronized(mAudioFocusLock) { + synchronized(mRCStack) { + // store the new display information + try { + for (int index = mRCStack.size()-1; index >= 0; index--) { + final RemoteControlStackEntry rcse = mRCStack.elementAt(index); + if(rcse.mMediaIntent.equals(mediaIntent)) { + // already had a remote control client? + if (rcse.mRcClientDeathHandler != null) { + // stop monitoring the old client's death + rcse.unlinkToRcClientDeath(); + } + // save the new remote control client + rcse.mRcClient = rcClient; + rcse.mCallingPackageName = callingPackageName; + rcse.mCallingUid = Binder.getCallingUid(); + if (rcClient == null) { + // here rcse.mRcClientDeathHandler is null; + rcse.resetPlaybackInfo(); + break; + } + rccId = rcse.mRccId; + + // there is a new (non-null) client: + // 1/ give the new client the displays (if any) + if (mRcDisplays.size() > 0) { + plugRemoteControlDisplaysIntoClient_syncRcStack(rcse.mRcClient); + } + // 2/ monitor the new client's death + IBinder b = rcse.mRcClient.asBinder(); + RcClientDeathHandler rcdh = + new RcClientDeathHandler(b, rcse.mMediaIntent); + try { + b.linkToDeath(rcdh, 0); + } catch (RemoteException e) { + // remote control client is DOA, disqualify it + Log.w(TAG, "registerRemoteControlClient() has a dead client " + b); + rcse.mRcClient = null; + } + rcse.mRcClientDeathHandler = rcdh; + break; + } + }//for + } catch (ArrayIndexOutOfBoundsException e) { + // not expected to happen, indicates improper concurrent modification + Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e); + } + + // if the eventReceiver is at the top of the stack + // then check for potential refresh of the remote controls + if (isCurrentRcController(mediaIntent)) { + checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL); + } + }//synchronized(mRCStack) + }//synchronized(mAudioFocusLock) + return rccId; + } + + /** + * see AudioManager.unregisterRemoteControlClient(PendingIntent pi, ...) + * rcClient is guaranteed non-null + */ + protected void unregisterRemoteControlClient(PendingIntent mediaIntent, + IRemoteControlClient rcClient) { + if (DEBUG_RC) Log.i(TAG, "Unregister remote control client rcClient="+rcClient); + synchronized(mAudioFocusLock) { + synchronized(mRCStack) { + boolean topRccChange = false; + try { + for (int index = mRCStack.size()-1; index >= 0; index--) { + final RemoteControlStackEntry rcse = mRCStack.elementAt(index); + if ((rcse.mMediaIntent.equals(mediaIntent)) + && rcClient.equals(rcse.mRcClient)) { + // we found the IRemoteControlClient to unregister + // stop monitoring its death + rcse.unlinkToRcClientDeath(); + // reset the client-related fields + rcse.mRcClient = null; + rcse.mCallingPackageName = null; + topRccChange = (index == mRCStack.size()-1); + // there can only be one matching RCC in the RC stack, we're done + break; + } + } + } catch (ArrayIndexOutOfBoundsException e) { + // not expected to happen, indicates improper concurrent modification + Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e); + } + if (topRccChange) { + // no more RCC for the RCD, check for potential refresh of the remote controls + checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL); + } + } + } + } + + + /** + * A class to encapsulate all the information about a remote control display. + * After instanciation, init() must always be called before the object is added in the list + * of displays. + * Before being removed from the list of displays, release() must always be called (otherwise + * it will leak death handlers). + */ + private class DisplayInfoForServer implements IBinder.DeathRecipient { + /** may never be null */ + private final IRemoteControlDisplay mRcDisplay; + private final IBinder mRcDisplayBinder; + private int mArtworkExpectedWidth = -1; + private int mArtworkExpectedHeight = -1; + private boolean mWantsPositionSync = false; + private ComponentName mClientNotifListComp; + private boolean mEnabled = true; + + public DisplayInfoForServer(IRemoteControlDisplay rcd, int w, int h) { + if (DEBUG_RC) Log.i(TAG, "new DisplayInfoForServer for " + rcd + " w=" + w + " h=" + h); + mRcDisplay = rcd; + mRcDisplayBinder = rcd.asBinder(); + mArtworkExpectedWidth = w; + mArtworkExpectedHeight = h; + } + + public boolean init() { + try { + mRcDisplayBinder.linkToDeath(this, 0); + } catch (RemoteException e) { + // remote control display is DOA, disqualify it + Log.w(TAG, "registerRemoteControlDisplay() has a dead client " + mRcDisplayBinder); + return false; + } + return true; + } + + public void release() { + try { + mRcDisplayBinder.unlinkToDeath(this, 0); + } catch (java.util.NoSuchElementException e) { + // not much we can do here, the display should have been unregistered anyway + Log.e(TAG, "Error in DisplaInfoForServer.relase()", e); + } + } + + public void binderDied() { + synchronized(mRCStack) { + Log.w(TAG, "RemoteControl: display " + mRcDisplay + " died"); + // remove the display from the list + final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator(); + while (displayIterator.hasNext()) { + final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next(); + if (di.mRcDisplay == mRcDisplay) { + if (DEBUG_RC) Log.w(TAG, " RCD removed from list"); + displayIterator.remove(); + return; + } + } + } + } + } + + /** + * The remote control displays. + * Access synchronized on mRCStack + */ + private ArrayList<DisplayInfoForServer> mRcDisplays = new ArrayList<DisplayInfoForServer>(1); + + /** + * Plug each registered display into the specified client + * @param rcc, guaranteed non null + */ + private void plugRemoteControlDisplaysIntoClient_syncRcStack(IRemoteControlClient rcc) { + final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator(); + while (displayIterator.hasNext()) { + final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next(); + try { + rcc.plugRemoteControlDisplay(di.mRcDisplay, di.mArtworkExpectedWidth, + di.mArtworkExpectedHeight); + if (di.mWantsPositionSync) { + rcc.setWantsSyncForDisplay(di.mRcDisplay, true); + } + } catch (RemoteException e) { + Log.e(TAG, "Error connecting RCD to RCC in RCC registration",e); + } + } + } + + private void enableRemoteControlDisplayForClient_syncRcStack(IRemoteControlDisplay rcd, + boolean enabled) { + // let all the remote control clients know whether the given display is enabled + // (so the remote control stack traversal order doesn't matter). + final Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator(); + while(stackIterator.hasNext()) { + RemoteControlStackEntry rcse = stackIterator.next(); + if(rcse.mRcClient != null) { + try { + rcse.mRcClient.enableRemoteControlDisplay(rcd, enabled); + } catch (RemoteException e) { + Log.e(TAG, "Error connecting RCD to client: ", e); + } + } + } + } + + /** + * Is the remote control display interface already registered + * @param rcd + * @return true if the IRemoteControlDisplay is already in the list of displays + */ + private boolean rcDisplayIsPluggedIn_syncRcStack(IRemoteControlDisplay rcd) { + final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator(); + while (displayIterator.hasNext()) { + final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next(); + if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) { + return true; + } + } + return false; + } + + /** + * Register an IRemoteControlDisplay. + * Notify all IRemoteControlClient of the new display and cause the RemoteControlClient + * at the top of the stack to update the new display with its information. + * @see android.media.IAudioService#registerRemoteControlDisplay(android.media.IRemoteControlDisplay, int, int) + * @param rcd the IRemoteControlDisplay to register. No effect if null. + * @param w the maximum width of the expected bitmap. Negative or zero values indicate this + * display doesn't need to receive artwork. + * @param h the maximum height of the expected bitmap. Negative or zero values indicate this + * display doesn't need to receive artwork. + * @param listenerComp the component for the listener interface, may be null if it's not needed + * to verify it belongs to one of the enabled notification listeners + */ + private void registerRemoteControlDisplay_int(IRemoteControlDisplay rcd, int w, int h, + ComponentName listenerComp) { + if (DEBUG_RC) Log.d(TAG, ">>> registerRemoteControlDisplay("+rcd+")"); + synchronized(mAudioFocusLock) { + synchronized(mRCStack) { + if ((rcd == null) || rcDisplayIsPluggedIn_syncRcStack(rcd)) { + return; + } + DisplayInfoForServer di = new DisplayInfoForServer(rcd, w, h); + di.mEnabled = true; + di.mClientNotifListComp = listenerComp; + if (!di.init()) { + if (DEBUG_RC) Log.e(TAG, " error registering RCD"); + return; + } + // add RCD to list of displays + mRcDisplays.add(di); + + // let all the remote control clients know there is a new display (so the remote + // control stack traversal order doesn't matter). + Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator(); + while(stackIterator.hasNext()) { + RemoteControlStackEntry rcse = stackIterator.next(); + if(rcse.mRcClient != null) { + try { + rcse.mRcClient.plugRemoteControlDisplay(rcd, w, h); + } catch (RemoteException e) { + Log.e(TAG, "Error connecting RCD to client: ", e); + } + } + } + + // we have a new display, of which all the clients are now aware: have it be + // initialized wih the current gen ID and the current client info, do not + // reset the information for the other (existing) displays + sendMsg(mEventHandler, MSG_RCDISPLAY_INIT_INFO, SENDMSG_QUEUE, + w /*arg1*/, h /*arg2*/, + rcd /*obj*/, 0/*delay*/); + } + } + } + + /** + * Unregister an IRemoteControlDisplay. + * No effect if the IRemoteControlDisplay hasn't been successfully registered. + * @see android.media.IAudioService#unregisterRemoteControlDisplay(android.media.IRemoteControlDisplay) + * @param rcd the IRemoteControlDisplay to unregister. No effect if null. + */ + protected void unregisterRemoteControlDisplay(IRemoteControlDisplay rcd) { + if (DEBUG_RC) Log.d(TAG, "<<< unregisterRemoteControlDisplay("+rcd+")"); + synchronized(mRCStack) { + if (rcd == null) { + return; + } + + boolean displayWasPluggedIn = false; + final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator(); + while (displayIterator.hasNext() && !displayWasPluggedIn) { + final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next(); + if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) { + displayWasPluggedIn = true; + di.release(); + displayIterator.remove(); + } + } + + if (displayWasPluggedIn) { + // disconnect this remote control display from all the clients, so the remote + // control stack traversal order doesn't matter + final Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator(); + while(stackIterator.hasNext()) { + final RemoteControlStackEntry rcse = stackIterator.next(); + if(rcse.mRcClient != null) { + try { + rcse.mRcClient.unplugRemoteControlDisplay(rcd); + } catch (RemoteException e) { + Log.e(TAG, "Error disconnecting remote control display to client: ", e); + } + } + } + } else { + if (DEBUG_RC) Log.w(TAG, " trying to unregister unregistered RCD"); + } + } + } + + /** + * Update the size of the artwork used by an IRemoteControlDisplay. + * @see android.media.IAudioService#remoteControlDisplayUsesBitmapSize(android.media.IRemoteControlDisplay, int, int) + * @param rcd the IRemoteControlDisplay with the new artwork size requirement + * @param w the maximum width of the expected bitmap. Negative or zero values indicate this + * display doesn't need to receive artwork. + * @param h the maximum height of the expected bitmap. Negative or zero values indicate this + * display doesn't need to receive artwork. + */ + protected void remoteControlDisplayUsesBitmapSize(IRemoteControlDisplay rcd, int w, int h) { + synchronized(mRCStack) { + final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator(); + boolean artworkSizeUpdate = false; + while (displayIterator.hasNext() && !artworkSizeUpdate) { + final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next(); + if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) { + if ((di.mArtworkExpectedWidth != w) || (di.mArtworkExpectedHeight != h)) { + di.mArtworkExpectedWidth = w; + di.mArtworkExpectedHeight = h; + artworkSizeUpdate = true; + } + } + } + if (artworkSizeUpdate) { + // RCD is currently plugged in and its artwork size has changed, notify all RCCs, + // stack traversal order doesn't matter + final Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator(); + while(stackIterator.hasNext()) { + final RemoteControlStackEntry rcse = stackIterator.next(); + if(rcse.mRcClient != null) { + try { + rcse.mRcClient.setBitmapSizeForDisplay(rcd, w, h); + } catch (RemoteException e) { + Log.e(TAG, "Error setting bitmap size for RCD on RCC: ", e); + } + } + } + } + } + } + + /** + * Controls whether a remote control display needs periodic checks of the RemoteControlClient + * playback position to verify that the estimated position has not drifted from the actual + * position. By default the check is not performed. + * The IRemoteControlDisplay must have been previously registered for this to have any effect. + * @param rcd the IRemoteControlDisplay for which the anti-drift mechanism will be enabled + * or disabled. Not null. + * @param wantsSync if true, RemoteControlClient instances which expose their playback position + * to the framework will regularly compare the estimated playback position with the actual + * position, and will update the IRemoteControlDisplay implementation whenever a drift is + * detected. + */ + protected void remoteControlDisplayWantsPlaybackPositionSync(IRemoteControlDisplay rcd, + boolean wantsSync) { + synchronized(mRCStack) { + boolean rcdRegistered = false; + // store the information about this display + // (display stack traversal order doesn't matter). + final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator(); + while (displayIterator.hasNext()) { + final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next(); + if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) { + di.mWantsPositionSync = wantsSync; + rcdRegistered = true; + break; + } + } + if (!rcdRegistered) { + return; + } + // notify all current RemoteControlClients + // (stack traversal order doesn't matter as we notify all RCCs) + final Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator(); + while (stackIterator.hasNext()) { + final RemoteControlStackEntry rcse = stackIterator.next(); + if (rcse.mRcClient != null) { + try { + rcse.mRcClient.setWantsSyncForDisplay(rcd, wantsSync); + } catch (RemoteException e) { + Log.e(TAG, "Error setting position sync flag for RCD on RCC: ", e); + } + } + } + } + } + + protected void setRemoteControlClientPlaybackPosition(int generationId, long timeMs) { + // ignore position change requests if invalid generation ID + synchronized(mRCStack) { + synchronized(mCurrentRcLock) { + if (mCurrentRcClientGen != generationId) { + return; + } + } + } + // discard any unprocessed seek request in the message queue, and replace with latest + sendMsg(mEventHandler, MSG_RCC_SEEK_REQUEST, SENDMSG_REPLACE, generationId /* arg1 */, + 0 /* arg2 ignored*/, new Long(timeMs) /* obj */, 0 /* delay */); + } + + private void onSetRemoteControlClientPlaybackPosition(int generationId, long timeMs) { + if(DEBUG_RC) Log.d(TAG, "onSetRemoteControlClientPlaybackPosition(genId=" + generationId + + ", timeMs=" + timeMs + ")"); + synchronized(mRCStack) { + synchronized(mCurrentRcLock) { + if ((mCurrentRcClient != null) && (mCurrentRcClientGen == generationId)) { + // tell the current client to seek to the requested location + try { + mCurrentRcClient.seekTo(generationId, timeMs); + } catch (RemoteException e) { + Log.e(TAG, "Current valid remote client is dead: "+e); + mCurrentRcClient = null; + } + } + } + } + } + + protected void updateRemoteControlClientMetadata(int genId, int key, Rating value) { + sendMsg(mEventHandler, MSG_RCC_UPDATE_METADATA, SENDMSG_QUEUE, + genId /* arg1 */, key /* arg2 */, value /* obj */, 0 /* delay */); + } + + private void onUpdateRemoteControlClientMetadata(int genId, int key, Rating value) { + if(DEBUG_RC) Log.d(TAG, "onUpdateRemoteControlClientMetadata(genId=" + genId + + ", what=" + key + ",rating=" + value + ")"); + synchronized(mRCStack) { + synchronized(mCurrentRcLock) { + if ((mCurrentRcClient != null) && (mCurrentRcClientGen == genId)) { + try { + switch (key) { + case MediaMetadataEditor.RATING_KEY_BY_USER: + mCurrentRcClient.updateMetadata(genId, key, value); + break; + default: + Log.e(TAG, "unhandled metadata key " + key + " update for RCC " + + genId); + break; + } + } catch (RemoteException e) { + Log.e(TAG, "Current valid remote client is dead", e); + mCurrentRcClient = null; + } + } + } + } + } + + protected void setPlaybackInfoForRcc(int rccId, int what, int value) { + sendMsg(mEventHandler, MSG_RCC_NEW_PLAYBACK_INFO, SENDMSG_QUEUE, + rccId /* arg1 */, what /* arg2 */, Integer.valueOf(value) /* obj */, 0 /* delay */); + } + + // handler for MSG_RCC_NEW_PLAYBACK_INFO + private void onNewPlaybackInfoForRcc(int rccId, int key, int value) { + if(DEBUG_RC) Log.d(TAG, "onNewPlaybackInfoForRcc(id=" + rccId + + ", what=" + key + ",val=" + value + ")"); + synchronized(mRCStack) { + // iterating from top of stack as playback information changes are more likely + // on entries at the top of the remote control stack + try { + for (int index = mRCStack.size()-1; index >= 0; index--) { + final RemoteControlStackEntry rcse = mRCStack.elementAt(index); + if (rcse.mRccId == rccId) { + switch (key) { + case RemoteControlClient.PLAYBACKINFO_PLAYBACK_TYPE: + rcse.mPlaybackType = value; + postReevaluateRemote(); + break; + case RemoteControlClient.PLAYBACKINFO_VOLUME: + rcse.mPlaybackVolume = value; + synchronized (mMainRemote) { + if (rccId == mMainRemote.mRccId) { + mMainRemote.mVolume = value; + mVolumeController.postHasNewRemotePlaybackInfo(); + } + } + break; + case RemoteControlClient.PLAYBACKINFO_VOLUME_MAX: + rcse.mPlaybackVolumeMax = value; + synchronized (mMainRemote) { + if (rccId == mMainRemote.mRccId) { + mMainRemote.mVolumeMax = value; + mVolumeController.postHasNewRemotePlaybackInfo(); + } + } + break; + case RemoteControlClient.PLAYBACKINFO_VOLUME_HANDLING: + rcse.mPlaybackVolumeHandling = value; + synchronized (mMainRemote) { + if (rccId == mMainRemote.mRccId) { + mMainRemote.mVolumeHandling = value; + mVolumeController.postHasNewRemotePlaybackInfo(); + } + } + break; + case RemoteControlClient.PLAYBACKINFO_USES_STREAM: + rcse.mPlaybackStream = value; + break; + default: + Log.e(TAG, "unhandled key " + key + " for RCC " + rccId); + break; + } + return; + } + }//for + } catch (ArrayIndexOutOfBoundsException e) { + // not expected to happen, indicates improper concurrent modification + Log.e(TAG, "Wrong index mRCStack on onNewPlaybackInfoForRcc, lock error? ", e); + } + } + } + + protected void setPlaybackStateForRcc(int rccId, int state, long timeMs, float speed) { + sendMsg(mEventHandler, MSG_RCC_NEW_PLAYBACK_STATE, SENDMSG_QUEUE, + rccId /* arg1 */, state /* arg2 */, + new RccPlaybackState(state, timeMs, speed) /* obj */, 0 /* delay */); + } + + private void onNewPlaybackStateForRcc(int rccId, int state, RccPlaybackState newState) { + if(DEBUG_RC) Log.d(TAG, "onNewPlaybackStateForRcc(id=" + rccId + ", state=" + state + + ", time=" + newState.mPositionMs + ", speed=" + newState.mSpeed + ")"); + synchronized(mRCStack) { + // iterating from top of stack as playback information changes are more likely + // on entries at the top of the remote control stack + try { + for (int index = mRCStack.size()-1; index >= 0; index--) { + final RemoteControlStackEntry rcse = mRCStack.elementAt(index); + if (rcse.mRccId == rccId) { + rcse.mPlaybackState = newState; + synchronized (mMainRemote) { + if (rccId == mMainRemote.mRccId) { + mMainRemoteIsActive = isPlaystateActive(state); + postReevaluateRemote(); + } + } + // an RCC moving to a "playing" state should become the media button + // event receiver so it can be controlled, without requiring the + // app to re-register its receiver + if (isPlaystateActive(state)) { + postPromoteRcc(rccId); + } + } + }//for + } catch (ArrayIndexOutOfBoundsException e) { + // not expected to happen, indicates improper concurrent modification + Log.e(TAG, "Wrong index on mRCStack in onNewPlaybackStateForRcc, lock error? ", e); + } + } + } + + protected void registerRemoteVolumeObserverForRcc(int rccId, IRemoteVolumeObserver rvo) { + sendMsg(mEventHandler, MSG_RCC_NEW_VOLUME_OBS, SENDMSG_QUEUE, + rccId /* arg1 */, 0, rvo /* obj */, 0 /* delay */); + } + + // handler for MSG_RCC_NEW_VOLUME_OBS + private void onRegisterVolumeObserverForRcc(int rccId, IRemoteVolumeObserver rvo) { + synchronized(mRCStack) { + // The stack traversal order doesn't matter because there is only one stack entry + // with this RCC ID, but the matching ID is more likely at the top of the stack, so + // start iterating from the top. + try { + for (int index = mRCStack.size()-1; index >= 0; index--) { + final RemoteControlStackEntry rcse = mRCStack.elementAt(index); + if (rcse.mRccId == rccId) { + rcse.mRemoteVolumeObs = rvo; + break; + } + } + } catch (ArrayIndexOutOfBoundsException e) { + // not expected to happen, indicates improper concurrent modification + Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e); + } + } + } + + /** + * Checks if a remote client is active on the supplied stream type. Update the remote stream + * volume state if found and playing + * @param streamType + * @return false if no remote playing is currently playing + */ + protected boolean checkUpdateRemoteStateIfActive(int streamType) { + synchronized(mRCStack) { + // iterating from top of stack as active playback is more likely on entries at the top + try { + for (int index = mRCStack.size()-1; index >= 0; index--) { + final RemoteControlStackEntry rcse = mRCStack.elementAt(index); + if ((rcse.mPlaybackType == RemoteControlClient.PLAYBACK_TYPE_REMOTE) + && isPlaystateActive(rcse.mPlaybackState.mState) + && (rcse.mPlaybackStream == streamType)) { + if (DEBUG_RC) Log.d(TAG, "remote playback active on stream " + streamType + + ", vol =" + rcse.mPlaybackVolume); + synchronized (mMainRemote) { + mMainRemote.mRccId = rcse.mRccId; + mMainRemote.mVolume = rcse.mPlaybackVolume; + mMainRemote.mVolumeMax = rcse.mPlaybackVolumeMax; + mMainRemote.mVolumeHandling = rcse.mPlaybackVolumeHandling; + mMainRemoteIsActive = true; + } + return true; + } + } + } catch (ArrayIndexOutOfBoundsException e) { + // not expected to happen, indicates improper concurrent modification + Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e); + } + } + synchronized (mMainRemote) { + mMainRemoteIsActive = false; + } + return false; + } + + /** + * Returns true if the given playback state is considered "active", i.e. it describes a state + * where playback is happening, or about to + * @param playState the playback state to evaluate + * @return true if active, false otherwise (inactive or unknown) + */ + private static boolean isPlaystateActive(int playState) { + switch (playState) { + case RemoteControlClient.PLAYSTATE_PLAYING: + case RemoteControlClient.PLAYSTATE_BUFFERING: + case RemoteControlClient.PLAYSTATE_FAST_FORWARDING: + case RemoteControlClient.PLAYSTATE_REWINDING: + case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS: + case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS: + return true; + default: + return false; + } + } + + protected void adjustRemoteVolume(int streamType, int direction, int flags) { + int rccId = RemoteControlClient.RCSE_ID_UNREGISTERED; + boolean volFixed = false; + synchronized (mMainRemote) { + if (!mMainRemoteIsActive) { + if (DEBUG_VOL) Log.w(TAG, "adjustRemoteVolume didn't find an active client"); + return; + } + rccId = mMainRemote.mRccId; + volFixed = (mMainRemote.mVolumeHandling == + RemoteControlClient.PLAYBACK_VOLUME_FIXED); + } + // unlike "local" stream volumes, we can't compute the new volume based on the direction, + // we can only notify the remote that volume needs to be updated, and we'll get an async' + // update through setPlaybackInfoForRcc() + if (!volFixed) { + sendVolumeUpdateToRemote(rccId, direction); + } + + // fire up the UI + mVolumeController.postRemoteVolumeChanged(streamType, flags); + } + + private void sendVolumeUpdateToRemote(int rccId, int direction) { + if (DEBUG_VOL) { Log.d(TAG, "sendVolumeUpdateToRemote(rccId="+rccId+" , dir="+direction); } + if (direction == 0) { + // only handling discrete events + return; + } + IRemoteVolumeObserver rvo = null; + synchronized (mRCStack) { + // The stack traversal order doesn't matter because there is only one stack entry + // with this RCC ID, but the matching ID is more likely at the top of the stack, so + // start iterating from the top. + try { + for (int index = mRCStack.size()-1; index >= 0; index--) { + final RemoteControlStackEntry rcse = mRCStack.elementAt(index); + //FIXME OPTIMIZE store this info in mMainRemote so we don't have to iterate? + if (rcse.mRccId == rccId) { + rvo = rcse.mRemoteVolumeObs; + break; + } + } + } catch (ArrayIndexOutOfBoundsException e) { + // not expected to happen, indicates improper concurrent modification + Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e); + } + } + if (rvo != null) { + try { + rvo.dispatchRemoteVolumeUpdate(direction, -1); + } catch (RemoteException e) { + Log.e(TAG, "Error dispatching relative volume update", e); + } + } + } + + protected int getRemoteStreamMaxVolume() { + synchronized (mMainRemote) { + if (mMainRemote.mRccId == RemoteControlClient.RCSE_ID_UNREGISTERED) { + return 0; + } + return mMainRemote.mVolumeMax; + } + } + + protected int getRemoteStreamVolume() { + synchronized (mMainRemote) { + if (mMainRemote.mRccId == RemoteControlClient.RCSE_ID_UNREGISTERED) { + return 0; + } + return mMainRemote.mVolume; + } + } + + protected void setRemoteStreamVolume(int vol) { + if (DEBUG_VOL) { Log.d(TAG, "setRemoteStreamVolume(vol="+vol+")"); } + int rccId = RemoteControlClient.RCSE_ID_UNREGISTERED; + synchronized (mMainRemote) { + if (mMainRemote.mRccId == RemoteControlClient.RCSE_ID_UNREGISTERED) { + return; + } + rccId = mMainRemote.mRccId; + } + IRemoteVolumeObserver rvo = null; + synchronized (mRCStack) { + // The stack traversal order doesn't matter because there is only one stack entry + // with this RCC ID, but the matching ID is more likely at the top of the stack, so + // start iterating from the top. + try { + for (int index = mRCStack.size()-1; index >= 0; index--) { + final RemoteControlStackEntry rcse = mRCStack.elementAt(index); + //FIXME OPTIMIZE store this info in mMainRemote so we don't have to iterate? + if (rcse.mRccId == rccId) { + rvo = rcse.mRemoteVolumeObs; + break; + } + } + } catch (ArrayIndexOutOfBoundsException e) { + // not expected to happen, indicates improper concurrent modification + Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e); + } + } + if (rvo != null) { + try { + rvo.dispatchRemoteVolumeUpdate(0, vol); + } catch (RemoteException e) { + Log.e(TAG, "Error dispatching absolute volume update", e); + } + } + } + + /** + * Call to make AudioService reevaluate whether it's in a mode where remote players should + * have their volume controlled. In this implementation this is only to reset whether + * VolumePanel should display remote volumes + */ + private void postReevaluateRemote() { + sendMsg(mEventHandler, MSG_REEVALUATE_REMOTE, SENDMSG_QUEUE, 0, 0, null, 0); + } + + private void onReevaluateRemote() { + if (DEBUG_VOL) { Log.w(TAG, "onReevaluateRemote()"); } + // is there a registered RemoteControlClient that is handling remote playback + boolean hasRemotePlayback = false; + synchronized (mRCStack) { + // iteration stops when PLAYBACK_TYPE_REMOTE is found, so remote control stack + // traversal order doesn't matter + Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator(); + while(stackIterator.hasNext()) { + RemoteControlStackEntry rcse = stackIterator.next(); + if (rcse.mPlaybackType == RemoteControlClient.PLAYBACK_TYPE_REMOTE) { + hasRemotePlayback = true; + break; + } + } + } + synchronized (mMainRemote) { + if (mHasRemotePlayback != hasRemotePlayback) { + mHasRemotePlayback = hasRemotePlayback; + mVolumeController.postRemoteSliderVisibility(hasRemotePlayback); + } + } + } + +} diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java index 3fbaf69..0f7906e 100644 --- a/media/java/android/media/MediaFormat.java +++ b/media/java/android/media/MediaFormat.java @@ -26,7 +26,7 @@ import java.util.Map; * * The format of the media data is specified as string/value pairs. * - * Keys common to all formats, <b>all keys not marked optional are mandatory</b>: + * Keys common to all audio/video formats, <b>all keys not marked optional are mandatory</b>: * * <table> * <tr><th>Name</th><th>Value Type</th><th>Description</th></tr> @@ -44,7 +44,19 @@ import java.util.Map; * for encoders, readable in the output format of decoders</b></td></tr> * <tr><td>{@link #KEY_FRAME_RATE}</td><td>Integer or Float</td><td><b>encoder-only</b></td></tr> * <tr><td>{@link #KEY_I_FRAME_INTERVAL}</td><td>Integer</td><td><b>encoder-only</b></td></tr> + * <tr><td>{@link #KEY_MAX_WIDTH}</td><td>Integer</td><td><b>decoder-only</b>, optional, max-resolution width</td></tr> + * <tr><td>{@link #KEY_MAX_HEIGHT}</td><td>Integer</td><td><b>decoder-only</b>, optional, max-resolution height</td></tr> + * <tr><td>{@link #KEY_REPEAT_PREVIOUS_FRAME_AFTER}</td><td>Long</td><td><b>video encoder in surface-mode only</b></td></tr> + * <tr><td>{@link #KEY_PUSH_BLANK_BUFFERS_ON_STOP}</td><td>Integer(1)</td><td><b>video decoder rendering to a surface only</b></td></tr> * </table> + * Specify both {@link #KEY_MAX_WIDTH} and {@link #KEY_MAX_HEIGHT} to enable + * adaptive playback (seamless resolution change) for a video decoder that + * supports it ({@link MediaCodecInfo.CodecCapabilities#FEATURE_AdaptivePlayback}). + * The values are used as hints for the codec: they are the maximum expected + * resolution to prepare for. Depending on codec support, preparing for larger + * maximum resolution may require more memory even if that resolution is never + * reached. These fields have no effect for codecs that do not support adaptive + * playback.<br /><br /> * * Audio formats have the following keys: * <table> @@ -57,6 +69,11 @@ import java.util.Map; * <tr><td>{@link #KEY_FLAC_COMPRESSION_LEVEL}</td><td>Integer</td><td><b>encoder-only</b>, optional, if content is FLAC audio, specifies the desired compression level.</td></tr> * </table> * + * Subtitle formats have the following keys: + * <table> + * <tr><td>{@link #KEY_MIME}</td><td>String</td><td>The type of the format.</td></tr> + * <tr><td>{@link #KEY_LANGUAGE}</td><td>String</td><td>The language of the content.</td></tr> + * </table> */ public final class MediaFormat { private Map<String, Object> mMap; @@ -68,6 +85,12 @@ public final class MediaFormat { public static final String KEY_MIME = "mime"; /** + * A key describing the language of the content, using either ISO 639-1 + * or 639-2/T codes. The associated value is a string. + */ + public static final String KEY_LANGUAGE = "language"; + + /** * A key describing the sample rate of an audio format. * The associated value is an integer */ @@ -91,6 +114,20 @@ public final class MediaFormat { */ public static final String KEY_HEIGHT = "height"; + /** + * A key describing the maximum expected width of the content in a video + * decoder format, in case there are resolution changes in the video content. + * The associated value is an integer + */ + public static final String KEY_MAX_WIDTH = "max-width"; + + /** + * A key describing the maximum expected height of the content in a video + * decoder format, in case there are resolution changes in the video content. + * The associated value is an integer + */ + public static final String KEY_MAX_HEIGHT = "max-height"; + /** A key describing the maximum size in bytes of a buffer of data * described by this MediaFormat. * The associated value is an integer @@ -132,6 +169,24 @@ public final class MediaFormat { public static final String KEY_SLICE_HEIGHT = "slice-height"; /** + * Applies only when configuring a video encoder in "surface-input" mode. + * The associated value is a long and gives the time in microseconds + * after which the frame previously submitted to the encoder will be + * repeated (once) if no new frame became available since. + */ + public static final String KEY_REPEAT_PREVIOUS_FRAME_AFTER + = "repeat-previous-frame-after"; + + /** + * If specified when configuring a video decoder rendering to a surface, + * causes the decoder to output "blank", i.e. black frames to the surface + * when stopped to clear out any previously displayed contents. + * The associated value is an integer of value 1. + */ + public static final String KEY_PUSH_BLANK_BUFFERS_ON_STOP + = "push-blank-buffers-on-shutdown"; + + /** * A key describing the duration (in microseconds) of the content. * The associated value is a long. */ @@ -166,6 +221,38 @@ public final class MediaFormat { */ public static final String KEY_FLAC_COMPRESSION_LEVEL = "flac-compression-level"; + /** + * A key for boolean AUTOSELECT behavior for the track. Tracks with AUTOSELECT=true + * are considered when automatically selecting a track without specific user + * choice, based on the current locale. + * This is currently only used for subtitle tracks, when the user selected + * 'Default' for the captioning locale. + * The associated value is an integer, where non-0 means TRUE. This is an optional + * field; if not specified, AUTOSELECT defaults to TRUE. + */ + public static final String KEY_IS_AUTOSELECT = "is-autoselect"; + + /** + * A key for boolean DEFAULT behavior for the track. The track with DEFAULT=true is + * selected in the absence of a specific user choice. + * This is currently only used for subtitle tracks, when the user selected + * 'Default' for the captioning locale. + * The associated value is an integer, where non-0 means TRUE. This is an optional + * field; if not specified, DEFAULT is considered to be FALSE. + */ + public static final String KEY_IS_DEFAULT = "is-default"; + + + /** + * A key for the FORCED field for subtitle tracks. True if it is a + * forced subtitle track. Forced subtitle tracks are essential for the + * content and are shown even when the user turns off Captions. They + * are used for example to translate foreign/alien dialogs or signs. + * The associated value is an integer, where non-0 means TRUE. This is an + * optional field; if not specified, FORCED defaults to FALSE. + */ + public static final String KEY_IS_FORCED_SUBTITLE = "is-forced-subtitle"; + /* package private */ MediaFormat(Map<String, Object> map) { mMap = map; } @@ -196,6 +283,20 @@ public final class MediaFormat { } /** + * Returns the value of an integer key, or the default value if the + * key is missing or is for another type value. + * @hide + */ + public final int getInteger(String name, int defaultValue) { + try { + return getInteger(name); + } + catch (NullPointerException e) { /* no such field */ } + catch (ClassCastException e) { /* field of different type */ } + return defaultValue; + } + + /** * Returns the value of a long key. */ public final long getLong(String name) { @@ -277,6 +378,24 @@ public final class MediaFormat { } /** + * Creates a minimal subtitle format. + * @param mime The mime type of the content. + * @param language The language of the content, using either ISO 639-1 or 639-2/T + * codes. Specify null or "und" if language information is only included + * in the content. (This will also work if there are multiple language + * tracks in the content.) + */ + public static final MediaFormat createSubtitleFormat( + String mime, + String language) { + MediaFormat format = new MediaFormat(); + format.setString(KEY_MIME, mime); + format.setString(KEY_LANGUAGE, language); + + return format; + } + + /** * Creates a minimal video format. * @param mime The mime type of the content. * @param width The width of the content (in pixels) diff --git a/media/java/android/media/MediaMetadataEditor.java b/media/java/android/media/MediaMetadataEditor.java new file mode 100644 index 0000000..373ba11 --- /dev/null +++ b/media/java/android/media/MediaMetadataEditor.java @@ -0,0 +1,462 @@ +/* + * Copyright (C) 2013 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.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; +import android.util.SparseIntArray; + +/** + * An abstract class for editing and storing metadata that can be published by + * {@link RemoteControlClient}. See the {@link RemoteControlClient#editMetadata(boolean)} + * method to instantiate a {@link RemoteControlClient.MetadataEditor} object. + */ +public abstract class MediaMetadataEditor { + + private final static String TAG = "MediaMetadataEditor"; + /** + * @hide + */ + protected MediaMetadataEditor() { + } + + // Public keys for metadata used by RemoteControlClient and RemoteController. + // Note that these keys are defined here, and not in MediaMetadataRetriever + // because they are not supported by the MediaMetadataRetriever features. + /** + * The metadata key for the content artwork / album art. + */ + public final static int BITMAP_KEY_ARTWORK = + RemoteControlClient.MetadataEditor.BITMAP_KEY_ARTWORK; + + /** + * The metadata key for the content's average rating, not the user's rating. + * The value associated with this key is a {@link Rating} instance. + * @see #RATING_KEY_BY_USER + */ + public final static int RATING_KEY_BY_OTHERS = 101; + + /** + * The metadata key for the content's user rating. + * The value associated with this key is a {@link Rating} instance. + * This key can be flagged as "editable" (with {@link #addEditableKey(int)}) to enable + * receiving user rating values through the + * {@link android.media.RemoteControlClient.OnMetadataUpdateListener} interface. + */ + public final static int RATING_KEY_BY_USER = 0x10000001; + + /** + * @hide + * Editable key mask + */ + public final static int KEY_EDITABLE_MASK = 0x1FFFFFFF; + + + /** + * Applies all of the metadata changes that have been set since the MediaMetadataEditor instance + * was created or since {@link #clear()} was called. + */ + public abstract void apply(); + + + /** + * @hide + * Mask of editable keys. + */ + protected long mEditableKeys; + + /** + * @hide + */ + protected boolean mMetadataChanged = false; + + /** + * @hide + */ + protected boolean mApplied = false; + + /** + * @hide + */ + protected boolean mArtworkChanged = false; + + /** + * @hide + */ + protected Bitmap mEditorArtwork; + + /** + * @hide + */ + protected Bundle mEditorMetadata; + + + /** + * Clears all the pending metadata changes set since the MediaMetadataEditor instance was + * created or since this method was last called. + * Note that clearing the metadata doesn't reset the editable keys + * (use {@link #removeEditableKeys()} instead). + */ + public synchronized void clear() { + if (mApplied) { + Log.e(TAG, "Can't clear a previously applied MediaMetadataEditor"); + return; + } + mEditorMetadata.clear(); + mEditorArtwork = null; + } + + /** + * Flags the given key as being editable. + * This should only be used by metadata publishers, such as {@link RemoteControlClient}, + * which will declare the metadata field as eligible to be updated, with new values + * received through the {@link RemoteControlClient.OnMetadataUpdateListener} interface. + * @param key the type of metadata that can be edited. The supported key is + * {@link #RATING_KEY_BY_USER}. + */ + public synchronized void addEditableKey(int key) { + if (mApplied) { + Log.e(TAG, "Can't change editable keys of a previously applied MetadataEditor"); + return; + } + // only one editable key at the moment, so we're not wasting memory on an array + // of editable keys to check the validity of the key, just hardcode the supported key. + if (key == RATING_KEY_BY_USER) { + mEditableKeys |= (KEY_EDITABLE_MASK & key); + mMetadataChanged = true; + } else { + Log.e(TAG, "Metadata key " + key + " cannot be edited"); + } + } + + /** + * Causes all metadata fields to be read-only. + */ + public synchronized void removeEditableKeys() { + if (mApplied) { + Log.e(TAG, "Can't remove all editable keys of a previously applied MetadataEditor"); + return; + } + if (mEditableKeys != 0) { + mEditableKeys = 0; + mMetadataChanged = true; + } + } + + /** + * Retrieves the keys flagged as editable. + * @return null if there are no editable keys, or an array containing the keys. + */ + public synchronized int[] getEditableKeys() { + // only one editable key supported here + if (mEditableKeys == RATING_KEY_BY_USER) { + int[] keys = { RATING_KEY_BY_USER }; + return keys; + } else { + return null; + } + } + + /** + * Adds textual information. + * Note that none of the information added after {@link #apply()} has been called, + * will be available to consumers of metadata stored by the MediaMetadataEditor. + * @param key The identifier of a the metadata field to set. Valid values are + * {@link android.media.MediaMetadataRetriever#METADATA_KEY_ALBUM}, + * {@link android.media.MediaMetadataRetriever#METADATA_KEY_ALBUMARTIST}, + * {@link android.media.MediaMetadataRetriever#METADATA_KEY_TITLE}, + * {@link android.media.MediaMetadataRetriever#METADATA_KEY_ARTIST}, + * {@link android.media.MediaMetadataRetriever#METADATA_KEY_AUTHOR}, + * {@link android.media.MediaMetadataRetriever#METADATA_KEY_COMPILATION}, + * {@link android.media.MediaMetadataRetriever#METADATA_KEY_COMPOSER}, + * {@link android.media.MediaMetadataRetriever#METADATA_KEY_DATE}, + * {@link android.media.MediaMetadataRetriever#METADATA_KEY_GENRE}, + * {@link android.media.MediaMetadataRetriever#METADATA_KEY_WRITER}. + * @param value The text for the given key, or {@code null} to signify there is no valid + * information for the field. + * @return Returns a reference to the same MediaMetadataEditor object, so you can chain put + * calls together. + */ + public synchronized MediaMetadataEditor putString(int key, String value) + throws IllegalArgumentException { + if (mApplied) { + Log.e(TAG, "Can't edit a previously applied MediaMetadataEditor"); + return this; + } + if (METADATA_KEYS_TYPE.get(key, METADATA_TYPE_INVALID) != METADATA_TYPE_STRING) { + throw(new IllegalArgumentException("Invalid type 'String' for key "+ key)); + } + mEditorMetadata.putString(String.valueOf(key), value); + mMetadataChanged = true; + return this; + } + + /** + * Adds numerical information. + * Note that none of the information added after {@link #apply()} has been called + * will be available to consumers of metadata stored by the MediaMetadataEditor. + * @param key the identifier of a the metadata field to set. Valid values are + * {@link android.media.MediaMetadataRetriever#METADATA_KEY_CD_TRACK_NUMBER}, + * {@link android.media.MediaMetadataRetriever#METADATA_KEY_DISC_NUMBER}, + * {@link android.media.MediaMetadataRetriever#METADATA_KEY_DURATION} (with a value + * expressed in milliseconds), + * {@link android.media.MediaMetadataRetriever#METADATA_KEY_YEAR}. + * @param value The long value for the given key + * @return Returns a reference to the same MediaMetadataEditor object, so you can chain put + * calls together. + * @throws IllegalArgumentException + */ + public synchronized MediaMetadataEditor putLong(int key, long value) + throws IllegalArgumentException { + if (mApplied) { + Log.e(TAG, "Can't edit a previously applied MediaMetadataEditor"); + return this; + } + if (METADATA_KEYS_TYPE.get(key, METADATA_TYPE_INVALID) != METADATA_TYPE_LONG) { + throw(new IllegalArgumentException("Invalid type 'long' for key "+ key)); + } + mEditorMetadata.putLong(String.valueOf(key), value); + mMetadataChanged = true; + return this; + } + + /** + * Adds image. + * @param key the identifier of the bitmap to set. The only valid value is + * {@link #BITMAP_KEY_ARTWORK} + * @param bitmap The bitmap for the artwork, or null if there isn't any. + * @return Returns a reference to the same MediaMetadataEditor object, so you can chain put + * calls together. + * @throws IllegalArgumentException + * @see android.graphics.Bitmap + */ + public synchronized MediaMetadataEditor putBitmap(int key, Bitmap bitmap) + throws IllegalArgumentException { + if (mApplied) { + Log.e(TAG, "Can't edit a previously applied MediaMetadataEditor"); + return this; + } + if (key != BITMAP_KEY_ARTWORK) { + throw(new IllegalArgumentException("Invalid type 'Bitmap' for key "+ key)); + } + mEditorArtwork = bitmap; + mArtworkChanged = true; + return this; + } + + /** + * Adds information stored as an instance. + * Note that none of the information added after {@link #apply()} has been called + * will be available to consumers of metadata stored by the MediaMetadataEditor. + * @param key the identifier of a the metadata field to set. Valid keys for a: + * <ul> + * <li>{@link Bitmap} object are {@link #BITMAP_KEY_ARTWORK},</li> + * <li>{@link String} object are the same as for {@link #putString(int, String)}</li> + * <li>{@link Long} object are the same as for {@link #putLong(int, long)}</li> + * <li>{@link Rating} object are {@link #RATING_KEY_BY_OTHERS} + * and {@link #RATING_KEY_BY_USER}.</li> + * </ul> + * @param value the metadata to add. + * @return Returns a reference to the same MediaMetadataEditor object, so you can chain put + * calls together. + * @throws IllegalArgumentException + */ + public synchronized MediaMetadataEditor putObject(int key, Object value) + throws IllegalArgumentException { + if (mApplied) { + Log.e(TAG, "Can't edit a previously applied MediaMetadataEditor"); + return this; + } + switch(METADATA_KEYS_TYPE.get(key, METADATA_TYPE_INVALID)) { + case METADATA_TYPE_LONG: + if (value instanceof Long) { + return putLong(key, ((Long)value).longValue()); + } else { + throw(new IllegalArgumentException("Not a non-null Long for key "+ key)); + } + case METADATA_TYPE_STRING: + if ((value == null) || (value instanceof String)) { + return putString(key, (String) value); + } else { + throw(new IllegalArgumentException("Not a String for key "+ key)); + } + case METADATA_TYPE_RATING: + mEditorMetadata.putParcelable(String.valueOf(key), (Parcelable)value); + mMetadataChanged = true; + break; + case METADATA_TYPE_BITMAP: + if ((value == null) || (value instanceof Bitmap)) { + return putBitmap(key, (Bitmap) value); + } else { + throw(new IllegalArgumentException("Not a Bitmap for key "+ key)); + } + default: + throw(new IllegalArgumentException("Invalid key "+ key)); + } + return this; + } + + + /** + * Returns the long value for the key. + * @param key one of the keys supported in {@link #putLong(int, long)} + * @param defaultValue the value returned if the key is not present + * @return the long value for the key, or the supplied default value if the key is not present + * @throws IllegalArgumentException + */ + public synchronized long getLong(int key, long defaultValue) + throws IllegalArgumentException { + if (METADATA_KEYS_TYPE.get(key, METADATA_TYPE_INVALID) != METADATA_TYPE_LONG) { + throw(new IllegalArgumentException("Invalid type 'long' for key "+ key)); + } + return mEditorMetadata.getLong(String.valueOf(key), defaultValue); + } + + /** + * Returns the {@link String} value for the key. + * @param key one of the keys supported in {@link #putString(int, String)} + * @param defaultValue the value returned if the key is not present + * @return the {@link String} value for the key, or the supplied default value if the key is + * not present + * @throws IllegalArgumentException + */ + public synchronized String getString(int key, String defaultValue) + throws IllegalArgumentException { + if (METADATA_KEYS_TYPE.get(key, METADATA_TYPE_INVALID) != METADATA_TYPE_STRING) { + throw(new IllegalArgumentException("Invalid type 'String' for key "+ key)); + } + return mEditorMetadata.getString(String.valueOf(key), defaultValue); + } + + /** + * Returns the {@link Bitmap} value for the key. + * @param key the {@link #BITMAP_KEY_ARTWORK} key + * @param defaultValue the value returned if the key is not present + * @return the {@link Bitmap} value for the key, or the supplied default value if the key is + * not present + * @throws IllegalArgumentException + */ + public synchronized Bitmap getBitmap(int key, Bitmap defaultValue) + throws IllegalArgumentException { + if (key != BITMAP_KEY_ARTWORK) { + throw(new IllegalArgumentException("Invalid type 'Bitmap' for key "+ key)); + } + return (mEditorArtwork != null ? mEditorArtwork : defaultValue); + } + + /** + * Returns an object representation of the value for the key + * @param key one of the keys supported in {@link #putObject(int, Object)} + * @param defaultValue the value returned if the key is not present + * @return the object for the key, as a {@link Long}, {@link Bitmap}, {@link String}, or + * {@link Rating} depending on the key value, or the supplied default value if the key is + * not present + * @throws IllegalArgumentException + */ + public synchronized Object getObject(int key, Object defaultValue) + throws IllegalArgumentException { + switch (METADATA_KEYS_TYPE.get(key, METADATA_TYPE_INVALID)) { + case METADATA_TYPE_LONG: + if (mEditorMetadata.containsKey(String.valueOf(key))) { + return mEditorMetadata.getLong(String.valueOf(key)); + } else { + return defaultValue; + } + case METADATA_TYPE_STRING: + if (mEditorMetadata.containsKey(String.valueOf(key))) { + return mEditorMetadata.getString(String.valueOf(key)); + } else { + return defaultValue; + } + case METADATA_TYPE_RATING: + if (mEditorMetadata.containsKey(String.valueOf(key))) { + return mEditorMetadata.getParcelable(String.valueOf(key)); + } else { + return defaultValue; + } + case METADATA_TYPE_BITMAP: + // only one key for Bitmap supported, value is not stored in mEditorMetadata Bundle + if (key == BITMAP_KEY_ARTWORK) { + return (mEditorArtwork != null ? mEditorArtwork : defaultValue); + } // else: fall through to invalid key handling + default: + throw(new IllegalArgumentException("Invalid key "+ key)); + } + } + + + /** + * @hide + */ + protected static final int METADATA_TYPE_INVALID = -1; + /** + * @hide + */ + protected static final int METADATA_TYPE_LONG = 0; + + /** + * @hide + */ + protected static final int METADATA_TYPE_STRING = 1; + + /** + * @hide + */ + protected static final int METADATA_TYPE_BITMAP = 2; + + /** + * @hide + */ + protected static final int METADATA_TYPE_RATING = 3; + + /** + * @hide + */ + protected static final SparseIntArray METADATA_KEYS_TYPE; + + static { + METADATA_KEYS_TYPE = new SparseIntArray(17); + // NOTE: if adding to the list below, make sure you increment the array initialization size + // keys with long values + METADATA_KEYS_TYPE.put( + MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER, METADATA_TYPE_LONG); + METADATA_KEYS_TYPE.put(MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER, METADATA_TYPE_LONG); + METADATA_KEYS_TYPE.put(MediaMetadataRetriever.METADATA_KEY_DURATION, METADATA_TYPE_LONG); + METADATA_KEYS_TYPE.put(MediaMetadataRetriever.METADATA_KEY_YEAR, METADATA_TYPE_LONG); + // keys with String values + METADATA_KEYS_TYPE.put(MediaMetadataRetriever.METADATA_KEY_ALBUM, METADATA_TYPE_STRING); + METADATA_KEYS_TYPE.put( + MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST, METADATA_TYPE_STRING); + METADATA_KEYS_TYPE.put(MediaMetadataRetriever.METADATA_KEY_TITLE, METADATA_TYPE_STRING); + METADATA_KEYS_TYPE.put(MediaMetadataRetriever.METADATA_KEY_ARTIST, METADATA_TYPE_STRING); + METADATA_KEYS_TYPE.put(MediaMetadataRetriever.METADATA_KEY_AUTHOR, METADATA_TYPE_STRING); + METADATA_KEYS_TYPE.put( + MediaMetadataRetriever.METADATA_KEY_COMPILATION, METADATA_TYPE_STRING); + METADATA_KEYS_TYPE.put(MediaMetadataRetriever.METADATA_KEY_COMPOSER, METADATA_TYPE_STRING); + METADATA_KEYS_TYPE.put(MediaMetadataRetriever.METADATA_KEY_DATE, METADATA_TYPE_STRING); + METADATA_KEYS_TYPE.put(MediaMetadataRetriever.METADATA_KEY_GENRE, METADATA_TYPE_STRING); + METADATA_KEYS_TYPE.put(MediaMetadataRetriever.METADATA_KEY_WRITER, METADATA_TYPE_STRING); + // keys with Bitmap values + METADATA_KEYS_TYPE.put(BITMAP_KEY_ARTWORK, METADATA_TYPE_BITMAP); + // keys with Rating values + METADATA_KEYS_TYPE.put(RATING_KEY_BY_OTHERS, METADATA_TYPE_RATING); + METADATA_KEYS_TYPE.put(RATING_KEY_BY_USER, METADATA_TYPE_RATING); + } +} diff --git a/media/java/android/media/MediaMuxer.java b/media/java/android/media/MediaMuxer.java index 774964e..65a9308 100644 --- a/media/java/android/media/MediaMuxer.java +++ b/media/java/android/media/MediaMuxer.java @@ -92,6 +92,7 @@ final public class MediaMuxer { Object[] values); private static native void nativeSetOrientationHint(int nativeObject, int degrees); + private static native void nativeSetLocation(int nativeObject, int latitude, int longitude); private static native void nativeWriteSampleData(int nativeObject, int trackIndex, ByteBuffer byteBuf, int offset, int size, long presentationTimeUs, int flags); @@ -165,6 +166,41 @@ final public class MediaMuxer { } /** + * Set and store the geodata (latitude and longitude) in the output file. + * This method should be called before {@link #start}. The geodata is stored + * in udta box if the output format is + * {@link OutputFormat#MUXER_OUTPUT_MPEG_4}, and is ignored for other output + * formats. The geodata is stored according to ISO-6709 standard. + * + * @param latitude Latitude in degrees. Its value must be in the range [-90, + * 90]. + * @param longitude Longitude in degrees. Its value must be in the range + * [-180, 180]. + * @throws IllegalArgumentException If the given latitude or longitude is out + * of range. + * @throws IllegalStateException If this method is called after {@link #start}. + */ + public void setLocation(float latitude, float longitude) { + int latitudex10000 = (int) (latitude * 10000 + 0.5); + int longitudex10000 = (int) (longitude * 10000 + 0.5); + + if (latitudex10000 > 900000 || latitudex10000 < -900000) { + String msg = "Latitude: " + latitude + " out of range."; + throw new IllegalArgumentException(msg); + } + if (longitudex10000 > 1800000 || longitudex10000 < -1800000) { + String msg = "Longitude: " + longitude + " out of range"; + throw new IllegalArgumentException(msg); + } + + if (mState == MUXER_STATE_INITIALIZED && mNativeObject != 0) { + nativeSetLocation(mNativeObject, latitudex10000, longitudex10000); + } else { + throw new IllegalStateException("Can't set location due to wrong state."); + } + } + + /** * Starts the muxer. * <p>Make sure this is called after {@link #addTrack} and before * {@link #writeSampleData}.</p> diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java index b729640..0abd5f8 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -26,11 +26,13 @@ import android.net.Proxy; import android.net.ProxyProperties; import android.net.Uri; import android.os.Handler; +import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.os.Parcel; import android.os.Parcelable; import android.os.ParcelFileDescriptor; +import android.os.Process; import android.os.PowerManager; import android.util.Log; import android.view.Surface; @@ -38,14 +40,23 @@ import android.view.SurfaceHolder; import android.graphics.Bitmap; import android.graphics.SurfaceTexture; import android.media.AudioManager; +import android.media.MediaFormat; +import android.media.MediaTimeProvider; +import android.media.MediaTimeProvider.OnMediaTimeListener; +import android.media.SubtitleController; +import android.media.SubtitleData; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStream; +import java.lang.Runnable; import java.net.InetSocketAddress; import java.util.Map; +import java.util.Scanner; import java.util.Set; +import java.util.Vector; import java.lang.ref.WeakReference; /** @@ -515,7 +526,7 @@ import java.lang.ref.WeakReference; * thread by default has a Looper running). * */ -public class MediaPlayer +public class MediaPlayer implements SubtitleController.Listener { /** Constant to retrieve only the new metadata since the last @@ -588,6 +599,11 @@ public class MediaPlayer mEventHandler = null; } + mTimeProvider = new TimeProvider(this); + mOutOfBandSubtitleTracks = new Vector<SubtitleTrack>(); + mOpenSubtitleSources = new Vector<InputStream>(); + mInbandSubtitleTracks = new SubtitleTrack[0]; + /* Native setup requires a weak reference to our object. * It's easier to create it here than in C++. */ @@ -639,7 +655,6 @@ public class MediaPlayer * * @param reply Output parcel with the data returned by the * native player. - * * {@hide} */ public void invoke(Parcel request, Parcel reply) { @@ -1336,6 +1351,11 @@ public class MediaPlayer mOnInfoListener = null; mOnVideoSizeChangedListener = null; mOnTimedTextListener = null; + if (mTimeProvider != null) { + mTimeProvider.close(); + mTimeProvider = null; + } + mOnSubtitleDataListener = null; _release(); } @@ -1347,10 +1367,32 @@ public class MediaPlayer * data source and calling prepare(). */ public void reset() { + mSelectedSubtitleTrackIndex = -1; + synchronized(mOpenSubtitleSources) { + for (final InputStream is: mOpenSubtitleSources) { + try { + is.close(); + } catch (IOException e) { + } + } + mOpenSubtitleSources.clear(); + } + mOutOfBandSubtitleTracks.clear(); + mInbandSubtitleTracks = new SubtitleTrack[0]; + if (mSubtitleController != null) { + mSubtitleController.reset(); + } + if (mTimeProvider != null) { + mTimeProvider.close(); + mTimeProvider = null; + } + stayAwake(false); _reset(); // make sure none of the listeners get called anymore - mEventHandler.removeCallbacksAndMessages(null); + if (mEventHandler != null) { + mEventHandler.removeCallbacksAndMessages(null); + } disableProxyListener(); } @@ -1410,13 +1452,6 @@ public class MediaPlayer } /** - * Currently not implemented, returns null. - * @deprecated - * @hide - */ - public native Bitmap getFrameAt(int msec) throws IllegalStateException; - - /** * Sets the audio session ID. * * @param sessionId the audio session ID. @@ -1458,100 +1493,6 @@ public class MediaPlayer */ public native void attachAuxEffect(int effectId); - /* Do not change these values (starting with KEY_PARAMETER) without updating - * their counterparts in include/media/mediaplayer.h! - */ - - // There are currently no defined keys usable from Java with get*Parameter. - // But if any keys are defined, the order must be kept in sync with include/media/mediaplayer.h. - // private static final int KEY_PARAMETER_... = ...; - - /** - * Sets the parameter indicated by key. - * @param key key indicates the parameter to be set. - * @param value value of the parameter to be set. - * @return true if the parameter is set successfully, false otherwise - * {@hide} - */ - public native boolean setParameter(int key, Parcel value); - - /** - * Sets the parameter indicated by key. - * @param key key indicates the parameter to be set. - * @param value value of the parameter to be set. - * @return true if the parameter is set successfully, false otherwise - * {@hide} - */ - public boolean setParameter(int key, String value) { - Parcel p = Parcel.obtain(); - p.writeString(value); - boolean ret = setParameter(key, p); - p.recycle(); - return ret; - } - - /** - * Sets the parameter indicated by key. - * @param key key indicates the parameter to be set. - * @param value value of the parameter to be set. - * @return true if the parameter is set successfully, false otherwise - * {@hide} - */ - public boolean setParameter(int key, int value) { - Parcel p = Parcel.obtain(); - p.writeInt(value); - boolean ret = setParameter(key, p); - p.recycle(); - return ret; - } - - /* - * Gets the value of the parameter indicated by key. - * @param key key indicates the parameter to get. - * @param reply value of the parameter to get. - */ - private native void getParameter(int key, Parcel reply); - - /** - * Gets the value of the parameter indicated by key. - * The caller is responsible for recycling the returned parcel. - * @param key key indicates the parameter to get. - * @return value of the parameter. - * {@hide} - */ - public Parcel getParcelParameter(int key) { - Parcel p = Parcel.obtain(); - getParameter(key, p); - return p; - } - - /** - * Gets the value of the parameter indicated by key. - * @param key key indicates the parameter to get. - * @return value of the parameter. - * {@hide} - */ - public String getStringParameter(int key) { - Parcel p = Parcel.obtain(); - getParameter(key, p); - String ret = p.readString(); - p.recycle(); - return ret; - } - - /** - * Gets the value of the parameter indicated by key. - * @param key key indicates the parameter to get. - * @return value of the parameter. - * {@hide} - */ - public int getIntParameter(int key) { - Parcel p = Parcel.obtain(); - getParameter(key, p); - int ret = p.readInt(); - p.recycle(); - return ret; - } /** * Sets the send level of the player to the attached auxiliary effect @@ -1628,20 +1569,56 @@ public class MediaPlayer * ISO-639-2 language code, "und", is returned. */ public String getLanguage() { - return mLanguage; + String language = mFormat.getString(MediaFormat.KEY_LANGUAGE); + return language == null ? "und" : language; + } + + /** + * Gets the {@link MediaFormat} of the track. If the format is + * unknown or could not be determined, null is returned. + */ + public MediaFormat getFormat() { + if (mTrackType == MEDIA_TRACK_TYPE_TIMEDTEXT + || mTrackType == MEDIA_TRACK_TYPE_SUBTITLE) { + return mFormat; + } + return null; } public static final int MEDIA_TRACK_TYPE_UNKNOWN = 0; public static final int MEDIA_TRACK_TYPE_VIDEO = 1; public static final int MEDIA_TRACK_TYPE_AUDIO = 2; public static final int MEDIA_TRACK_TYPE_TIMEDTEXT = 3; + /** @hide */ + public static final int MEDIA_TRACK_TYPE_SUBTITLE = 4; final int mTrackType; - final String mLanguage; + final MediaFormat mFormat; TrackInfo(Parcel in) { mTrackType = in.readInt(); - mLanguage = in.readString(); + // TODO: parcel in the full MediaFormat + String language = in.readString(); + + if (mTrackType == MEDIA_TRACK_TYPE_TIMEDTEXT) { + mFormat = MediaFormat.createSubtitleFormat( + MEDIA_MIMETYPE_TEXT_SUBRIP, language); + } else if (mTrackType == MEDIA_TRACK_TYPE_SUBTITLE) { + mFormat = MediaFormat.createSubtitleFormat( + MEDIA_MIMETYPE_TEXT_VTT, language); + mFormat.setInteger(MediaFormat.KEY_IS_AUTOSELECT, in.readInt()); + mFormat.setInteger(MediaFormat.KEY_IS_DEFAULT, in.readInt()); + mFormat.setInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE, in.readInt()); + } else { + mFormat = new MediaFormat(); + mFormat.setString(MediaFormat.KEY_LANGUAGE, language); + } + } + + /** @hide */ + TrackInfo(int type, MediaFormat format) { + mTrackType = type; + mFormat = format; } /** @@ -1658,7 +1635,13 @@ public class MediaPlayer @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mTrackType); - dest.writeString(mLanguage); + dest.writeString(getLanguage()); + + if (mTrackType == MEDIA_TRACK_TYPE_SUBTITLE) { + dest.writeInt(mFormat.getInteger(MediaFormat.KEY_IS_AUTOSELECT)); + dest.writeInt(mFormat.getInteger(MediaFormat.KEY_IS_DEFAULT)); + dest.writeInt(mFormat.getInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE)); + } } /** @@ -1688,6 +1671,19 @@ public class MediaPlayer * @throws IllegalStateException if it is called in an invalid state. */ public TrackInfo[] getTrackInfo() throws IllegalStateException { + TrackInfo trackInfo[] = getInbandTrackInfo(); + // add out-of-band tracks + TrackInfo allTrackInfo[] = new TrackInfo[trackInfo.length + mOutOfBandSubtitleTracks.size()]; + System.arraycopy(trackInfo, 0, allTrackInfo, 0, trackInfo.length); + int i = trackInfo.length; + for (SubtitleTrack track: mOutOfBandSubtitleTracks) { + allTrackInfo[i] = new TrackInfo(TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE, track.getFormat()); + ++i; + } + return allTrackInfo; + } + + private TrackInfo[] getInbandTrackInfo() throws IllegalStateException { Parcel request = Parcel.obtain(); Parcel reply = Parcel.obtain(); try { @@ -1710,6 +1706,12 @@ public class MediaPlayer */ public static final String MEDIA_MIMETYPE_TEXT_SUBRIP = "application/x-subrip"; + /** + * MIME type for WebVTT subtitle data. + * @hide + */ + public static final String MEDIA_MIMETYPE_TEXT_VTT = "text/vtt"; + /* * A helper function to check if the mime type is supported by media framework. */ @@ -1720,6 +1722,149 @@ public class MediaPlayer return false; } + private SubtitleController mSubtitleController; + + /** @hide */ + public void setSubtitleAnchor( + SubtitleController controller, + SubtitleController.Anchor anchor) { + // TODO: create SubtitleController in MediaPlayer + mSubtitleController = controller; + mSubtitleController.setAnchor(anchor); + } + + private SubtitleTrack[] mInbandSubtitleTracks; + private int mSelectedSubtitleTrackIndex = -1; + private Vector<SubtitleTrack> mOutOfBandSubtitleTracks; + private Vector<InputStream> mOpenSubtitleSources; + + private OnSubtitleDataListener mSubtitleDataListener = new OnSubtitleDataListener() { + @Override + public void onSubtitleData(MediaPlayer mp, SubtitleData data) { + int index = data.getTrackIndex(); + if (index >= mInbandSubtitleTracks.length) { + return; + } + SubtitleTrack track = mInbandSubtitleTracks[index]; + if (track != null) { + try { + long runID = data.getStartTimeUs() + 1; + // TODO: move conversion into track + track.onData(new String(data.getData(), "UTF-8"), true /* eos */, runID); + track.setRunDiscardTimeMs( + runID, + (data.getStartTimeUs() + data.getDurationUs()) / 1000); + } catch (java.io.UnsupportedEncodingException e) { + Log.w(TAG, "subtitle data for track " + index + " is not UTF-8 encoded: " + e); + } + } + } + }; + + /** @hide */ + @Override + public void onSubtitleTrackSelected(SubtitleTrack track) { + if (mSelectedSubtitleTrackIndex >= 0) { + try { + selectOrDeselectInbandTrack(mSelectedSubtitleTrackIndex, false); + } catch (IllegalStateException e) { + } + mSelectedSubtitleTrackIndex = -1; + } + setOnSubtitleDataListener(null); + if (track == null) { + return; + } + for (int i = 0; i < mInbandSubtitleTracks.length; i++) { + if (mInbandSubtitleTracks[i] == track) { + Log.v(TAG, "Selecting subtitle track " + i); + mSelectedSubtitleTrackIndex = i; + try { + selectOrDeselectInbandTrack(mSelectedSubtitleTrackIndex, true); + } catch (IllegalStateException e) { + } + setOnSubtitleDataListener(mSubtitleDataListener); + break; + } + } + // no need to select out-of-band tracks + } + + /** @hide */ + public void addSubtitleSource(InputStream is, MediaFormat format) + throws IllegalStateException + { + final InputStream fIs = is; + final MediaFormat fFormat = format; + + // Ensure all input streams are closed. It is also a handy + // way to implement timeouts in the future. + synchronized(mOpenSubtitleSources) { + mOpenSubtitleSources.add(is); + } + + // process each subtitle in its own thread + final HandlerThread thread = new HandlerThread("SubtitleReadThread", + Process.THREAD_PRIORITY_BACKGROUND + Process.THREAD_PRIORITY_MORE_FAVORABLE); + thread.start(); + Handler handler = new Handler(thread.getLooper()); + handler.post(new Runnable() { + private int addTrack() { + if (fIs == null || mSubtitleController == null) { + return MEDIA_INFO_UNSUPPORTED_SUBTITLE; + } + + SubtitleTrack track = mSubtitleController.addTrack(fFormat); + if (track == null) { + return MEDIA_INFO_UNSUPPORTED_SUBTITLE; + } + + // TODO: do the conversion in the subtitle track + Scanner scanner = new Scanner(fIs, "UTF-8"); + String contents = scanner.useDelimiter("\\A").next(); + synchronized(mOpenSubtitleSources) { + mOpenSubtitleSources.remove(fIs); + } + scanner.close(); + mOutOfBandSubtitleTracks.add(track); + track.onData(contents, true /* eos */, ~0 /* runID: keep forever */); + return MEDIA_INFO_EXTERNAL_METADATA_UPDATE; + } + + public void run() { + int res = addTrack(); + if (mEventHandler != null) { + Message m = mEventHandler.obtainMessage(MEDIA_INFO, res, 0, null); + mEventHandler.sendMessage(m); + } + thread.getLooper().quitSafely(); + } + }); + } + + private void scanInternalSubtitleTracks() { + if (mSubtitleController == null) { + Log.e(TAG, "Should have subtitle controller already set"); + return; + } + + TrackInfo[] tracks = getInbandTrackInfo(); + SubtitleTrack[] inbandTracks = new SubtitleTrack[tracks.length]; + for (int i=0; i < tracks.length; i++) { + if (tracks[i].getTrackType() == TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE) { + if (i < mInbandSubtitleTracks.length) { + inbandTracks[i] = mInbandSubtitleTracks[i]; + } else { + SubtitleTrack track = mSubtitleController.addTrack( + tracks[i].getFormat()); + inbandTracks[i] = track; + } + } + } + mInbandSubtitleTracks = inbandTracks; + mSubtitleController.selectDefaultTrack(); + } + /* TODO: Limit the total number of external timed text source to a reasonable number. */ /** @@ -1910,6 +2055,30 @@ public class MediaPlayer private void selectOrDeselectTrack(int index, boolean select) throws IllegalStateException { + // handle subtitle track through subtitle controller + SubtitleTrack track = null; + if (index < mInbandSubtitleTracks.length) { + track = mInbandSubtitleTracks[index]; + } else if (index < mInbandSubtitleTracks.length + mOutOfBandSubtitleTracks.size()) { + track = mOutOfBandSubtitleTracks.get(index - mInbandSubtitleTracks.length); + } + + if (mSubtitleController != null && track != null) { + if (select) { + mSubtitleController.selectTrack(track); + } else if (mSubtitleController.getSelectedTrack() == track) { + mSubtitleController.selectTrack(null); + } else { + Log.w(TAG, "trying to deselect track that was not selected"); + } + return; + } + + selectOrDeselectInbandTrack(index, select); + } + + private void selectOrDeselectInbandTrack(int index, boolean select) + throws IllegalStateException { Parcel request = Parcel.obtain(); Parcel reply = Parcel.obtain(); try { @@ -1990,9 +2159,24 @@ public class MediaPlayer private static final int MEDIA_BUFFERING_UPDATE = 3; private static final int MEDIA_SEEK_COMPLETE = 4; private static final int MEDIA_SET_VIDEO_SIZE = 5; + private static final int MEDIA_STARTED = 6; + private static final int MEDIA_PAUSED = 7; + private static final int MEDIA_STOPPED = 8; + private static final int MEDIA_SKIPPED = 9; private static final int MEDIA_TIMED_TEXT = 99; private static final int MEDIA_ERROR = 100; private static final int MEDIA_INFO = 200; + private static final int MEDIA_SUBTITLE_DATA = 201; + + private TimeProvider mTimeProvider; + + /** @hide */ + public MediaTimeProvider getMediaTimeProvider() { + if (mTimeProvider == null) { + mTimeProvider = new TimeProvider(this); + } + return mTimeProvider; + } private class EventHandler extends Handler { @@ -2011,6 +2195,7 @@ public class MediaPlayer } switch(msg.what) { case MEDIA_PREPARED: + scanInternalSubtitleTracks(); if (mOnPreparedListener != null) mOnPreparedListener.onPrepared(mMediaPlayer); return; @@ -2021,14 +2206,34 @@ public class MediaPlayer stayAwake(false); return; + case MEDIA_STOPPED: + if (mTimeProvider != null) { + mTimeProvider.onStopped(); + } + break; + + case MEDIA_STARTED: + case MEDIA_PAUSED: + if (mTimeProvider != null) { + mTimeProvider.onPaused(msg.what == MEDIA_PAUSED); + } + break; + case MEDIA_BUFFERING_UPDATE: if (mOnBufferingUpdateListener != null) mOnBufferingUpdateListener.onBufferingUpdate(mMediaPlayer, msg.arg1); return; case MEDIA_SEEK_COMPLETE: - if (mOnSeekCompleteListener != null) + if (mOnSeekCompleteListener != null) { mOnSeekCompleteListener.onSeekComplete(mMediaPlayer); + } + // fall through + + case MEDIA_SKIPPED: + if (mTimeProvider != null) { + mTimeProvider.onSeekComplete(mMediaPlayer); + } return; case MEDIA_SET_VIDEO_SIZE: @@ -2049,9 +2254,21 @@ public class MediaPlayer return; case MEDIA_INFO: - if (msg.arg1 != MEDIA_INFO_VIDEO_TRACK_LAGGING) { + switch (msg.arg1) { + case MEDIA_INFO_VIDEO_TRACK_LAGGING: Log.i(TAG, "Info (" + msg.arg1 + "," + msg.arg2 + ")"); + break; + case MEDIA_INFO_METADATA_UPDATE: + scanInternalSubtitleTracks(); + // fall through + + case MEDIA_INFO_EXTERNAL_METADATA_UPDATE: + msg.arg1 = MEDIA_INFO_METADATA_UPDATE; + // update default track selection + mSubtitleController.selectDefaultTrack(); + break; } + if (mOnInfoListener != null) { mOnInfoListener.onInfo(mMediaPlayer, msg.arg1, msg.arg2); } @@ -2072,6 +2289,18 @@ public class MediaPlayer } return; + case MEDIA_SUBTITLE_DATA: + if (mOnSubtitleDataListener == null) { + return; + } + if (msg.obj instanceof Parcel) { + Parcel parcel = (Parcel) msg.obj; + SubtitleData data = new SubtitleData(parcel); + parcel.recycle(); + mOnSubtitleDataListener.onSubtitleData(mMediaPlayer, data); + } + return; + case MEDIA_NOP: // interface test message - ignore break; @@ -2283,6 +2512,30 @@ public class MediaPlayer private OnTimedTextListener mOnTimedTextListener; + /** + * Interface definition of a callback to be invoked when a + * track has data available. + * + * @hide + */ + public interface OnSubtitleDataListener + { + public void onSubtitleData(MediaPlayer mp, SubtitleData data); + } + + /** + * Register a callback to be invoked when a track has data available. + * + * @param listener the callback that will be run + * + * @hide + */ + public void setOnSubtitleDataListener(OnSubtitleDataListener listener) + { + mOnSubtitleDataListener = listener; + } + + private OnSubtitleDataListener mOnSubtitleDataListener; /* Do not change these values without updating their counterparts * in include/media/mediaplayer.h! @@ -2414,6 +2667,12 @@ public class MediaPlayer */ public static final int MEDIA_INFO_METADATA_UPDATE = 802; + /** A new set of external-only metadata is available. Used by + * JAVA framework to avoid triggering track scanning. + * @hide + */ + public static final int MEDIA_INFO_EXTERNAL_METADATA_UPDATE = 803; + /** Failed to handle timed text track properly. * @see android.media.MediaPlayer.OnInfoListener * @@ -2421,6 +2680,16 @@ public class MediaPlayer */ public static final int MEDIA_INFO_TIMED_TEXT_ERROR = 900; + /** Subtitle track was not supported by the media framework. + * @see android.media.MediaPlayer.OnInfoListener + */ + public static final int MEDIA_INFO_UNSUPPORTED_SUBTITLE = 901; + + /** Reading the subtitle track takes too long. + * @see android.media.MediaPlayer.OnInfoListener + */ + public static final int MEDIA_INFO_SUBTITLE_TIMED_OUT = 902; + /** * Interface definition of a callback to be invoked to communicate some * info and/or warning about the media or its playback. @@ -2441,6 +2710,8 @@ public class MediaPlayer * <li>{@link #MEDIA_INFO_BAD_INTERLEAVING} * <li>{@link #MEDIA_INFO_NOT_SEEKABLE} * <li>{@link #MEDIA_INFO_METADATA_UPDATE} + * <li>{@link #MEDIA_INFO_UNSUPPORTED_SUBTITLE} + * <li>{@link #MEDIA_INFO_SUBTITLE_TIMED_OUT} * </ul> * @param extra an extra code, specific to the info. Typically * implementation dependent. @@ -2523,4 +2794,390 @@ public class MediaPlayer } private native void updateProxyConfig(ProxyProperties props); + + /** @hide */ + static class TimeProvider implements MediaPlayer.OnSeekCompleteListener, + MediaTimeProvider { + private static final String TAG = "MTP"; + private static final long MAX_NS_WITHOUT_POSITION_CHECK = 5000000000L; + private static final long MAX_EARLY_CALLBACK_US = 1000; + private static final long TIME_ADJUSTMENT_RATE = 2; /* meaning 1/2 */ + private long mLastTimeUs = 0; + private MediaPlayer mPlayer; + private boolean mPaused = true; + private boolean mStopped = true; + private long mLastReportedTime; + private long mTimeAdjustment; + // since we are expecting only a handful listeners per stream, there is + // no need for log(N) search performance + private MediaTimeProvider.OnMediaTimeListener mListeners[]; + private long mTimes[]; + private long mLastNanoTime; + private Handler mEventHandler; + private boolean mRefresh = false; + private boolean mPausing = false; + private boolean mSeeking = false; + private static final int NOTIFY = 1; + private static final int NOTIFY_TIME = 0; + private static final int REFRESH_AND_NOTIFY_TIME = 1; + private static final int NOTIFY_STOP = 2; + private static final int NOTIFY_SEEK = 3; + private HandlerThread mHandlerThread; + + /** @hide */ + public boolean DEBUG = false; + + public TimeProvider(MediaPlayer mp) { + mPlayer = mp; + try { + getCurrentTimeUs(true, false); + } catch (IllegalStateException e) { + // we assume starting position + mRefresh = true; + } + + Looper looper; + if ((looper = Looper.myLooper()) == null && + (looper = Looper.getMainLooper()) == null) { + // Create our own looper here in case MP was created without one + mHandlerThread = new HandlerThread("MediaPlayerMTPEventThread", + Process.THREAD_PRIORITY_FOREGROUND); + mHandlerThread.start(); + looper = mHandlerThread.getLooper(); + } + mEventHandler = new EventHandler(looper); + + mListeners = new MediaTimeProvider.OnMediaTimeListener[0]; + mTimes = new long[0]; + mLastTimeUs = 0; + mTimeAdjustment = 0; + } + + private void scheduleNotification(int type, long delayUs) { + // ignore time notifications until seek is handled + if (mSeeking && + (type == NOTIFY_TIME || type == REFRESH_AND_NOTIFY_TIME)) { + return; + } + + if (DEBUG) Log.v(TAG, "scheduleNotification " + type + " in " + delayUs); + mStopped = type == NOTIFY_STOP; + mSeeking = type == NOTIFY_SEEK; + mEventHandler.removeMessages(NOTIFY); + Message msg = mEventHandler.obtainMessage(NOTIFY, type, 0); + mEventHandler.sendMessageDelayed(msg, (int) (delayUs / 1000)); + } + + /** @hide */ + public void close() { + mEventHandler.removeMessages(NOTIFY); + if (mHandlerThread != null) { + mHandlerThread.quitSafely(); + mHandlerThread = null; + } + } + + /** @hide */ + protected void finalize() { + if (mHandlerThread != null) { + mHandlerThread.quitSafely(); + } + } + + /** @hide */ + public void onPaused(boolean paused) { + synchronized(this) { + if (DEBUG) Log.d(TAG, "onPaused: " + paused); + if (mStopped) { // handle as seek if we were stopped + scheduleNotification(NOTIFY_SEEK, 0 /* delay */); + } else { + mPausing = paused; // special handling if player disappeared + scheduleNotification(REFRESH_AND_NOTIFY_TIME, 0 /* delay */); + } + } + } + + /** @hide */ + public void onStopped() { + synchronized(this) { + if (DEBUG) Log.d(TAG, "onStopped"); + mPaused = true; + scheduleNotification(NOTIFY_STOP, 0 /* delay */); + } + } + + /** @hide */ + @Override + public void onSeekComplete(MediaPlayer mp) { + synchronized(this) { + scheduleNotification(NOTIFY_SEEK, 0 /* delay */); + } + } + + /** @hide */ + public void onNewPlayer() { + if (mRefresh) { + synchronized(this) { + scheduleNotification(NOTIFY_SEEK, 0 /* delay */); + } + } + } + + private synchronized void notifySeek() { + mSeeking = false; + try { + long timeUs = getCurrentTimeUs(true, false); + if (DEBUG) Log.d(TAG, "onSeekComplete at " + timeUs); + + for (MediaTimeProvider.OnMediaTimeListener listener: mListeners) { + if (listener == null) { + break; + } + listener.onSeek(timeUs); + } + } catch (IllegalStateException e) { + // we should not be there, but at least signal pause + if (DEBUG) Log.d(TAG, "onSeekComplete but no player"); + mPausing = true; // special handling if player disappeared + notifyTimedEvent(false /* refreshTime */); + } + } + + private synchronized void notifyStop() { + for (MediaTimeProvider.OnMediaTimeListener listener: mListeners) { + if (listener == null) { + break; + } + listener.onStop(); + } + } + + private int registerListener(MediaTimeProvider.OnMediaTimeListener listener) { + int i = 0; + for (; i < mListeners.length; i++) { + if (mListeners[i] == listener || mListeners[i] == null) { + break; + } + } + + // new listener + if (i >= mListeners.length) { + MediaTimeProvider.OnMediaTimeListener[] newListeners = + new MediaTimeProvider.OnMediaTimeListener[i + 1]; + long[] newTimes = new long[i + 1]; + System.arraycopy(mListeners, 0, newListeners, 0, mListeners.length); + System.arraycopy(mTimes, 0, newTimes, 0, mTimes.length); + mListeners = newListeners; + mTimes = newTimes; + } + + if (mListeners[i] == null) { + mListeners[i] = listener; + mTimes[i] = MediaTimeProvider.NO_TIME; + } + return i; + } + + public void notifyAt( + long timeUs, MediaTimeProvider.OnMediaTimeListener listener) { + synchronized(this) { + if (DEBUG) Log.d(TAG, "notifyAt " + timeUs); + mTimes[registerListener(listener)] = timeUs; + scheduleNotification(NOTIFY_TIME, 0 /* delay */); + } + } + + public void scheduleUpdate(MediaTimeProvider.OnMediaTimeListener listener) { + synchronized(this) { + if (DEBUG) Log.d(TAG, "scheduleUpdate"); + int i = registerListener(listener); + + if (mStopped) { + scheduleNotification(NOTIFY_STOP, 0 /* delay */); + } else { + mTimes[i] = 0; + scheduleNotification(NOTIFY_TIME, 0 /* delay */); + } + } + } + + public void cancelNotifications( + MediaTimeProvider.OnMediaTimeListener listener) { + synchronized(this) { + int i = 0; + for (; i < mListeners.length; i++) { + if (mListeners[i] == listener) { + System.arraycopy(mListeners, i + 1, + mListeners, i, mListeners.length - i - 1); + System.arraycopy(mTimes, i + 1, + mTimes, i, mTimes.length - i - 1); + mListeners[mListeners.length - 1] = null; + mTimes[mTimes.length - 1] = NO_TIME; + break; + } else if (mListeners[i] == null) { + break; + } + } + + scheduleNotification(NOTIFY_TIME, 0 /* delay */); + } + } + + private synchronized void notifyTimedEvent(boolean refreshTime) { + // figure out next callback + long nowUs; + try { + nowUs = getCurrentTimeUs(refreshTime, true); + } catch (IllegalStateException e) { + // assume we paused until new player arrives + mRefresh = true; + mPausing = true; // this ensures that call succeeds + nowUs = getCurrentTimeUs(refreshTime, true); + } + long nextTimeUs = nowUs; + + if (mSeeking) { + // skip timed-event notifications until seek is complete + return; + } + + if (DEBUG) { + StringBuilder sb = new StringBuilder(); + sb.append("notifyTimedEvent(").append(mLastTimeUs).append(" -> ") + .append(nowUs).append(") from {"); + boolean first = true; + for (long time: mTimes) { + if (time == NO_TIME) { + continue; + } + if (!first) sb.append(", "); + sb.append(time); + first = false; + } + sb.append("}"); + Log.d(TAG, sb.toString()); + } + + Vector<MediaTimeProvider.OnMediaTimeListener> activatedListeners = + new Vector<MediaTimeProvider.OnMediaTimeListener>(); + for (int ix = 0; ix < mTimes.length; ix++) { + if (mListeners[ix] == null) { + break; + } + if (mTimes[ix] <= NO_TIME) { + // ignore, unless we were stopped + } else if (mTimes[ix] <= nowUs + MAX_EARLY_CALLBACK_US) { + activatedListeners.add(mListeners[ix]); + if (DEBUG) Log.d(TAG, "removed"); + mTimes[ix] = NO_TIME; + } else if (nextTimeUs == nowUs || mTimes[ix] < nextTimeUs) { + nextTimeUs = mTimes[ix]; + } + } + + if (nextTimeUs > nowUs && !mPaused) { + // schedule callback at nextTimeUs + if (DEBUG) Log.d(TAG, "scheduling for " + nextTimeUs + " and " + nowUs); + scheduleNotification(NOTIFY_TIME, nextTimeUs - nowUs); + } else { + mEventHandler.removeMessages(NOTIFY); + // no more callbacks + } + + for (MediaTimeProvider.OnMediaTimeListener listener: activatedListeners) { + listener.onTimedEvent(nowUs); + } + } + + private long getEstimatedTime(long nanoTime, boolean monotonic) { + if (mPaused) { + mLastReportedTime = mLastTimeUs + mTimeAdjustment; + } else { + long timeSinceRead = (nanoTime - mLastNanoTime) / 1000; + mLastReportedTime = mLastTimeUs + timeSinceRead; + if (mTimeAdjustment > 0) { + long adjustment = + mTimeAdjustment - timeSinceRead / TIME_ADJUSTMENT_RATE; + if (adjustment <= 0) { + mTimeAdjustment = 0; + } else { + mLastReportedTime += adjustment; + } + } + } + return mLastReportedTime; + } + + public long getCurrentTimeUs(boolean refreshTime, boolean monotonic) + throws IllegalStateException { + synchronized (this) { + // we always refresh the time when the paused-state changes, because + // we expect to have received the pause-change event delayed. + if (mPaused && !refreshTime) { + return mLastReportedTime; + } + + long nanoTime = System.nanoTime(); + if (refreshTime || + nanoTime >= mLastNanoTime + MAX_NS_WITHOUT_POSITION_CHECK) { + try { + mLastTimeUs = mPlayer.getCurrentPosition() * 1000; + mPaused = !mPlayer.isPlaying(); + if (DEBUG) Log.v(TAG, (mPaused ? "paused" : "playing") + " at " + mLastTimeUs); + } catch (IllegalStateException e) { + if (mPausing) { + // if we were pausing, get last estimated timestamp + mPausing = false; + getEstimatedTime(nanoTime, monotonic); + mPaused = true; + if (DEBUG) Log.d(TAG, "illegal state, but pausing: estimating at " + mLastReportedTime); + return mLastReportedTime; + } + // TODO get time when prepared + throw e; + } + mLastNanoTime = nanoTime; + if (monotonic && mLastTimeUs < mLastReportedTime) { + /* have to adjust time */ + mTimeAdjustment = mLastReportedTime - mLastTimeUs; + if (mTimeAdjustment > 1000000) { + // schedule seeked event if time jumped significantly + // TODO: do this properly by introducing an exception + scheduleNotification(NOTIFY_SEEK, 0 /* delay */); + } + } else { + mTimeAdjustment = 0; + } + } + + return getEstimatedTime(nanoTime, monotonic); + } + } + + private class EventHandler extends Handler { + public EventHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + if (msg.what == NOTIFY) { + switch (msg.arg1) { + case NOTIFY_TIME: + notifyTimedEvent(false /* refreshTime */); + break; + case REFRESH_AND_NOTIFY_TIME: + notifyTimedEvent(true /* refreshTime */); + break; + case NOTIFY_STOP: + notifyStop(); + break; + case NOTIFY_SEEK: + notifySeek(); + break; + } + } + } + } + } } diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java index 3e688db..8dcbd6b 100644 --- a/media/java/android/media/MediaRecorder.java +++ b/media/java/android/media/MediaRecorder.java @@ -179,6 +179,40 @@ public class MediaRecorder * is applied. */ public static final int VOICE_COMMUNICATION = 7; + + /** + * Audio source for a submix of audio streams to be presented remotely. + * <p> + * An application can use this audio source to capture a mix of audio streams + * that should be transmitted to a remote receiver such as a Wifi display. + * While recording is active, these audio streams are redirected to the remote + * submix instead of being played on the device speaker or headset. + * </p><p> + * Certain streams are excluded from the remote submix, including + * {@link AudioManager#STREAM_RING}, {@link AudioManager#STREAM_ALARM}, + * and {@link AudioManager#STREAM_NOTIFICATION}. These streams will continue + * to be presented locally as usual. + * </p><p> + * Capturing the remote submix audio requires the + * {@link android.Manifest.permission#CAPTURE_AUDIO_OUTPUT} permission. + * This permission is reserved for use by system components and is not available to + * third-party applications. + * </p> + */ + public static final int REMOTE_SUBMIX = 8; + + /** + * Audio source for preemptible, low-priority software hotword detection + * It presents the same gain and pre processing tuning as {@link #VOICE_RECOGNITION}. + * <p> + * An application should use this audio source when it wishes to do + * always-on software hotword detection, while gracefully giving in to any other application + * that might want to read from the microphone. + * </p> + * This is a hidden audio source. + * @hide + */ + protected static final int HOTWORD = 1999; } /** @@ -294,7 +328,7 @@ public class MediaRecorder * @see android.media.MediaRecorder.AudioSource */ public static final int getAudioSourceMax() { - return AudioSource.VOICE_COMMUNICATION; + return AudioSource.REMOTE_SUBMIX; } /** diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java index 5c58503..9a79c94 100644 --- a/media/java/android/media/MediaRouter.java +++ b/media/java/android/media/MediaRouter.java @@ -16,6 +16,7 @@ package android.media; +import android.app.ActivityThread; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -653,7 +654,13 @@ public class MediaRouter { if (info == sStatic.mSelectedRoute) { // Removing the currently selected route? Select the default before we remove it. // TODO: Be smarter about the route types here; this selects for all valid. - selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_USER, sStatic.mDefaultAudioVideo); + if (info != sStatic.mBluetoothA2dpRoute && sStatic.mBluetoothA2dpRoute != null) { + selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_USER, + sStatic.mBluetoothA2dpRoute); + } else { + selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_USER, + sStatic.mDefaultAudioVideo); + } } if (!found) { sStatic.mCategories.remove(removingCat); @@ -875,44 +882,45 @@ public class MediaRouter { boolean wantScan = false; boolean blockScan = false; WifiDisplay[] oldDisplays = oldStatus != null ? - oldStatus.getRememberedDisplays() : WifiDisplay.EMPTY_ARRAY; + oldStatus.getDisplays() : WifiDisplay.EMPTY_ARRAY; WifiDisplay[] newDisplays; - WifiDisplay[] availableDisplays; WifiDisplay activeDisplay; if (newStatus.getFeatureState() == WifiDisplayStatus.FEATURE_STATE_ON) { - newDisplays = newStatus.getRememberedDisplays(); - availableDisplays = newStatus.getAvailableDisplays(); + newDisplays = newStatus.getDisplays(); activeDisplay = newStatus.getActiveDisplay(); } else { - newDisplays = availableDisplays = WifiDisplay.EMPTY_ARRAY; + newDisplays = WifiDisplay.EMPTY_ARRAY; activeDisplay = null; } for (int i = 0; i < newDisplays.length; i++) { final WifiDisplay d = newDisplays[i]; - final boolean available = findMatchingDisplay(d, availableDisplays) != null; - RouteInfo route = findWifiDisplayRoute(d); - if (route == null) { - route = makeWifiDisplayRoute(d, available); - addRouteStatic(route); - wantScan = true; - } else { - updateWifiDisplayRoute(route, d, available, newStatus); - } - if (d.equals(activeDisplay)) { - selectRouteStatic(route.getSupportedTypes(), route); + if (d.isRemembered()) { + RouteInfo route = findWifiDisplayRoute(d); + if (route == null) { + route = makeWifiDisplayRoute(d, newStatus); + addRouteStatic(route); + wantScan = true; + } else { + updateWifiDisplayRoute(route, d, newStatus); + } + if (d.equals(activeDisplay)) { + selectRouteStatic(route.getSupportedTypes(), route); - // Don't scan if we're already connected to a wifi display, - // the scanning process can cause a hiccup with some configurations. - blockScan = true; + // Don't scan if we're already connected to a wifi display, + // the scanning process can cause a hiccup with some configurations. + blockScan = true; + } } } for (int i = 0; i < oldDisplays.length; i++) { final WifiDisplay d = oldDisplays[i]; - final WifiDisplay newDisplay = findMatchingDisplay(d, newDisplays); - if (newDisplay == null) { - removeRoute(findWifiDisplayRoute(d)); + if (d.isRemembered()) { + final WifiDisplay newDisplay = findMatchingDisplay(d, newDisplays); + if (newDisplay == null || !newDisplay.isRemembered()) { + removeRoute(findWifiDisplayRoute(d)); + } } } @@ -923,42 +931,20 @@ public class MediaRouter { sStatic.mLastKnownWifiDisplayStatus = newStatus; } - static RouteInfo makeWifiDisplayRoute(WifiDisplay display, boolean available) { - final RouteInfo newRoute = new RouteInfo(sStatic.mSystemCategory); - newRoute.mDeviceAddress = display.getDeviceAddress(); - newRoute.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO; - newRoute.mVolumeHandling = RouteInfo.PLAYBACK_VOLUME_FIXED; - newRoute.mPlaybackType = RouteInfo.PLAYBACK_TYPE_REMOTE; - - newRoute.setStatusCode(available ? - RouteInfo.STATUS_AVAILABLE : RouteInfo.STATUS_CONNECTING); - newRoute.mEnabled = available; - - newRoute.mName = display.getFriendlyDisplayName(); - newRoute.mDescription = sStatic.mResources.getText( - com.android.internal.R.string.wireless_display_route_description); - - newRoute.mPresentationDisplay = choosePresentationDisplayForRoute(newRoute, - sStatic.getAllPresentationDisplays()); - return newRoute; - } - - private static void updateWifiDisplayRoute(RouteInfo route, WifiDisplay display, - boolean available, WifiDisplayStatus wifiDisplayStatus) { - final boolean isScanning = - wifiDisplayStatus.getScanState() == WifiDisplayStatus.SCAN_STATE_SCANNING; - - boolean changed = false; + static int getWifiDisplayStatusCode(WifiDisplay d, WifiDisplayStatus wfdStatus) { int newStatus = RouteInfo.STATUS_NONE; - if (available) { - newStatus = isScanning ? RouteInfo.STATUS_SCANNING : RouteInfo.STATUS_AVAILABLE; + if (wfdStatus.getScanState() == WifiDisplayStatus.SCAN_STATE_SCANNING) { + newStatus = RouteInfo.STATUS_SCANNING; + } else if (d.isAvailable()) { + newStatus = d.canConnect() ? + RouteInfo.STATUS_AVAILABLE: RouteInfo.STATUS_IN_USE; } else { newStatus = RouteInfo.STATUS_NOT_AVAILABLE; } - if (display.equals(wifiDisplayStatus.getActiveDisplay())) { - final int activeState = wifiDisplayStatus.getActiveDisplayState(); + if (d.equals(wfdStatus.getActiveDisplay())) { + final int activeState = wfdStatus.getActiveDisplayState(); switch (activeState) { case WifiDisplayStatus.DISPLAY_STATE_CONNECTED: newStatus = RouteInfo.STATUS_NONE; @@ -972,22 +958,51 @@ public class MediaRouter { } } + return newStatus; + } + + static boolean isWifiDisplayEnabled(WifiDisplay d, WifiDisplayStatus wfdStatus) { + return d.isAvailable() && (d.canConnect() || d.equals(wfdStatus.getActiveDisplay())); + } + + static RouteInfo makeWifiDisplayRoute(WifiDisplay display, WifiDisplayStatus wfdStatus) { + final RouteInfo newRoute = new RouteInfo(sStatic.mSystemCategory); + newRoute.mDeviceAddress = display.getDeviceAddress(); + newRoute.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO; + newRoute.mVolumeHandling = RouteInfo.PLAYBACK_VOLUME_FIXED; + newRoute.mPlaybackType = RouteInfo.PLAYBACK_TYPE_REMOTE; + + newRoute.setStatusCode(getWifiDisplayStatusCode(display, wfdStatus)); + newRoute.mEnabled = isWifiDisplayEnabled(display, wfdStatus); + newRoute.mName = display.getFriendlyDisplayName(); + newRoute.mDescription = sStatic.mResources.getText( + com.android.internal.R.string.wireless_display_route_description); + + newRoute.mPresentationDisplay = choosePresentationDisplayForRoute(newRoute, + sStatic.getAllPresentationDisplays()); + return newRoute; + } + + private static void updateWifiDisplayRoute( + RouteInfo route, WifiDisplay display, WifiDisplayStatus wfdStatus) { + boolean changed = false; final String newName = display.getFriendlyDisplayName(); if (!route.getName().equals(newName)) { route.mName = newName; changed = true; } - changed |= route.mEnabled != available; - route.mEnabled = available; + boolean enabled = isWifiDisplayEnabled(display, wfdStatus); + changed |= route.mEnabled != enabled; + route.mEnabled = enabled; - changed |= route.setStatusCode(newStatus); + changed |= route.setStatusCode(getWifiDisplayStatusCode(display, wfdStatus)); if (changed) { dispatchRouteChanged(route); } - if (!available && route == sStatic.mSelectedRoute) { + if (!enabled && route == sStatic.mSelectedRoute) { // Oops, no longer available. Reselect the default. final RouteInfo defaultRoute = sStatic.mDefaultAudioVideo; selectRouteStatic(defaultRoute.getSupportedTypes(), defaultRoute); @@ -1068,6 +1083,7 @@ public class MediaRouter { /** @hide */ public static final int STATUS_CONNECTING = 2; /** @hide */ public static final int STATUS_AVAILABLE = 3; /** @hide */ public static final int STATUS_NOT_AVAILABLE = 4; + /** @hide */ public static final int STATUS_IN_USE = 5; private Object mTag; @@ -1179,6 +1195,9 @@ public class MediaRouter { case STATUS_NOT_AVAILABLE: resId = com.android.internal.R.string.media_route_status_not_available; break; + case STATUS_IN_USE: + resId = com.android.internal.R.string.media_route_status_in_use; + break; } mStatus = resId != 0 ? sStatic.mResources.getText(resId) : null; return true; @@ -1292,7 +1311,8 @@ public class MediaRouter { public void requestSetVolume(int volume) { if (mPlaybackType == PLAYBACK_TYPE_LOCAL) { try { - sStatic.mAudioService.setStreamVolume(mPlaybackStream, volume, 0); + sStatic.mAudioService.setStreamVolume(mPlaybackStream, volume, 0, + ActivityThread.currentPackageName()); } catch (RemoteException e) { Log.e(TAG, "Error setting local stream volume", e); } @@ -1312,7 +1332,8 @@ public class MediaRouter { try { final int volume = Math.max(0, Math.min(getVolume() + direction, getVolumeMax())); - sStatic.mAudioService.setStreamVolume(mPlaybackStream, volume, 0); + sStatic.mAudioService.setStreamVolume(mPlaybackStream, volume, 0, + ActivityThread.currentPackageName()); } catch (RemoteException e) { Log.e(TAG, "Error setting local stream volume", e); } diff --git a/media/java/android/media/MediaScannerConnection.java b/media/java/android/media/MediaScannerConnection.java index 21b6e14..273eb64 100644 --- a/media/java/android/media/MediaScannerConnection.java +++ b/media/java/android/media/MediaScannerConnection.java @@ -113,6 +113,9 @@ public class MediaScannerConnection implements ServiceConnection { synchronized (this) { if (!mConnected) { Intent intent = new Intent(IMediaScannerService.class.getName()); + intent.setComponent( + new ComponentName("com.android.providers.media", + "com.android.providers.media.MediaScannerService")); mContext.bindService(intent, this, Context.BIND_AUTO_CREATE); mConnected = true; } diff --git a/media/java/android/media/MediaTimeProvider.java b/media/java/android/media/MediaTimeProvider.java new file mode 100644 index 0000000..fe37712 --- /dev/null +++ b/media/java/android/media/MediaTimeProvider.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2013 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 MediaTimeProvider { + // we do not allow negative media time + /** + * Presentation time value if no timed event notification is requested. + */ + public final static long NO_TIME = -1; + + /** + * Cancels all previous notification request from this listener if any. It + * registers the listener to get seek and stop notifications. If timeUs is + * not negative, it also registers the listener for a timed event + * notification when the presentation time reaches (becomes greater) than + * the value specified. This happens immediately if the current media time + * is larger than or equal to timeUs. + * + * @param timeUs presentation time to get timed event callback at (or + * {@link #NO_TIME}) + */ + public void notifyAt(long timeUs, OnMediaTimeListener listener); + + /** + * Cancels all previous notification request from this listener if any. It + * registers the listener to get seek and stop notifications. If the media + * is stopped, the listener will immediately receive a stop notification. + * Otherwise, it will receive a timed event notificaton. + */ + public void scheduleUpdate(OnMediaTimeListener listener); + + /** + * Cancels all previous notification request from this listener if any. + */ + public void cancelNotifications(OnMediaTimeListener listener); + + /** + * Get the current presentation time. + * + * @param precise Whether getting a precise time is important. This is + * more costly. + * @param monotonic Whether returned time should be monotonic: that is, + * greater than or equal to the last returned time. Don't + * always set this to true. E.g. this has undesired + * consequences if the media is seeked between calls. + * @throws IllegalStateException if the media is not initialized + */ + public long getCurrentTimeUs(boolean precise, boolean monotonic) + throws IllegalStateException; + + /** @hide */ + public static interface OnMediaTimeListener { + /** + * Called when the registered time was reached naturally. + * + * @param timeUs current media time + */ + void onTimedEvent(long timeUs); + + /** + * Called when the media time changed due to seeking. + * + * @param timeUs current media time + */ + void onSeek(long timeUs); + + /** + * Called when the playback stopped. This is not called on pause, only + * on full stop, at which point there is no further current media time. + */ + void onStop(); + } +} + diff --git a/media/java/android/media/Rating.aidl b/media/java/android/media/Rating.aidl new file mode 100644 index 0000000..1dc336a --- /dev/null +++ b/media/java/android/media/Rating.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2013 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; + +parcelable Rating; diff --git a/media/java/android/media/Rating.java b/media/java/android/media/Rating.java new file mode 100644 index 0000000..82c0392 --- /dev/null +++ b/media/java/android/media/Rating.java @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2013 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.os.Parcel; +import android.os.Parcelable; +import android.util.Log; + +/** + * A class to encapsulate rating information used as content metadata. + * A rating is defined by its rating style (see {@link #RATING_HEART}, + * {@link #RATING_THUMB_UP_DOWN}, {@link #RATING_3_STARS}, {@link #RATING_4_STARS}, + * {@link #RATING_5_STARS} or {@link #RATING_PERCENTAGE}) and the actual rating value (which may + * be defined as "unrated"), both of which are defined when the rating instance is constructed + * through one of the factory methods. + */ +public final class Rating implements Parcelable { + + private final static String TAG = "Rating"; + + /** + * A rating style with a single degree of rating, "heart" vs "no heart". Can be used to + * indicate the content referred to is a favorite (or not). + */ + public final static int RATING_HEART = 1; + + /** + * A rating style for "thumb up" vs "thumb down". + */ + public final static int RATING_THUMB_UP_DOWN = 2; + + /** + * A rating style with 0 to 3 stars. + */ + public final static int RATING_3_STARS = 3; + + /** + * A rating style with 0 to 4 stars. + */ + public final static int RATING_4_STARS = 4; + + /** + * A rating style with 0 to 5 stars. + */ + public final static int RATING_5_STARS = 5; + + /** + * A rating style expressed as a percentage. + */ + public final static int RATING_PERCENTAGE = 6; + + private final static float RATING_NOT_RATED = -1.0f; + + private final int mRatingStyle; + + private final float mRatingValue; + + private Rating(int ratingStyle, float rating) { + mRatingStyle = ratingStyle; + mRatingValue = rating; + } + + + /** + * @hide + */ + @Override + public String toString () { + return "Rating:style=" + mRatingStyle + " rating=" + + (mRatingValue < 0.0f ? "unrated" : String.valueOf(mRatingValue)); + } + + @Override + public int describeContents() { + return mRatingStyle; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mRatingStyle); + dest.writeFloat(mRatingValue); + } + + public static final Parcelable.Creator<Rating> CREATOR + = new Parcelable.Creator<Rating>() { + /** + * Rebuilds a Rating previously stored with writeToParcel(). + * @param p Parcel object to read the Rating from + * @return a new Rating created from the data in the parcel + */ + public Rating createFromParcel(Parcel p) { + return new Rating(p.readInt(), p.readFloat()); + } + public Rating[] newArray(int size) { + return new Rating[size]; + } + }; + + /** + * Return a Rating instance with no rating. + * Create and return a new Rating instance with no rating known for the given + * rating style. + * @param ratingStyle one of {@link #RATING_HEART}, {@link #RATING_THUMB_UP_DOWN}, + * {@link #RATING_3_STARS}, {@link #RATING_4_STARS}, {@link #RATING_5_STARS}, + * or {@link #RATING_PERCENTAGE}. + * @return null if an invalid rating style is passed, a new Rating instance otherwise. + */ + public static Rating newUnratedRating(int ratingStyle) { + switch(ratingStyle) { + case RATING_HEART: + case RATING_THUMB_UP_DOWN: + case RATING_3_STARS: + case RATING_4_STARS: + case RATING_5_STARS: + case RATING_PERCENTAGE: + return new Rating(ratingStyle, RATING_NOT_RATED); + default: + return null; + } + } + + /** + * Return a Rating instance with a heart-based rating. + * Create and return a new Rating instance with a rating style of {@link #RATING_HEART}, + * and a heart-based rating. + * @param hasHeart true for a "heart selected" rating, false for "heart unselected". + * @return a new Rating instance. + */ + public static Rating newHeartRating(boolean hasHeart) { + return new Rating(RATING_HEART, hasHeart ? 1.0f : 0.0f); + } + + /** + * Return a Rating instance with a thumb-based rating. + * Create and return a new Rating instance with a {@link #RATING_THUMB_UP_DOWN} + * rating style, and a "thumb up" or "thumb down" rating. + * @param thumbIsUp true for a "thumb up" rating, false for "thumb down". + * @return a new Rating instance. + */ + public static Rating newThumbRating(boolean thumbIsUp) { + return new Rating(RATING_THUMB_UP_DOWN, thumbIsUp ? 1.0f : 0.0f); + } + + /** + * Return a Rating instance with a star-based rating. + * Create and return a new Rating instance with one of the star-base rating styles + * and the given integer or fractional number of stars. Non integer values can for instance + * be used to represent an average rating value, which might not be an integer number of stars. + * @param starRatingStyle one of {@link #RATING_3_STARS}, {@link #RATING_4_STARS}, + * {@link #RATING_5_STARS}. + * @param starRating a number ranging from 0.0f to 3.0f, 4.0f or 5.0f according to + * the rating style. + * @return null if the rating style is invalid, or the rating is out of range, + * a new Rating instance otherwise. + */ + public static Rating newStarRating(int starRatingStyle, float starRating) { + float maxRating = -1.0f; + switch(starRatingStyle) { + case RATING_3_STARS: + maxRating = 3.0f; + break; + case RATING_4_STARS: + maxRating = 4.0f; + break; + case RATING_5_STARS: + maxRating = 5.0f; + break; + default: + Log.e(TAG, "Invalid rating style (" + starRatingStyle + ") for a star rating"); + return null; + } + if ((starRating < 0.0f) || (starRating > maxRating)) { + Log.e(TAG, "Trying to set out of range star-based rating"); + return null; + } + return new Rating(starRatingStyle, starRating); + } + + /** + * Return a Rating instance with a percentage-based rating. + * Create and return a new Rating instance with a {@link #RATING_PERCENTAGE} + * rating style, and a rating of the given percentage. + * @param percent the value of the rating + * @return null if the rating is out of range, a new Rating instance otherwise. + */ + public static Rating newPercentageRating(float percent) { + if ((percent < 0.0f) || (percent > 100.0f)) { + Log.e(TAG, "Invalid percentage-based rating value"); + return null; + } else { + return new Rating(RATING_PERCENTAGE, percent); + } + } + + /** + * Return whether there is a rating value available. + * @return true if the instance was not created with {@link #newUnratedRating(int)}. + */ + public boolean isRated() { + return mRatingValue >= 0.0f; + } + + /** + * Return the rating style. + * @return one of {@link #RATING_HEART}, {@link #RATING_THUMB_UP_DOWN}, + * {@link #RATING_3_STARS}, {@link #RATING_4_STARS}, {@link #RATING_5_STARS}, + * or {@link #RATING_PERCENTAGE}. + */ + public int getRatingStyle() { + return mRatingStyle; + } + + /** + * Return whether the rating is "heart selected". + * @return true if the rating is "heart selected", false if the rating is "heart unselected", + * if the rating style is not {@link #RATING_HEART} or if it is unrated. + */ + public boolean hasHeart() { + if (mRatingStyle != RATING_HEART) { + return false; + } else { + return (mRatingValue == 1.0f); + } + } + + /** + * Return whether the rating is "thumb up". + * @return true if the rating is "thumb up", false if the rating is "thumb down", + * if the rating style is not {@link #RATING_THUMB_UP_DOWN} or if it is unrated. + */ + public boolean isThumbUp() { + if (mRatingStyle != RATING_THUMB_UP_DOWN) { + return false; + } else { + return (mRatingValue == 1.0f); + } + } + + /** + * Return the star-based rating value. + * @return a rating value greater or equal to 0.0f, or a negative value if the rating style is + * not star-based, or if it is unrated. + */ + public float getStarRating() { + switch (mRatingStyle) { + case RATING_3_STARS: + case RATING_4_STARS: + case RATING_5_STARS: + if (isRated()) { + return mRatingValue; + } + default: + return -1.0f; + } + } + + /** + * Return the percentage-based rating value. + * @return a rating value greater or equal to 0.0f, or a negative value if the rating style is + * not percentage-based, or if it is unrated. + */ + public float getPercentRating() { + if ((mRatingStyle != RATING_PERCENTAGE) || !isRated()) { + return -1.0f; + } else { + return mRatingValue; + } + } +}
\ No newline at end of file diff --git a/media/java/android/media/RemoteControlClient.java b/media/java/android/media/RemoteControlClient.java index 7379438..0c00aba 100644 --- a/media/java/android/media/RemoteControlClient.java +++ b/media/java/android/media/RemoteControlClient.java @@ -30,6 +30,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.Parcelable; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; @@ -293,6 +294,17 @@ public class RemoteControlClient * @see #setPlaybackPositionUpdateListener(OnPlaybackPositionUpdateListener) */ public final static int FLAG_KEY_MEDIA_POSITION_UPDATE = 1 << 8; + /** + * Flag indicating a RemoteControlClient supports ratings. + * This flag must be set in order for components that display the RemoteControlClient + * information, to display ratings information, and, if ratings are declared editable + * (by calling {@link MediaMetadataEditor#addEditableKey(int)} with the + * {@link MediaMetadataEditor#RATING_KEY_BY_USER} key), it will enable the user to rate + * the media, with values being received through the interface set with + * {@link #setMetadataUpdateListener(OnMetadataUpdateListener)}. + * @see #setTransportControlFlags(int) + */ + public final static int FLAG_KEY_MEDIA_RATING = 1 << 9; /** * @hide @@ -374,23 +386,6 @@ public class RemoteControlClient mEventHandler = new EventHandler(this, looper); } - private static final int[] METADATA_KEYS_TYPE_STRING = { - MediaMetadataRetriever.METADATA_KEY_ALBUM, - MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST, - MediaMetadataRetriever.METADATA_KEY_TITLE, - MediaMetadataRetriever.METADATA_KEY_ARTIST, - MediaMetadataRetriever.METADATA_KEY_AUTHOR, - MediaMetadataRetriever.METADATA_KEY_COMPILATION, - MediaMetadataRetriever.METADATA_KEY_COMPOSER, - MediaMetadataRetriever.METADATA_KEY_DATE, - MediaMetadataRetriever.METADATA_KEY_GENRE, - MediaMetadataRetriever.METADATA_KEY_TITLE, - MediaMetadataRetriever.METADATA_KEY_WRITER }; - private static final int[] METADATA_KEYS_TYPE_LONG = { - MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER, - MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER, - MediaMetadataRetriever.METADATA_KEY_DURATION }; - /** * Class used to modify metadata in a {@link RemoteControlClient} object. * Use {@link RemoteControlClient#editMetadata(boolean)} to create an instance of an editor, @@ -399,24 +394,7 @@ public class RemoteControlClient * for the associated client. Once the metadata has been "applied", you cannot reuse this * instance of the MetadataEditor. */ - public class MetadataEditor { - /** - * @hide - */ - protected boolean mMetadataChanged; - /** - * @hide - */ - protected boolean mArtworkChanged; - /** - * @hide - */ - protected Bitmap mEditorArtwork; - /** - * @hide - */ - protected Bundle mEditorMetadata; - private boolean mApplied = false; + public class MetadataEditor extends MediaMetadataEditor { // only use RemoteControlClient.editMetadata() to get a MetadataEditor instance private MetadataEditor() { } @@ -431,9 +409,10 @@ public class RemoteControlClient * The metadata key for the content artwork / album art. */ public final static int BITMAP_KEY_ARTWORK = 100; + /** * @hide - * TODO(jmtrivi) have lockscreen and music move to the new key name + * TODO(jmtrivi) have lockscreen move to the new key name and remove */ public final static int METADATA_KEY_ARTWORK = BITMAP_KEY_ARTWORK; @@ -460,15 +439,7 @@ public class RemoteControlClient */ public synchronized MetadataEditor putString(int key, String value) throws IllegalArgumentException { - if (mApplied) { - Log.e(TAG, "Can't edit a previously applied MetadataEditor"); - return this; - } - if (!validTypeForKey(key, METADATA_KEYS_TYPE_STRING)) { - throw(new IllegalArgumentException("Invalid type 'String' for key "+ key)); - } - mEditorMetadata.putString(String.valueOf(key), value); - mMetadataChanged = true; + super.putString(key, value); return this; } @@ -489,15 +460,7 @@ public class RemoteControlClient */ public synchronized MetadataEditor putLong(int key, long value) throws IllegalArgumentException { - if (mApplied) { - Log.e(TAG, "Can't edit a previously applied MetadataEditor"); - return this; - } - if (!validTypeForKey(key, METADATA_KEYS_TYPE_LONG)) { - throw(new IllegalArgumentException("Invalid type 'long' for key "+ key)); - } - mEditorMetadata.putLong(String.valueOf(key), value); - mMetadataChanged = true; + super.putLong(key, value); return this; } @@ -511,31 +474,22 @@ public class RemoteControlClient * @throws IllegalArgumentException * @see android.graphics.Bitmap */ + @Override public synchronized MetadataEditor putBitmap(int key, Bitmap bitmap) throws IllegalArgumentException { - if (mApplied) { - Log.e(TAG, "Can't edit a previously applied MetadataEditor"); - return this; - } - if (key != BITMAP_KEY_ARTWORK) { - throw(new IllegalArgumentException("Invalid type 'Bitmap' for key "+ key)); - } - mEditorArtwork = bitmap; - mArtworkChanged = true; + super.putBitmap(key, bitmap); return this; } /** - * Clears all the metadata that has been set since the MetadataEditor instance was - * created with {@link RemoteControlClient#editMetadata(boolean)}. + * Clears all the metadata that has been set since the MetadataEditor instance was created + * (with {@link RemoteControlClient#editMetadata(boolean)}). + * Note that clearing the metadata doesn't reset the editable keys + * (use {@link MediaMetadataEditor#removeEditableKeys()} instead). */ + @Override public synchronized void clear() { - if (mApplied) { - Log.e(TAG, "Can't clear a previously applied MetadataEditor"); - return; - } - mEditorMetadata.clear(); - mEditorArtwork = null; + super.clear(); } /** @@ -552,6 +506,8 @@ public class RemoteControlClient synchronized(mCacheLock) { // assign the edited data mMetadata = new Bundle(mEditorMetadata); + // add the information about editable keys + mMetadata.putLong(String.valueOf(KEY_EDITABLE_MASK), mEditableKeys); if ((mOriginalArtwork != null) && (!mOriginalArtwork.equals(mEditorArtwork))) { mOriginalArtwork.recycle(); } @@ -559,13 +515,13 @@ public class RemoteControlClient mEditorArtwork = null; if (mMetadataChanged & mArtworkChanged) { // send to remote control display if conditions are met - sendMetadataWithArtwork_syncCacheLock(); + sendMetadataWithArtwork_syncCacheLock(null, 0, 0); } else if (mMetadataChanged) { // send to remote control display if conditions are met - sendMetadata_syncCacheLock(); + sendMetadata_syncCacheLock(null); } else if (mArtworkChanged) { // send to remote control display if conditions are met - sendArtwork_syncCacheLock(); + sendArtwork_syncCacheLock(null, 0, 0); } mApplied = true; } @@ -585,6 +541,7 @@ public class RemoteControlClient editor.mEditorArtwork = null; editor.mMetadataChanged = true; editor.mArtworkChanged = true; + editor.mEditableKeys = 0; } else { editor.mEditorMetadata = new Bundle(mMetadata); editor.mEditorArtwork = mOriginalArtwork; @@ -663,7 +620,7 @@ public class RemoteControlClient mPlaybackStateChangeTimeMs = SystemClock.elapsedRealtime(); // send to remote control display if conditions are met - sendPlaybackState_syncCacheLock(); + sendPlaybackState_syncCacheLock(null); // update AudioService sendAudioServiceNewPlaybackState_syncCacheLock(); @@ -739,7 +696,8 @@ public class RemoteControlClient * {@link #FLAG_KEY_MEDIA_STOP}, * {@link #FLAG_KEY_MEDIA_FAST_FORWARD}, * {@link #FLAG_KEY_MEDIA_NEXT}, - * {@link #FLAG_KEY_MEDIA_POSITION_UPDATE} + * {@link #FLAG_KEY_MEDIA_POSITION_UPDATE}, + * {@link #FLAG_KEY_MEDIA_RATING}. */ public void setTransportControlFlags(int transportControlFlags) { synchronized(mCacheLock) { @@ -747,10 +705,39 @@ public class RemoteControlClient mTransportControlFlags = transportControlFlags; // send to remote control display if conditions are met - sendTransportControlInfo_syncCacheLock(); + sendTransportControlInfo_syncCacheLock(null); + } + } + + /** + * Interface definition for a callback to be invoked when one of the metadata values has + * been updated. + * Implement this interface to receive metadata updates after registering your listener + * through {@link RemoteControlClient#setMetadataUpdateListener(OnMetadataUpdateListener)}. + */ + public interface OnMetadataUpdateListener { + /** + * Called on the implementer to notify that the metadata field for the given key has + * been updated to the new value. + * @param key the identifier of the updated metadata field. + * @param newValue the Object storing the new value for the key. + */ + public abstract void onMetadataUpdate(int key, Object newValue); + } + + /** + * Sets the listener to be called whenever the metadata is updated. + * New metadata values will be received in the same thread as the one in which + * RemoteControlClient was created. + * @param l the metadata update listener + */ + public void setMetadataUpdateListener(OnMetadataUpdateListener l) { + synchronized(mCacheLock) { + mMetadataUpdateListener = l; } } + /** * Interface definition for a callback to be invoked when the media playback position is * requested to be updated. @@ -804,7 +791,7 @@ public class RemoteControlClient mPositionUpdateListener = l; if (oldCapa != mPlaybackPositionCapabilities) { // tell RCDs that this RCC's playback position capabilities have changed - sendTransportControlInfo_syncCacheLock(); + sendTransportControlInfo_syncCacheLock(null); } } } @@ -826,7 +813,7 @@ public class RemoteControlClient mPositionProvider = l; if (oldCapa != mPlaybackPositionCapabilities) { // tell RCDs that this RCC's playback position capabilities have changed - sendTransportControlInfo_syncCacheLock(); + sendTransportControlInfo_syncCacheLock(null); } if ((mPositionProvider != null) && (mEventHandler != null) && playbackPositionShouldMove(mPlaybackState)) { @@ -1023,6 +1010,11 @@ public class RemoteControlClient */ private OnGetPlaybackPositionListener mPositionProvider; /** + * Listener registered by user of RemoteControlClient to receive edit changes to metadata + * it exposes. + */ + private OnMetadataUpdateListener mMetadataUpdateListener; + /** * The current remote control client generation ID across the system, as known by this object */ private int mCurrentClientGenId = -1; @@ -1057,6 +1049,7 @@ public class RemoteControlClient private int mArtworkExpectedWidth; private int mArtworkExpectedHeight; private boolean mWantsPositionSync = false; + private boolean mEnabled = true; DisplayInfoForClient(IRemoteControlDisplay rcd, int w, int h) { mRcDisplay = rcd; @@ -1091,6 +1084,7 @@ public class RemoteControlClient */ private final IRemoteControlClient mIRCC = new IRemoteControlClient.Stub() { + //TODO change name to informationRequestForAllDisplays() public void onInformationRequested(int generationId, int infoFlags) { // only post messages, we can't block here if (mEventHandler != null) { @@ -1104,12 +1098,30 @@ public class RemoteControlClient mEventHandler.removeMessages(MSG_REQUEST_METADATA); mEventHandler.removeMessages(MSG_REQUEST_TRANSPORTCONTROL); mEventHandler.removeMessages(MSG_REQUEST_ARTWORK); + mEventHandler.removeMessages(MSG_REQUEST_METADATA_ARTWORK); mEventHandler.sendMessage( - mEventHandler.obtainMessage(MSG_REQUEST_PLAYBACK_STATE)); + mEventHandler.obtainMessage(MSG_REQUEST_PLAYBACK_STATE, null)); mEventHandler.sendMessage( - mEventHandler.obtainMessage(MSG_REQUEST_TRANSPORTCONTROL)); - mEventHandler.sendMessage(mEventHandler.obtainMessage(MSG_REQUEST_METADATA)); - mEventHandler.sendMessage(mEventHandler.obtainMessage(MSG_REQUEST_ARTWORK)); + mEventHandler.obtainMessage(MSG_REQUEST_TRANSPORTCONTROL, null)); + mEventHandler.sendMessage(mEventHandler.obtainMessage(MSG_REQUEST_METADATA_ARTWORK, + 0, 0, null)); + } + } + + public void informationRequestForDisplay(IRemoteControlDisplay rcd, int w, int h) { + // only post messages, we can't block here + if (mEventHandler != null) { + mEventHandler.sendMessage( + mEventHandler.obtainMessage(MSG_REQUEST_TRANSPORTCONTROL, rcd)); + mEventHandler.sendMessage( + mEventHandler.obtainMessage(MSG_REQUEST_PLAYBACK_STATE, rcd)); + if ((w > 0) && (h > 0)) { + mEventHandler.sendMessage( + mEventHandler.obtainMessage(MSG_REQUEST_METADATA_ARTWORK, w, h, rcd)); + } else { + mEventHandler.sendMessage( + mEventHandler.obtainMessage(MSG_REQUEST_METADATA, rcd)); + } } } @@ -1154,6 +1166,14 @@ public class RemoteControlClient } } + public void enableRemoteControlDisplay(IRemoteControlDisplay rcd, boolean enabled) { + // only post messages, we can't block here + if ((mEventHandler != null) && (rcd != null)) { + mEventHandler.sendMessage(mEventHandler.obtainMessage( + MSG_DISPLAY_ENABLE, enabled ? 1 : 0, 0/*arg2 ignored*/, rcd)); + } + } + public void seekTo(int generationId, long timeMs) { // only post messages, we can't block here if (mEventHandler != null) { @@ -1163,6 +1183,14 @@ public class RemoteControlClient new Long(timeMs))); } } + + public void updateMetadata(int generationId, int key, Rating value) { + // only post messages, we can't block here + if (mEventHandler != null) { + mEventHandler.sendMessage(mEventHandler.obtainMessage( + MSG_UPDATE_METADATA, generationId /* arg1 */, key /* arg2*/, value)); + } + } }; /** @@ -1206,6 +1234,9 @@ public class RemoteControlClient private final static int MSG_SEEK_TO = 10; private final static int MSG_POSITION_DRIFT_CHECK = 11; private final static int MSG_DISPLAY_WANTS_POS_SYNC = 12; + private final static int MSG_UPDATE_METADATA = 13; + private final static int MSG_REQUEST_METADATA_ARTWORK = 14; + private final static int MSG_DISPLAY_ENABLE = 15; private class EventHandler extends Handler { public EventHandler(RemoteControlClient rcc, Looper looper) { @@ -1217,22 +1248,29 @@ public class RemoteControlClient switch(msg.what) { case MSG_REQUEST_PLAYBACK_STATE: synchronized (mCacheLock) { - sendPlaybackState_syncCacheLock(); + sendPlaybackState_syncCacheLock((IRemoteControlDisplay)msg.obj); } break; case MSG_REQUEST_METADATA: synchronized (mCacheLock) { - sendMetadata_syncCacheLock(); + sendMetadata_syncCacheLock((IRemoteControlDisplay)msg.obj); } break; case MSG_REQUEST_TRANSPORTCONTROL: synchronized (mCacheLock) { - sendTransportControlInfo_syncCacheLock(); + sendTransportControlInfo_syncCacheLock((IRemoteControlDisplay)msg.obj); } break; case MSG_REQUEST_ARTWORK: synchronized (mCacheLock) { - sendArtwork_syncCacheLock(); + sendArtwork_syncCacheLock((IRemoteControlDisplay)msg.obj, + msg.arg1, msg.arg2); + } + break; + case MSG_REQUEST_METADATA_ARTWORK: + synchronized (mCacheLock) { + sendMetadataWithArtwork_syncCacheLock((IRemoteControlDisplay)msg.obj, + msg.arg1, msg.arg2); } break; case MSG_NEW_INTERNAL_CLIENT_GEN: @@ -1259,6 +1297,12 @@ public class RemoteControlClient case MSG_DISPLAY_WANTS_POS_SYNC: onDisplayWantsSync((IRemoteControlDisplay)msg.obj, msg.arg1 == 1); break; + case MSG_UPDATE_METADATA: + onUpdateMetadata(msg.arg1, msg.arg2, msg.obj); + break; + case MSG_DISPLAY_ENABLE: + onDisplayEnable((IRemoteControlDisplay)msg.obj, msg.arg1 == 1); + break; default: Log.e(TAG, "Unknown event " + msg.what + " in RemoteControlClient handler"); } @@ -1268,58 +1312,101 @@ public class RemoteControlClient //=========================================================== // Communication with the IRemoteControlDisplay (the displays known to the system) - private void sendPlaybackState_syncCacheLock() { + private void sendPlaybackState_syncCacheLock(IRemoteControlDisplay target) { if (mCurrentClientGenId == mInternalClientGenId) { - final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator(); - while (displayIterator.hasNext()) { - final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); + if (target != null) { try { - di.mRcDisplay.setPlaybackState(mInternalClientGenId, + target.setPlaybackState(mInternalClientGenId, mPlaybackState, mPlaybackStateChangeTimeMs, mPlaybackPositionMs, mPlaybackSpeed); } catch (RemoteException e) { - Log.e(TAG, "Error in setPlaybackState(), dead display " + di.mRcDisplay, e); - displayIterator.remove(); + Log.e(TAG, "Error in setPlaybackState() for dead display " + target, e); + } + return; + } + // target == null implies all displays must be updated + final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator(); + while (displayIterator.hasNext()) { + final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); + if (di.mEnabled) { + try { + di.mRcDisplay.setPlaybackState(mInternalClientGenId, + mPlaybackState, mPlaybackStateChangeTimeMs, mPlaybackPositionMs, + mPlaybackSpeed); + } catch (RemoteException e) { + Log.e(TAG, "Error in setPlaybackState(), dead display " + di.mRcDisplay, e); + displayIterator.remove(); + } } } } } - private void sendMetadata_syncCacheLock() { + private void sendMetadata_syncCacheLock(IRemoteControlDisplay target) { if (mCurrentClientGenId == mInternalClientGenId) { + if (target != null) { + try { + target.setMetadata(mInternalClientGenId, mMetadata); + } catch (RemoteException e) { + Log.e(TAG, "Error in setMetadata() for dead display " + target, e); + } + return; + } + // target == null implies all displays must be updated final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator(); while (displayIterator.hasNext()) { final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); - try { - di.mRcDisplay.setMetadata(mInternalClientGenId, mMetadata); - } catch (RemoteException e) { - Log.e(TAG, "Error in setMetadata(), dead display " + di.mRcDisplay, e); - displayIterator.remove(); + if (di.mEnabled) { + try { + di.mRcDisplay.setMetadata(mInternalClientGenId, mMetadata); + } catch (RemoteException e) { + Log.e(TAG, "Error in setMetadata(), dead display " + di.mRcDisplay, e); + displayIterator.remove(); + } } } } } - private void sendTransportControlInfo_syncCacheLock() { + private void sendTransportControlInfo_syncCacheLock(IRemoteControlDisplay target) { if (mCurrentClientGenId == mInternalClientGenId) { - final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator(); - while (displayIterator.hasNext()) { - final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); + if (target != null) { try { - di.mRcDisplay.setTransportControlInfo(mInternalClientGenId, + target.setTransportControlInfo(mInternalClientGenId, mTransportControlFlags, mPlaybackPositionCapabilities); } catch (RemoteException e) { - Log.e(TAG, "Error in setTransportControlFlags(), dead display " + di.mRcDisplay, + Log.e(TAG, "Error in setTransportControlFlags() for dead display " + target, e); - displayIterator.remove(); + } + return; + } + // target == null implies all displays must be updated + final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator(); + while (displayIterator.hasNext()) { + final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); + if (di.mEnabled) { + try { + di.mRcDisplay.setTransportControlInfo(mInternalClientGenId, + mTransportControlFlags, mPlaybackPositionCapabilities); + } catch (RemoteException e) { + Log.e(TAG, "Error in setTransportControlFlags(), dead display " + di.mRcDisplay, + e); + displayIterator.remove(); + } } } } } - private void sendArtwork_syncCacheLock() { + private void sendArtwork_syncCacheLock(IRemoteControlDisplay target, int w, int h) { // FIXME modify to cache all requested sizes? if (mCurrentClientGenId == mInternalClientGenId) { + if (target != null) { + final DisplayInfoForClient di = new DisplayInfoForClient(target, w, h); + sendArtworkToDisplay(di); + return; + } + // target == null implies all displays must be updated final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator(); while (displayIterator.hasNext()) { if (!sendArtworkToDisplay((DisplayInfoForClient) displayIterator.next())) { @@ -1349,19 +1436,35 @@ public class RemoteControlClient return true; } - private void sendMetadataWithArtwork_syncCacheLock() { + private void sendMetadataWithArtwork_syncCacheLock(IRemoteControlDisplay target, int w, int h) { // FIXME modify to cache all requested sizes? if (mCurrentClientGenId == mInternalClientGenId) { + if (target != null) { + try { + if ((w > 0) && (h > 0)) { + Bitmap artwork = scaleBitmapIfTooBig(mOriginalArtwork, w, h); + target.setAllMetadata(mInternalClientGenId, mMetadata, artwork); + } else { + target.setMetadata(mInternalClientGenId, mMetadata); + } + } catch (RemoteException e) { + Log.e(TAG, "Error in set(All)Metadata() for dead display " + target, e); + } + return; + } + // target == null implies all displays must be updated final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator(); while (displayIterator.hasNext()) { final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); try { - if ((di.mArtworkExpectedWidth > 0) && (di.mArtworkExpectedHeight > 0)) { - Bitmap artwork = scaleBitmapIfTooBig(mOriginalArtwork, - di.mArtworkExpectedWidth, di.mArtworkExpectedHeight); - di.mRcDisplay.setAllMetadata(mInternalClientGenId, mMetadata, artwork); - } else { - di.mRcDisplay.setMetadata(mInternalClientGenId, mMetadata); + if (di.mEnabled) { + if ((di.mArtworkExpectedWidth > 0) && (di.mArtworkExpectedHeight > 0)) { + Bitmap artwork = scaleBitmapIfTooBig(mOriginalArtwork, + di.mArtworkExpectedWidth, di.mArtworkExpectedHeight); + di.mRcDisplay.setAllMetadata(mInternalClientGenId, mMetadata, artwork); + } else { + di.mRcDisplay.setMetadata(mInternalClientGenId, mMetadata); + } } } catch (RemoteException e) { Log.e(TAG, "Error when setting metadata, dead display " + di.mRcDisplay, e); @@ -1496,8 +1599,10 @@ public class RemoteControlClient ((di.mArtworkExpectedWidth != w) || (di.mArtworkExpectedHeight != h))) { di.mArtworkExpectedWidth = w; di.mArtworkExpectedHeight = h; - if (!sendArtworkToDisplay(di)) { - displayIterator.remove(); + if (di.mEnabled) { + if (!sendArtworkToDisplay(di)) { + displayIterator.remove(); + } } break; } @@ -1515,11 +1620,13 @@ public class RemoteControlClient // that gets upated, and whether the list has one entry that wants position sync while (displayIterator.hasNext()) { final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); - if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) { - di.mWantsPositionSync = wantsSync; - } - if (di.mWantsPositionSync) { - newNeedsPositionSync = true; + if (di.mEnabled) { + if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) { + di.mWantsPositionSync = wantsSync; + } + if (di.mWantsPositionSync) { + newNeedsPositionSync = true; + } } } mNeedsPositionSync = newNeedsPositionSync; @@ -1530,6 +1637,19 @@ public class RemoteControlClient } } + /** pre-condition rcd != null */ + private void onDisplayEnable(IRemoteControlDisplay rcd, boolean enable) { + synchronized(mCacheLock) { + final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator(); + while (displayIterator.hasNext()) { + final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next(); + if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) { + di.mEnabled = enable; + } + } + } + } + private void onSeekTo(int generationId, long timeMs) { synchronized (mCacheLock) { if ((mCurrentClientGenId == generationId) && (mPositionUpdateListener != null)) { @@ -1538,6 +1658,14 @@ public class RemoteControlClient } } + private void onUpdateMetadata(int generationId, int key, Object value) { + synchronized (mCacheLock) { + if ((mCurrentClientGenId == generationId) && (mMetadataUpdateListener != null)) { + mMetadataUpdateListener.onMetadataUpdate(key, value); + } + } + } + //=========================================================== // Internal utilities @@ -1576,24 +1704,6 @@ public class RemoteControlClient return bitmap; } - /** - * Fast routine to go through an array of allowed keys and return whether the key is part - * of that array - * @param key the key value - * @param validKeys the array of valid keys for a given type - * @return true if the key is part of the array, false otherwise - */ - private static boolean validTypeForKey(int key, int[] validKeys) { - try { - for (int i = 0 ; ; i++) { - if (key == validKeys[i]) { - return true; - } - } - } catch (ArrayIndexOutOfBoundsException e) { - return false; - } - } /** * Returns whether, for the given playback state, the playback position is expected to @@ -1602,7 +1712,7 @@ public class RemoteControlClient * @return true during any form of playback, false if it's not playing anything while in this * playback state */ - private static boolean playbackPositionShouldMove(int playstate) { + static boolean playbackPositionShouldMove(int playstate) { switch(playstate) { case PLAYSTATE_STOPPED: case PLAYSTATE_PAUSED: diff --git a/media/java/android/media/RemoteController.java b/media/java/android/media/RemoteController.java new file mode 100644 index 0000000..7865ec8 --- /dev/null +++ b/media/java/android/media/RemoteController.java @@ -0,0 +1,908 @@ +/* + * Copyright (C) 2013 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.Manifest; +import android.app.ActivityManager; +import android.app.PendingIntent; +import android.app.PendingIntent.CanceledException; +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.media.IRemoteControlDisplay; +import android.media.MediaMetadataEditor; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.KeyEvent; + +import java.lang.ref.WeakReference; + +/** + * The RemoteController class is used to control media playback, display and update media metadata + * and playback status, published by applications using the {@link RemoteControlClient} class. + * <p> + * A RemoteController shall be registered through + * {@link AudioManager#registerRemoteController(RemoteController)} in order for the system to send + * media event updates to the {@link OnClientUpdateListener} listener set in the class constructor. + * Implement the methods of the interface to receive the information published by the active + * {@link RemoteControlClient} instances. + * <br>By default an {@link OnClientUpdateListener} implementation will not receive bitmaps for + * album art. Use {@link #setArtworkConfiguration(int, int)} to receive images as well. + * <p> + * Registration requires the {@link OnClientUpdateListener} listener to be one of the enabled + * notification listeners (see {@link android.service.notification.NotificationListenerService}). + */ +public final class RemoteController +{ + private final static int MAX_BITMAP_DIMENSION = 512; + private final static int TRANSPORT_UNKNOWN = 0; + private final static String TAG = "RemoteController"; + private final static boolean DEBUG = false; + private final static Object mGenLock = new Object(); + private final static Object mInfoLock = new Object(); + private final RcDisplay mRcd; + private final Context mContext; + private final AudioManager mAudioManager; + private final int mMaxBitmapDimension; + private MetadataEditor mMetadataEditor; + + /** + * Synchronized on mGenLock + */ + private int mClientGenerationIdCurrent = 0; + + /** + * Synchronized on mInfoLock + */ + private boolean mIsRegistered = false; + private PendingIntent mClientPendingIntentCurrent; + private OnClientUpdateListener mOnClientUpdateListener; + private PlaybackInfo mLastPlaybackInfo; + private int mArtworkWidth = -1; + private int mArtworkHeight = -1; + private boolean mEnabled = true; + + /** + * Class constructor. + * @param context the {@link Context}, must be non-null. + * @param updateListener the listener to be called whenever new client information is available, + * must be non-null. + * @throws IllegalArgumentException + */ + public RemoteController(Context context, OnClientUpdateListener updateListener) + throws IllegalArgumentException { + this(context, updateListener, null); + } + + /** + * Class constructor. + * @param context the {@link Context}, must be non-null. + * @param updateListener the listener to be called whenever new client information is available, + * must be non-null. + * @param looper the {@link Looper} on which to run the event loop, + * or null to use the current thread's looper. + * @throws java.lang.IllegalArgumentException + */ + public RemoteController(Context context, OnClientUpdateListener updateListener, Looper looper) + throws IllegalArgumentException { + if (context == null) { + throw new IllegalArgumentException("Invalid null Context"); + } + if (updateListener == null) { + throw new IllegalArgumentException("Invalid null OnClientUpdateListener"); + } + if (looper != null) { + mEventHandler = new EventHandler(this, looper); + } else { + Looper l = Looper.myLooper(); + if (l != null) { + mEventHandler = new EventHandler(this, l); + } else { + throw new IllegalArgumentException("Calling thread not associated with a looper"); + } + } + mOnClientUpdateListener = updateListener; + mContext = context; + mRcd = new RcDisplay(this); + mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); + + if (ActivityManager.isLowRamDeviceStatic()) { + mMaxBitmapDimension = MAX_BITMAP_DIMENSION; + } else { + final DisplayMetrics dm = context.getResources().getDisplayMetrics(); + mMaxBitmapDimension = Math.max(dm.widthPixels, dm.heightPixels); + } + } + + + /** + * Interface definition for the callbacks to be invoked whenever media events, metadata + * and playback status are available. + */ + public interface OnClientUpdateListener { + /** + * Called whenever all information, previously received through the other + * methods of the listener, is no longer valid and is about to be refreshed. + * This is typically called whenever a new {@link RemoteControlClient} has been selected + * by the system to have its media information published. + * @param clearing true if there is no selected RemoteControlClient and no information + * is available. + */ + public void onClientChange(boolean clearing); + + /** + * Called whenever the playback state has changed. + * It is called when no information is known about the playback progress in the media and + * the playback speed. + * @param state one of the playback states authorized + * in {@link RemoteControlClient#setPlaybackState(int)}. + */ + public void onClientPlaybackStateUpdate(int state); + /** + * Called whenever the playback state has changed, and playback position + * and speed are known. + * @param state one of the playback states authorized + * in {@link RemoteControlClient#setPlaybackState(int)}. + * @param stateChangeTimeMs the system time at which the state change was reported, + * expressed in ms. Based on {@link android.os.SystemClock#elapsedRealtime()}. + * @param currentPosMs a positive value for the current media playback position expressed + * in ms, a negative value if the position is temporarily unknown. + * @param speed a value expressed as a ratio of 1x playback: 1.0f is normal playback, + * 2.0f is 2x, 0.5f is half-speed, -2.0f is rewind at 2x speed. 0.0f means nothing is + * playing (e.g. when state is {@link RemoteControlClient#PLAYSTATE_ERROR}). + */ + public void onClientPlaybackStateUpdate(int state, long stateChangeTimeMs, + long currentPosMs, float speed); + /** + * Called whenever the transport control flags have changed. + * @param transportControlFlags one of the flags authorized + * in {@link RemoteControlClient#setTransportControlFlags(int)}. + */ + public void onClientTransportControlUpdate(int transportControlFlags); + /** + * Called whenever new metadata is available. + * See the {@link MediaMetadataEditor#putLong(int, long)}, + * {@link MediaMetadataEditor#putString(int, String)}, + * {@link MediaMetadataEditor#putBitmap(int, Bitmap)}, and + * {@link MediaMetadataEditor#putObject(int, Object)} methods for the various keys that + * can be queried. + * @param metadataEditor the container of the new metadata. + */ + public void onClientMetadataUpdate(MetadataEditor metadataEditor); + }; + + + /** + * @hide + */ + public String getRemoteControlClientPackageName() { + return mClientPendingIntentCurrent != null ? + mClientPendingIntentCurrent.getCreatorPackage() : null; + } + + /** + * Return the estimated playback position of the current media track or a negative value + * if not available. + * + * <p>The value returned is estimated by the current process and may not be perfect. + * The time returned by this method is calculated from the last state change time based + * on the current play position at that time and the last known playback speed. + * An application may call {@link #setSynchronizationMode(int)} to apply + * a synchronization policy that will periodically re-sync the estimated position + * with the RemoteControlClient.</p> + * + * @return the current estimated playback position in milliseconds or a negative value + * if not available + * + * @see OnClientUpdateListener#onClientPlaybackStateUpdate(int, long, long, float) + */ + public long getEstimatedMediaPosition() { + if (mLastPlaybackInfo != null) { + if (!RemoteControlClient.playbackPositionShouldMove(mLastPlaybackInfo.mState)) { + return mLastPlaybackInfo.mCurrentPosMs; + } + + // Take the current position at the time of state change and estimate. + final long thenPos = mLastPlaybackInfo.mCurrentPosMs; + if (thenPos < 0) { + return -1; + } + + final long now = SystemClock.elapsedRealtime(); + final long then = mLastPlaybackInfo.mStateChangeTimeMs; + final long sinceThen = now - then; + final long scaledSinceThen = (long) (sinceThen * mLastPlaybackInfo.mSpeed); + return thenPos + scaledSinceThen; + } + return -1; + } + + + /** + * Send a simulated key event for a media button to be received by the current client. + * To simulate a key press, you must first send a KeyEvent built with + * a {@link KeyEvent#ACTION_DOWN} action, then another event with the {@link KeyEvent#ACTION_UP} + * action. + * <p>The key event will be sent to the registered receiver + * (see {@link AudioManager#registerMediaButtonEventReceiver(PendingIntent)}) whose associated + * {@link RemoteControlClient}'s metadata and playback state is published (there may be + * none under some circumstances). + * @param keyEvent a {@link KeyEvent} instance whose key code is one of + * {@link KeyEvent#KEYCODE_MUTE}, + * {@link KeyEvent#KEYCODE_HEADSETHOOK}, + * {@link KeyEvent#KEYCODE_MEDIA_PLAY}, + * {@link KeyEvent#KEYCODE_MEDIA_PAUSE}, + * {@link KeyEvent#KEYCODE_MEDIA_PLAY_PAUSE}, + * {@link KeyEvent#KEYCODE_MEDIA_STOP}, + * {@link KeyEvent#KEYCODE_MEDIA_NEXT}, + * {@link KeyEvent#KEYCODE_MEDIA_PREVIOUS}, + * {@link KeyEvent#KEYCODE_MEDIA_REWIND}, + * {@link KeyEvent#KEYCODE_MEDIA_RECORD}, + * {@link KeyEvent#KEYCODE_MEDIA_FAST_FORWARD}, + * {@link KeyEvent#KEYCODE_MEDIA_CLOSE}, + * {@link KeyEvent#KEYCODE_MEDIA_EJECT}, + * or {@link KeyEvent#KEYCODE_MEDIA_AUDIO_TRACK}. + * @return true if the event was successfully sent, false otherwise. + * @throws IllegalArgumentException + */ + public boolean sendMediaKeyEvent(KeyEvent keyEvent) throws IllegalArgumentException { + if (!MediaFocusControl.isMediaKeyCode(keyEvent.getKeyCode())) { + throw new IllegalArgumentException("not a media key event"); + } + final PendingIntent pi; + synchronized(mInfoLock) { + if (!mIsRegistered) { + Log.e(TAG, "Cannot use sendMediaKeyEvent() from an unregistered RemoteController"); + return false; + } + if (!mEnabled) { + Log.e(TAG, "Cannot use sendMediaKeyEvent() from a disabled RemoteController"); + return false; + } + pi = mClientPendingIntentCurrent; + } + if (pi != null) { + Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON); + intent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent); + try { + pi.send(mContext, 0, intent); + } catch (CanceledException e) { + Log.e(TAG, "Error sending intent for media button down: ", e); + return false; + } + } else { + Log.i(TAG, "No-op when sending key click, no receiver right now"); + return false; + } + return true; + } + + + /** + * Sets the new playback position. + * This method can only be called on a registered RemoteController. + * @param timeMs a 0 or positive value for the new playback position, expressed in ms. + * @return true if the command to set the playback position was successfully sent. + * @throws IllegalArgumentException + */ + public boolean seekTo(long timeMs) throws IllegalArgumentException { + if (!mEnabled) { + Log.e(TAG, "Cannot use seekTo() from a disabled RemoteController"); + return false; + } + if (timeMs < 0) { + throw new IllegalArgumentException("illegal negative time value"); + } + final int genId; + synchronized (mGenLock) { + genId = mClientGenerationIdCurrent; + } + mAudioManager.setRemoteControlClientPlaybackPosition(genId, timeMs); + return true; + } + + + /** + * @hide + * @param wantBitmap + * @param width + * @param height + * @return true if successful + * @throws IllegalArgumentException + */ + public boolean setArtworkConfiguration(boolean wantBitmap, int width, int height) + throws IllegalArgumentException { + synchronized (mInfoLock) { + if (wantBitmap) { + if ((width > 0) && (height > 0)) { + if (width > mMaxBitmapDimension) { width = mMaxBitmapDimension; } + if (height > mMaxBitmapDimension) { height = mMaxBitmapDimension; } + mArtworkWidth = width; + mArtworkHeight = height; + } else { + throw new IllegalArgumentException("Invalid dimensions"); + } + } else { + mArtworkWidth = -1; + mArtworkHeight = -1; + } + if (mIsRegistered) { + mAudioManager.remoteControlDisplayUsesBitmapSize(mRcd, + mArtworkWidth, mArtworkHeight); + } // else new values have been stored, and will be read by AudioManager with + // RemoteController.getArtworkSize() when AudioManager.registerRemoteController() + // is called. + } + return true; + } + + /** + * Set the maximum artwork image dimensions to be received in the metadata. + * No bitmaps will be received unless this has been specified. + * @param width the maximum width in pixels + * @param height the maximum height in pixels + * @return true if the artwork dimension was successfully set. + * @throws IllegalArgumentException + */ + public boolean setArtworkConfiguration(int width, int height) throws IllegalArgumentException { + return setArtworkConfiguration(true, width, height); + } + + /** + * Prevents this RemoteController from receiving artwork images. + * @return true if receiving artwork images was successfully disabled. + */ + public boolean clearArtworkConfiguration() { + return setArtworkConfiguration(false, -1, -1); + } + + + /** + * Default playback position synchronization mode where the RemoteControlClient is not + * asked regularly for its playback position to see if it has drifted from the estimated + * position. + */ + public static final int POSITION_SYNCHRONIZATION_NONE = 0; + + /** + * The playback position synchronization mode where the RemoteControlClient instances which + * expose their playback position to the framework, will be regularly polled to check + * whether any drift has been noticed between their estimated position and the one they report. + * Note that this mode should only ever be used when needing to display very accurate playback + * position, as regularly polling a RemoteControlClient for its position may have an impact + * on battery life (if applicable) when this query will trigger network transactions in the + * case of remote playback. + */ + public static final int POSITION_SYNCHRONIZATION_CHECK = 1; + + /** + * Set the playback position synchronization mode. + * Must be called on a registered RemoteController. + * @param sync {@link #POSITION_SYNCHRONIZATION_NONE} or {@link #POSITION_SYNCHRONIZATION_CHECK} + * @return true if the synchronization mode was successfully set. + * @throws IllegalArgumentException + */ + public boolean setSynchronizationMode(int sync) throws IllegalArgumentException { + if ((sync != POSITION_SYNCHRONIZATION_NONE) || (sync != POSITION_SYNCHRONIZATION_CHECK)) { + throw new IllegalArgumentException("Unknown synchronization mode " + sync); + } + if (!mIsRegistered) { + Log.e(TAG, "Cannot set synchronization mode on an unregistered RemoteController"); + return false; + } + mAudioManager.remoteControlDisplayWantsPlaybackPositionSync(mRcd, + POSITION_SYNCHRONIZATION_CHECK == sync); + return true; + } + + + /** + * Creates a {@link MetadataEditor} for updating metadata values of the editable keys of + * the current {@link RemoteControlClient}. + * This method can only be called on a registered RemoteController. + * @return a new MetadataEditor instance. + */ + public MetadataEditor editMetadata() { + MetadataEditor editor = new MetadataEditor(); + editor.mEditorMetadata = new Bundle(); + editor.mEditorArtwork = null; + editor.mMetadataChanged = true; + editor.mArtworkChanged = true; + editor.mEditableKeys = 0; + return editor; + } + + + /** + * A class to read the metadata published by a {@link RemoteControlClient}, or send a + * {@link RemoteControlClient} new values for keys that can be edited. + */ + public class MetadataEditor extends MediaMetadataEditor { + /** + * @hide + */ + protected MetadataEditor() { } + + /** + * @hide + */ + protected MetadataEditor(Bundle metadata, long editableKeys) { + mEditorMetadata = metadata; + mEditableKeys = editableKeys; + + mEditorArtwork = (Bitmap) metadata.getParcelable( + String.valueOf(MediaMetadataEditor.BITMAP_KEY_ARTWORK)); + if (mEditorArtwork != null) { + cleanupBitmapFromBundle(MediaMetadataEditor.BITMAP_KEY_ARTWORK); + } + + mMetadataChanged = true; + mArtworkChanged = true; + mApplied = false; + } + + private void cleanupBitmapFromBundle(int key) { + if (METADATA_KEYS_TYPE.get(key, METADATA_TYPE_INVALID) == METADATA_TYPE_BITMAP) { + mEditorMetadata.remove(String.valueOf(key)); + } + } + + /** + * Applies all of the metadata changes that have been set since the MediaMetadataEditor + * instance was created with {@link RemoteController#editMetadata()} + * or since {@link #clear()} was called. + */ + public synchronized void apply() { + // "applying" a metadata bundle in RemoteController is only for sending edited + // key values back to the RemoteControlClient, so here we only care about the only + // editable key we support: RATING_KEY_BY_USER + if (!mMetadataChanged) { + return; + } + final int genId; + synchronized(mGenLock) { + genId = mClientGenerationIdCurrent; + } + synchronized(mInfoLock) { + if (mEditorMetadata.containsKey( + String.valueOf(MediaMetadataEditor.RATING_KEY_BY_USER))) { + Rating rating = (Rating) getObject( + MediaMetadataEditor.RATING_KEY_BY_USER, null); + mAudioManager.updateRemoteControlClientMetadata(genId, + MediaMetadataEditor.RATING_KEY_BY_USER, + rating); + } else { + Log.e(TAG, "no metadata to apply"); + } + // NOT setting mApplied to true as this type of MetadataEditor will be applied + // multiple times, whenever the user of a RemoteController needs to change the + // metadata (e.g. user changes the rating of a song more than once during playback) + mApplied = false; + } + } + + } + + + //================================================== + // Implementation of IRemoteControlDisplay interface + private static class RcDisplay extends IRemoteControlDisplay.Stub { + private final WeakReference<RemoteController> mController; + + RcDisplay(RemoteController rc) { + mController = new WeakReference<RemoteController>(rc); + } + + public void setCurrentClientId(int genId, PendingIntent clientMediaIntent, + boolean clearing) { + final RemoteController rc = mController.get(); + if (rc == null) { + return; + } + boolean isNew = false; + synchronized(mGenLock) { + if (rc.mClientGenerationIdCurrent != genId) { + rc.mClientGenerationIdCurrent = genId; + isNew = true; + } + } + if (clientMediaIntent != null) { + sendMsg(rc.mEventHandler, MSG_NEW_PENDING_INTENT, SENDMSG_REPLACE, + genId /*arg1*/, 0, clientMediaIntent /*obj*/, 0 /*delay*/); + } + if (isNew || clearing) { + sendMsg(rc.mEventHandler, MSG_CLIENT_CHANGE, SENDMSG_REPLACE, + genId /*arg1*/, clearing ? 1 : 0, null /*obj*/, 0 /*delay*/); + } + } + + public void setEnabled(boolean enabled) { + final RemoteController rc = mController.get(); + if (rc == null) { + return; + } + sendMsg(rc.mEventHandler, MSG_DISPLAY_ENABLE, SENDMSG_REPLACE, + enabled ? 1 : 0 /*arg1*/, 0, null /*obj*/, 0 /*delay*/); + } + + public void setPlaybackState(int genId, int state, + long stateChangeTimeMs, long currentPosMs, float speed) { + final RemoteController rc = mController.get(); + if (rc == null) { + return; + } + if (DEBUG) { + Log.d(TAG, "> new playback state: genId="+genId + + " state="+ state + + " changeTime="+ stateChangeTimeMs + + " pos=" + currentPosMs + + "ms speed=" + speed); + } + + synchronized(mGenLock) { + if (rc.mClientGenerationIdCurrent != genId) { + return; + } + } + final PlaybackInfo playbackInfo = + new PlaybackInfo(state, stateChangeTimeMs, currentPosMs, speed); + sendMsg(rc.mEventHandler, MSG_NEW_PLAYBACK_INFO, SENDMSG_REPLACE, + genId /*arg1*/, 0, playbackInfo /*obj*/, 0 /*delay*/); + + } + + public void setTransportControlInfo(int genId, int transportControlFlags, + int posCapabilities) { + final RemoteController rc = mController.get(); + if (rc == null) { + return; + } + synchronized(mGenLock) { + if (rc.mClientGenerationIdCurrent != genId) { + return; + } + } + sendMsg(rc.mEventHandler, MSG_NEW_TRANSPORT_INFO, SENDMSG_REPLACE, + genId /*arg1*/, transportControlFlags /*arg2*/, + null /*obj*/, 0 /*delay*/); + } + + public void setMetadata(int genId, Bundle metadata) { + final RemoteController rc = mController.get(); + if (rc == null) { + return; + } + if (DEBUG) { Log.e(TAG, "setMetadata("+genId+")"); } + if (metadata == null) { + return; + } + synchronized(mGenLock) { + if (rc.mClientGenerationIdCurrent != genId) { + return; + } + } + sendMsg(rc.mEventHandler, MSG_NEW_METADATA, SENDMSG_QUEUE, + genId /*arg1*/, 0 /*arg2*/, + metadata /*obj*/, 0 /*delay*/); + } + + public void setArtwork(int genId, Bitmap artwork) { + final RemoteController rc = mController.get(); + if (rc == null) { + return; + } + if (DEBUG) { Log.v(TAG, "setArtwork("+genId+")"); } + synchronized(mGenLock) { + if (rc.mClientGenerationIdCurrent != genId) { + return; + } + } + Bundle metadata = new Bundle(1); + metadata.putParcelable(String.valueOf(MediaMetadataEditor.BITMAP_KEY_ARTWORK), artwork); + sendMsg(rc.mEventHandler, MSG_NEW_METADATA, SENDMSG_QUEUE, + genId /*arg1*/, 0 /*arg2*/, + metadata /*obj*/, 0 /*delay*/); + } + + public void setAllMetadata(int genId, Bundle metadata, Bitmap artwork) { + final RemoteController rc = mController.get(); + if (rc == null) { + return; + } + if (DEBUG) { Log.e(TAG, "setAllMetadata("+genId+")"); } + if ((metadata == null) && (artwork == null)) { + return; + } + synchronized(mGenLock) { + if (rc.mClientGenerationIdCurrent != genId) { + return; + } + } + if (metadata == null) { + metadata = new Bundle(1); + } + if (artwork != null) { + metadata.putParcelable(String.valueOf(MediaMetadataEditor.BITMAP_KEY_ARTWORK), + artwork); + } + sendMsg(rc.mEventHandler, MSG_NEW_METADATA, SENDMSG_QUEUE, + genId /*arg1*/, 0 /*arg2*/, + metadata /*obj*/, 0 /*delay*/); + } + } + + //================================================== + // Event handling + private final EventHandler mEventHandler; + private final static int MSG_NEW_PENDING_INTENT = 0; + private final static int MSG_NEW_PLAYBACK_INFO = 1; + private final static int MSG_NEW_TRANSPORT_INFO = 2; + private final static int MSG_NEW_METADATA = 3; // msg always has non-null obj parameter + private final static int MSG_CLIENT_CHANGE = 4; + private final static int MSG_DISPLAY_ENABLE = 5; + + private class EventHandler extends Handler { + + public EventHandler(RemoteController rc, Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch(msg.what) { + case MSG_NEW_PENDING_INTENT: + onNewPendingIntent(msg.arg1, (PendingIntent) msg.obj); + break; + case MSG_NEW_PLAYBACK_INFO: + onNewPlaybackInfo(msg.arg1, (PlaybackInfo) msg.obj); + break; + case MSG_NEW_TRANSPORT_INFO: + onNewTransportInfo(msg.arg1, msg.arg2); + break; + case MSG_NEW_METADATA: + onNewMetadata(msg.arg1, (Bundle)msg.obj); + break; + case MSG_CLIENT_CHANGE: + onClientChange(msg.arg1, msg.arg2 == 1); + break; + case MSG_DISPLAY_ENABLE: + onDisplayEnable(msg.arg1 == 1); + break; + default: + Log.e(TAG, "unknown event " + msg.what); + } + } + } + + /** 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; + + private static void sendMsg(Handler handler, int msg, int existingMsgPolicy, + int arg1, int arg2, Object obj, int delayMs) { + if (handler == null) { + Log.e(TAG, "null event handler, will not deliver message " + msg); + return; + } + if (existingMsgPolicy == SENDMSG_REPLACE) { + handler.removeMessages(msg); + } else if (existingMsgPolicy == SENDMSG_NOOP && handler.hasMessages(msg)) { + return; + } + handler.sendMessageDelayed(handler.obtainMessage(msg, arg1, arg2, obj), delayMs); + } + + private void onNewPendingIntent(int genId, PendingIntent pi) { + synchronized(mGenLock) { + if (mClientGenerationIdCurrent != genId) { + return; + } + } + synchronized(mInfoLock) { + mClientPendingIntentCurrent = pi; + } + } + + private void onNewPlaybackInfo(int genId, PlaybackInfo pi) { + synchronized(mGenLock) { + if (mClientGenerationIdCurrent != genId) { + return; + } + } + final OnClientUpdateListener l; + synchronized(mInfoLock) { + l = this.mOnClientUpdateListener; + mLastPlaybackInfo = pi; + } + if (l != null) { + if (pi.mCurrentPosMs == RemoteControlClient.PLAYBACK_POSITION_ALWAYS_UNKNOWN) { + l.onClientPlaybackStateUpdate(pi.mState); + } else { + l.onClientPlaybackStateUpdate(pi.mState, pi.mStateChangeTimeMs, pi.mCurrentPosMs, + pi.mSpeed); + } + } + } + + private void onNewTransportInfo(int genId, int transportControlFlags) { + synchronized(mGenLock) { + if (mClientGenerationIdCurrent != genId) { + return; + } + } + final OnClientUpdateListener l; + synchronized(mInfoLock) { + l = mOnClientUpdateListener; + } + if (l != null) { + l.onClientTransportControlUpdate(transportControlFlags); + } + } + + /** + * @param genId + * @param metadata guaranteed to be always non-null + */ + private void onNewMetadata(int genId, Bundle metadata) { + synchronized(mGenLock) { + if (mClientGenerationIdCurrent != genId) { + return; + } + } + final OnClientUpdateListener l; + final MetadataEditor metadataEditor; + // prepare the received Bundle to be used inside a MetadataEditor + final long editableKeys = metadata.getLong( + String.valueOf(MediaMetadataEditor.KEY_EDITABLE_MASK), 0); + if (editableKeys != 0) { + metadata.remove(String.valueOf(MediaMetadataEditor.KEY_EDITABLE_MASK)); + } + synchronized(mInfoLock) { + l = mOnClientUpdateListener; + if ((mMetadataEditor != null) && (mMetadataEditor.mEditorMetadata != null)) { + if (mMetadataEditor.mEditorMetadata != metadata) { + // existing metadata, merge existing and new + mMetadataEditor.mEditorMetadata.putAll(metadata); + } + + mMetadataEditor.putBitmap(MediaMetadataEditor.BITMAP_KEY_ARTWORK, + (Bitmap)metadata.getParcelable( + String.valueOf(MediaMetadataEditor.BITMAP_KEY_ARTWORK))); + mMetadataEditor.cleanupBitmapFromBundle(MediaMetadataEditor.BITMAP_KEY_ARTWORK); + } else { + mMetadataEditor = new MetadataEditor(metadata, editableKeys); + } + metadataEditor = mMetadataEditor; + } + if (l != null) { + l.onClientMetadataUpdate(metadataEditor); + } + } + + private void onClientChange(int genId, boolean clearing) { + synchronized(mGenLock) { + if (mClientGenerationIdCurrent != genId) { + return; + } + } + final OnClientUpdateListener l; + synchronized(mInfoLock) { + l = mOnClientUpdateListener; + } + if (l != null) { + l.onClientChange(clearing); + } + } + + private void onDisplayEnable(boolean enabled) { + final OnClientUpdateListener l; + synchronized(mInfoLock) { + mEnabled = enabled; + l = this.mOnClientUpdateListener; + } + if (!enabled) { + // when disabling, reset all info sent to the user + final int genId; + synchronized (mGenLock) { + genId = mClientGenerationIdCurrent; + } + // send "stopped" state, happened "now", playback position is 0, speed 0.0f + final PlaybackInfo pi = new PlaybackInfo(RemoteControlClient.PLAYSTATE_STOPPED, + SystemClock.elapsedRealtime() /*stateChangeTimeMs*/, + 0 /*currentPosMs*/, 0.0f /*speed*/); + sendMsg(mEventHandler, MSG_NEW_PLAYBACK_INFO, SENDMSG_REPLACE, + genId /*arg1*/, 0 /*arg2, ignored*/, pi /*obj*/, 0 /*delay*/); + // send "blank" transport control info: no controls are supported + sendMsg(mEventHandler, MSG_NEW_TRANSPORT_INFO, SENDMSG_REPLACE, + genId /*arg1*/, 0 /*arg2, no flags*/, + null /*obj, ignored*/, 0 /*delay*/); + // send dummy metadata with empty string for title and artist, duration of 0 + Bundle metadata = new Bundle(3); + metadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_TITLE), ""); + metadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_ARTIST), ""); + metadata.putLong(String.valueOf(MediaMetadataRetriever.METADATA_KEY_DURATION), 0); + sendMsg(mEventHandler, MSG_NEW_METADATA, SENDMSG_QUEUE, + genId /*arg1*/, 0 /*arg2, ignored*/, metadata /*obj*/, 0 /*delay*/); + } + } + + //================================================== + private static class PlaybackInfo { + int mState; + long mStateChangeTimeMs; + long mCurrentPosMs; + float mSpeed; + + PlaybackInfo(int state, long stateChangeTimeMs, long currentPosMs, float speed) { + mState = state; + mStateChangeTimeMs = stateChangeTimeMs; + mCurrentPosMs = currentPosMs; + mSpeed = speed; + } + } + + /** + * @hide + * Used by AudioManager to mark this instance as registered. + * @param registered + */ + void setIsRegistered(boolean registered) { + synchronized (mInfoLock) { + mIsRegistered = registered; + } + } + + /** + * @hide + * Used by AudioManager to access binder to be registered/unregistered inside MediaFocusControl + * @return + */ + RcDisplay getRcDisplay() { + return mRcd; + } + + /** + * @hide + * Used by AudioManager to read the current artwork dimension + * @return array containing width (index 0) and height (index 1) of currently set artwork size + */ + int[] getArtworkSize() { + synchronized (mInfoLock) { + int[] size = { mArtworkWidth, mArtworkHeight }; + return size; + } + } + + /** + * @hide + * Used by AudioManager to access user listener receiving the client update notifications + * @return + */ + OnClientUpdateListener getUpdateListener() { + return mOnClientUpdateListener; + } +} diff --git a/media/java/android/media/RemoteDisplay.java b/media/java/android/media/RemoteDisplay.java index b463d26..7afce1a 100644 --- a/media/java/android/media/RemoteDisplay.java +++ b/media/java/android/media/RemoteDisplay.java @@ -42,6 +42,8 @@ public final class RemoteDisplay { private native int nativeListen(String iface); private native void nativeDispose(int ptr); + private native void nativePause(int ptr); + private native void nativeResume(int ptr); private RemoteDisplay(Listener listener, Handler handler) { mListener = listener; @@ -87,6 +89,14 @@ public final class RemoteDisplay { dispose(false); } + public void pause() { + nativePause(mPtr); + } + + public void resume() { + nativeResume(mPtr); + } + private void dispose(boolean finalized) { if (mPtr != 0) { if (mGuard != null) { @@ -113,11 +123,11 @@ public final class RemoteDisplay { // Called from native. private void notifyDisplayConnected(final Surface surface, - final int width, final int height, final int flags) { + final int width, final int height, final int flags, final int session) { mHandler.post(new Runnable() { @Override public void run() { - mListener.onDisplayConnected(surface, width, height, flags); + mListener.onDisplayConnected(surface, width, height, flags, session); } }); } @@ -146,7 +156,8 @@ public final class RemoteDisplay { * Listener invoked when the remote display connection changes state. */ public interface Listener { - void onDisplayConnected(Surface surface, int width, int height, int flags); + void onDisplayConnected(Surface surface, + int width, int height, int flags, int session); void onDisplayDisconnected(); void onDisplayError(int error); } diff --git a/media/java/android/drm/mobile1/DrmException.java b/media/java/android/media/ResourceBusyException.java index 7b06c92..a5abe21 100644 --- a/media/java/android/drm/mobile1/DrmException.java +++ b/media/java/android/media/ResourceBusyException.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007 The Android Open Source Project + * Copyright (C) 2013 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. @@ -14,21 +14,14 @@ * limitations under the License. */ -package android.drm.mobile1; - -import java.io.IOException; +package android.media; /** - * A DrmException is thrown to report errors specific to handle DRM content and rights. + * Exception thrown when an operation on a MediaDrm object is attempted + * and hardware resources are not available, due to being in use. */ -public class DrmException extends Exception -{ - // TODO: add more specific DRM error codes. - - private DrmException() { - } - - public DrmException(String message) { - super(message); +public final class ResourceBusyException extends MediaDrmException { + public ResourceBusyException(String detailMessage) { + super(detailMessage); } } diff --git a/media/java/android/media/Ringtone.java b/media/java/android/media/Ringtone.java index ebbfad9..1283e9b 100644 --- a/media/java/android/media/Ringtone.java +++ b/media/java/android/media/Ringtone.java @@ -24,7 +24,6 @@ import android.database.Cursor; import android.net.Uri; import android.os.Binder; import android.os.RemoteException; -import android.provider.DrmStore; import android.provider.MediaStore; import android.provider.Settings; import android.util.Log; @@ -50,12 +49,6 @@ public class Ringtone { MediaStore.Audio.Media.TITLE }; - private static final String[] DRM_COLUMNS = new String[] { - DrmStore.Audio._ID, - DrmStore.Audio.DATA, - DrmStore.Audio.TITLE - }; - private final Context mContext; private final AudioManager mAudioManager; private final boolean mAllowRemote; @@ -101,8 +94,8 @@ public class Ringtone { } /** - * Returns a human-presentable title for ringtone. Looks in media and DRM - * content providers. If not in either, uses the filename + * Returns a human-presentable title for ringtone. Looks in media + * content provider. If not in either, uses the filename * * @param context A context used for querying. */ @@ -131,9 +124,7 @@ public class Ringtone { } } else { try { - if (DrmStore.AUTHORITY.equals(authority)) { - cursor = res.query(uri, DRM_COLUMNS, null, null, null); - } else if (MediaStore.AUTHORITY.equals(authority)) { + if (MediaStore.AUTHORITY.equals(authority)) { cursor = res.query(uri, MEDIA_COLUMNS, null, null, null); } } catch (SecurityException e) { @@ -289,7 +280,7 @@ public class Ringtone { private boolean playFallbackRingtone() { if (mAudioManager.getStreamVolume(mStreamType) != 0) { int ringtoneType = RingtoneManager.getDefaultType(mUri); - if (ringtoneType != -1 && + if (ringtoneType == -1 || RingtoneManager.getActualDefaultRingtoneUri(mContext, ringtoneType) != null) { // Default ringtone, try fallback ringtone. try { @@ -318,6 +309,8 @@ public class Ringtone { } catch (NotFoundException nfe) { Log.e(TAG, "Fallback ringtone does not exist"); } + } else { + Log.w(TAG, "not playing fallback for " + mUri); } } return false; diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java index 5e18bfa..8e4004b 100644 --- a/media/java/android/media/RingtoneManager.java +++ b/media/java/android/media/RingtoneManager.java @@ -27,7 +27,6 @@ import android.content.res.AssetFileDescriptor; import android.database.Cursor; import android.net.Uri; import android.os.Environment; -import android.provider.DrmStore; import android.provider.MediaStore; import android.provider.Settings; import android.provider.Settings.System; @@ -85,7 +84,6 @@ public class RingtoneManager { * {@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}. */ @@ -113,7 +111,9 @@ public class RingtoneManager { /** * Given to the ringtone picker as a boolean. Whether to include DRM ringtones. + * @deprecated DRM ringtones are no longer supported */ + @Deprecated public static final String EXTRA_RINGTONE_INCLUDE_DRM = "android.intent.extra.ringtone.INCLUDE_DRM"; @@ -183,12 +183,6 @@ public class RingtoneManager { MediaStore.Audio.Media.TITLE_KEY }; - private static final String[] DRM_COLUMNS = new String[] { - DrmStore.Audio._ID, DrmStore.Audio.TITLE, - "\"" + DrmStore.Audio.CONTENT_URI + "\"", - DrmStore.Audio.TITLE + " AS " + MediaStore.Audio.Media.TITLE_KEY - }; - private static final String[] MEDIA_COLUMNS = new String[] { MediaStore.Audio.Media._ID, MediaStore.Audio.Media.TITLE, "\"" + MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "\"", @@ -228,8 +222,6 @@ public class RingtoneManager { private boolean mStopPreviousRingtone = true; private Ringtone mPreviousRingtone; - - private boolean mIncludeDrm; /** * Constructs a RingtoneManager. This constructor is recommended as its @@ -328,18 +320,26 @@ public class RingtoneManager { * * @return Whether DRM ringtones will be included. * @see #setIncludeDrm(boolean) + * Obsolete - always returns false + * @deprecated DRM ringtones are no longer supported */ + @Deprecated public boolean getIncludeDrm() { - return mIncludeDrm; + return false; } /** * Sets whether to include DRM ringtones. * * @param includeDrm Whether to include DRM ringtones. + * Obsolete - no longer has any effect + * @deprecated DRM ringtones are no longer supported */ + @Deprecated public void setIncludeDrm(boolean includeDrm) { - mIncludeDrm = includeDrm; + if (includeDrm) { + Log.w(TAG, "setIncludeDrm no longer supported"); + } } /** @@ -363,10 +363,9 @@ public class RingtoneManager { } final Cursor internalCursor = getInternalRingtones(); - final Cursor drmCursor = mIncludeDrm ? getDrmRingtones() : null; final Cursor mediaCursor = getMediaRingtones(); - return mCursor = new SortCursor(new Cursor[] { internalCursor, drmCursor, mediaCursor }, + return mCursor = new SortCursor(new Cursor[] { internalCursor, mediaCursor }, MediaStore.Audio.Media.DEFAULT_SORT_ORDER); } @@ -462,10 +461,6 @@ public class RingtoneManager { uri = getValidRingtoneUriFromCursorAndClose(context, rm.getMediaRingtones()); } - if (uri == null) { - uri = getValidRingtoneUriFromCursorAndClose(context, rm.getDrmRingtones()); - } - return uri; } @@ -487,16 +482,9 @@ public class RingtoneManager { private Cursor getInternalRingtones() { return query( MediaStore.Audio.Media.INTERNAL_CONTENT_URI, INTERNAL_COLUMNS, - constructBooleanTrueWhereClause(mFilterColumns, mIncludeDrm), + 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. @@ -506,7 +494,7 @@ public class RingtoneManager { status.equals(Environment.MEDIA_MOUNTED_READ_ONLY)) ? query( MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, MEDIA_COLUMNS, - constructBooleanTrueWhereClause(mFilterColumns, mIncludeDrm), null, + constructBooleanTrueWhereClause(mFilterColumns), null, MediaStore.Audio.Media.DEFAULT_SORT_ORDER) : null; } @@ -536,7 +524,7 @@ public class RingtoneManager { * @param columns The columns that must be true. * @return The where clause. */ - private static String constructBooleanTrueWhereClause(List<String> columns, boolean includeDrm) { + private static String constructBooleanTrueWhereClause(List<String> columns) { if (columns == null) return null; @@ -554,15 +542,6 @@ public class RingtoneManager { sb.append(")"); - if (!includeDrm) { - // If not DRM files should be shown, the where clause - // will be something like "(is_notification=1) and is_drm=0" - sb.append(" and "); - sb.append(MediaStore.MediaColumns.IS_DRM); - sb.append("=0"); - } - - return sb.toString(); } diff --git a/media/java/android/media/SoundPool.java b/media/java/android/media/SoundPool.java index 587af47..06af5de 100644 --- a/media/java/android/media/SoundPool.java +++ b/media/java/android/media/SoundPool.java @@ -16,19 +16,21 @@ package android.media; -import android.util.AndroidRuntimeException; -import android.util.Log; import java.io.File; import java.io.FileDescriptor; -import android.os.ParcelFileDescriptor; +import java.io.IOException; import java.lang.ref.WeakReference; + import android.content.Context; import android.content.res.AssetFileDescriptor; -import java.io.IOException; - import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.os.ParcelFileDescriptor; +import android.os.SystemProperties; +import android.util.AndroidRuntimeException; +import android.util.Log; + /** * The SoundPool class manages and plays audio resources for applications. @@ -102,24 +104,8 @@ import android.os.Message; * another level, a new SoundPool is created, sounds are loaded, and play * resumes.</p> */ -public class SoundPool -{ - static { System.loadLibrary("soundpool"); } - - private final static String TAG = "SoundPool"; - private final static boolean DEBUG = false; - - private int mNativeContext; // accessed by native methods - - private EventHandler mEventHandler; - private OnLoadCompleteListener mOnLoadCompleteListener; - - private final Object mLock; - - // SoundPool messages - // - // must match SoundPool.h - private static final int SAMPLE_LOADED = 1; +public class SoundPool { + private final SoundPoolDelegate mImpl; /** * Constructor. Constructs a SoundPool object with the following @@ -135,12 +121,11 @@ public class SoundPool * @return a SoundPool object, or null if creation failed */ public SoundPool(int maxStreams, int streamType, int srcQuality) { - - // do native setup - if (native_setup(new WeakReference(this), maxStreams, streamType, srcQuality) != 0) { - throw new RuntimeException("Native setup failed"); + if (SystemProperties.getBoolean("config.disable_media", false)) { + mImpl = new SoundPoolStub(); + } else { + mImpl = new SoundPoolImpl(this, maxStreams, streamType, srcQuality); } - mLock = new Object(); } /** @@ -151,25 +136,8 @@ public class SoundPool * a value of 1 for future compatibility. * @return a sound ID. This value can be used to play or unload the sound. */ - 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); - ParcelFileDescriptor fd = ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY); - if (fd != null) { - id = _load(fd.getFileDescriptor(), 0, f.length(), priority); - fd.close(); - } - } catch (java.io.IOException e) { - Log.e(TAG, "error loading " + path); - } - return id; + public int load(String path, int priority) { + return mImpl.load(path, priority); } /** @@ -188,17 +156,7 @@ public class SoundPool * @return a sound ID. This value can be used to play or unload the sound. */ 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 { - afd.close(); - } catch (java.io.IOException ex) { - //Log.d(TAG, "close failed:", ex); - } - } - return id; + return mImpl.load(context, resId, priority); } /** @@ -210,15 +168,7 @@ public class SoundPool * @return a sound ID. This value can be used to play or unload the sound. */ public int load(AssetFileDescriptor afd, int priority) { - if (afd != null) { - long len = afd.getLength(); - if (len < 0) { - throw new AndroidRuntimeException("no length for fd"); - } - return _load(afd.getFileDescriptor(), afd.getStartOffset(), len, priority); - } else { - return 0; - } + return mImpl.load(afd, priority); } /** @@ -236,13 +186,9 @@ public class SoundPool * @return a sound ID. This value can be used to play or unload the sound. */ public int load(FileDescriptor fd, long offset, long length, int priority) { - return _load(fd, offset, length, priority); + return mImpl.load(fd, offset, length, priority); } - private native final int _load(String uri, int priority); - - private native final int _load(FileDescriptor fd, long offset, long length, int priority); - /** * Unload a sound from a sound ID. * @@ -253,7 +199,9 @@ public class SoundPool * @param soundID a soundID returned by the load() function * @return true if just unloaded, false if previously unloaded */ - public native final boolean unload(int soundID); + public final boolean unload(int soundID) { + return mImpl.unload(soundID); + } /** * Play a sound from a sound ID. @@ -279,8 +227,11 @@ public class SoundPool * @param rate playback rate (1.0 = normal playback, range 0.5 to 2.0) * @return non-zero streamID if successful, zero if failed */ - public native final int play(int soundID, float leftVolume, float rightVolume, - int priority, int loop, float rate); + public final int play(int soundID, float leftVolume, float rightVolume, + int priority, int loop, float rate) { + return mImpl.play( + soundID, leftVolume, rightVolume, priority, loop, rate); + } /** * Pause a playback stream. @@ -293,7 +244,9 @@ public class SoundPool * * @param streamID a streamID returned by the play() function */ - public native final void pause(int streamID); + public final void pause(int streamID) { + mImpl.pause(streamID); + } /** * Resume a playback stream. @@ -305,7 +258,9 @@ public class SoundPool * * @param streamID a streamID returned by the play() function */ - public native final void resume(int streamID); + public final void resume(int streamID) { + mImpl.resume(streamID); + } /** * Pause all active streams. @@ -315,7 +270,9 @@ public class SoundPool * are playing. It also sets a flag so that any streams that * are playing can be resumed by calling autoResume(). */ - public native final void autoPause(); + public final void autoPause() { + mImpl.autoPause(); + } /** * Resume all previously active streams. @@ -323,7 +280,9 @@ public class SoundPool * Automatically resumes all streams that were paused in previous * calls to autoPause(). */ - public native final void autoResume(); + public final void autoResume() { + mImpl.autoResume(); + } /** * Stop a playback stream. @@ -336,7 +295,9 @@ public class SoundPool * * @param streamID a streamID returned by the play() function */ - public native final void stop(int streamID); + public final void stop(int streamID) { + mImpl.stop(streamID); + } /** * Set stream volume. @@ -350,8 +311,10 @@ public class SoundPool * @param leftVolume left volume value (range = 0.0 to 1.0) * @param rightVolume right volume value (range = 0.0 to 1.0) */ - public native final void setVolume(int streamID, - float leftVolume, float rightVolume); + public final void setVolume(int streamID, + float leftVolume, float rightVolume) { + mImpl.setVolume(streamID, leftVolume, rightVolume); + } /** * Similar, except set volume of all channels to same value. @@ -371,7 +334,9 @@ public class SoundPool * * @param streamID a streamID returned by the play() function */ - public native final void setPriority(int streamID, int priority); + public final void setPriority(int streamID, int priority) { + mImpl.setPriority(streamID, priority); + } /** * Set loop mode. @@ -384,7 +349,9 @@ public class SoundPool * @param streamID a streamID returned by the play() function * @param loop loop mode (0 = no loop, -1 = loop forever) */ - public native final void setLoop(int streamID, int loop); + public final void setLoop(int streamID, int loop) { + mImpl.setLoop(streamID, loop); + } /** * Change playback rate. @@ -398,19 +365,16 @@ public class SoundPool * @param streamID a streamID returned by the play() function * @param rate playback rate (1.0 = normal playback, range 0.5 to 2.0) */ - public native final void setRate(int streamID, float rate); + public final void setRate(int streamID, float rate) { + mImpl.setRate(streamID, rate); + } - /** - * Interface definition for a callback to be invoked when all the - * sounds are loaded. - */ - public interface OnLoadCompleteListener - { + public interface OnLoadCompleteListener { /** * Called when a sound has completed loading. * * @param soundPool SoundPool object from the load() method - * @param soundPool the sample ID of the sound loaded. + * @param sampleId the sample ID of the sound loaded. * @param status the status of the load operation (0 = success) */ public void onLoadComplete(SoundPool soundPool, int sampleId, int status); @@ -419,76 +383,297 @@ public class SoundPool /** * Sets the callback hook for the OnLoadCompleteListener. */ - public void setOnLoadCompleteListener(OnLoadCompleteListener listener) - { - synchronized(mLock) { - if (listener != null) { - // setup message handler - 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; + public void setOnLoadCompleteListener(OnLoadCompleteListener listener) { + mImpl.setOnLoadCompleteListener(listener); + } + + /** + * Release the SoundPool resources. + * + * Release all memory and native resources used by the SoundPool + * object. The SoundPool can no longer be used and the reference + * should be set to null. + */ + public final void release() { + mImpl.release(); + } + + /** + * Interface for SoundPool implementations. + * SoundPool is statically referenced and unconditionally called from all + * over the framework, so we can't simply omit the class or make it throw + * runtime exceptions, as doing so would break the framework. Instead we + * now select either a real or no-op impl object based on whether media is + * enabled. + * + * @hide + */ + /* package */ interface SoundPoolDelegate { + public int load(String path, int priority); + public int load(Context context, int resId, int priority); + public int load(AssetFileDescriptor afd, int priority); + public int load( + FileDescriptor fd, long offset, long length, int priority); + public boolean unload(int soundID); + public int play( + int soundID, float leftVolume, float rightVolume, + int priority, int loop, float rate); + public void pause(int streamID); + public void resume(int streamID); + public void autoPause(); + public void autoResume(); + public void stop(int streamID); + public void setVolume(int streamID, float leftVolume, float rightVolume); + public void setVolume(int streamID, float volume); + public void setPriority(int streamID, int priority); + public void setLoop(int streamID, int loop); + public void setRate(int streamID, float rate); + public void setOnLoadCompleteListener(OnLoadCompleteListener listener); + public void release(); + } + + + /** + * Real implementation of the delegate interface. This was formerly the + * body of SoundPool itself. + */ + /* package */ static class SoundPoolImpl implements SoundPoolDelegate { + static { System.loadLibrary("soundpool"); } + + private final static String TAG = "SoundPool"; + private final static boolean DEBUG = false; + + private int mNativeContext; // accessed by native methods + + private EventHandler mEventHandler; + private SoundPool.OnLoadCompleteListener mOnLoadCompleteListener; + private SoundPool mProxy; + + private final Object mLock; + + // SoundPool messages + // + // must match SoundPool.h + private static final int SAMPLE_LOADED = 1; + + public SoundPoolImpl(SoundPool proxy, int maxStreams, int streamType, int srcQuality) { + + // do native setup + if (native_setup(new WeakReference(this), maxStreams, streamType, srcQuality) != 0) { + throw new RuntimeException("Native setup failed"); + } + mLock = new Object(); + mProxy = proxy; + } + + 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); + ParcelFileDescriptor fd = ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY); + if (fd != null) { + id = _load(fd.getFileDescriptor(), 0, f.length(), priority); + fd.close(); } + } catch (java.io.IOException e) { + Log.e(TAG, "error loading " + path); + } + 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 { + afd.close(); + } catch (java.io.IOException ex) { + //Log.d(TAG, "close failed:", ex); + } + } + return id; + } + + public int load(AssetFileDescriptor afd, int priority) { + if (afd != null) { + long len = afd.getLength(); + if (len < 0) { + throw new AndroidRuntimeException("no length for fd"); + } + return _load(afd.getFileDescriptor(), afd.getStartOffset(), len, priority); } else { - mEventHandler = null; + return 0; } - mOnLoadCompleteListener = listener; } - } - private class EventHandler extends Handler - { - private SoundPool mSoundPool; + public int load(FileDescriptor fd, long offset, long length, int priority) { + return _load(fd, offset, length, priority); + } + + private native final int _load(String uri, int priority); + + private native final int _load(FileDescriptor fd, long offset, long length, int priority); + + public native final boolean unload(int soundID); + + public native final int play(int soundID, float leftVolume, float rightVolume, + int priority, int loop, float rate); + + public native final void pause(int streamID); + + public native final void resume(int streamID); + + public native final void autoPause(); + + public native final void autoResume(); + + public native final void stop(int streamID); - public EventHandler(SoundPool soundPool, Looper looper) { - super(looper); - mSoundPool = soundPool; + public native final void setVolume(int streamID, + float leftVolume, float rightVolume); + + public void setVolume(int streamID, float volume) { + setVolume(streamID, volume, volume); } - @Override - public void handleMessage(Message msg) { - switch(msg.what) { - case SAMPLE_LOADED: - if (DEBUG) Log.d(TAG, "Sample " + msg.arg1 + " loaded"); - synchronized(mLock) { - if (mOnLoadCompleteListener != null) { - mOnLoadCompleteListener.onLoadComplete(mSoundPool, msg.arg1, msg.arg2); + 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 void setOnLoadCompleteListener(SoundPool.OnLoadCompleteListener listener) + { + synchronized(mLock) { + if (listener != null) { + // setup message handler + Looper looper; + if ((looper = Looper.myLooper()) != null) { + mEventHandler = new EventHandler(mProxy, looper); + } else if ((looper = Looper.getMainLooper()) != null) { + mEventHandler = new EventHandler(mProxy, looper); + } else { + mEventHandler = null; } + } else { + mEventHandler = null; } - break; - default: - Log.e(TAG, "Unknown message type " + msg.what); - return; + mOnLoadCompleteListener = listener; } } - } - // post event from native code to message handler - private static void postEventFromNative(Object weakRef, int msg, int arg1, int arg2, Object obj) - { - SoundPool soundPool = (SoundPool)((WeakReference)weakRef).get(); - if (soundPool == null) - return; + private class EventHandler extends Handler + { + private SoundPool mSoundPool; + + public EventHandler(SoundPool soundPool, Looper looper) { + super(looper); + mSoundPool = soundPool; + } - if (soundPool.mEventHandler != null) { - Message m = soundPool.mEventHandler.obtainMessage(msg, arg1, arg2, obj); - soundPool.mEventHandler.sendMessage(m); + @Override + public void handleMessage(Message msg) { + switch(msg.what) { + case SAMPLE_LOADED: + if (DEBUG) Log.d(TAG, "Sample " + msg.arg1 + " loaded"); + synchronized(mLock) { + if (mOnLoadCompleteListener != null) { + mOnLoadCompleteListener.onLoadComplete(mSoundPool, msg.arg1, msg.arg2); + } + } + break; + default: + Log.e(TAG, "Unknown message type " + msg.what); + return; + } + } } + + // post event from native code to message handler + private static void postEventFromNative(Object weakRef, int msg, int arg1, int arg2, Object obj) + { + SoundPoolImpl soundPoolImpl = (SoundPoolImpl)((WeakReference)weakRef).get(); + if (soundPoolImpl == null) + return; + + if (soundPoolImpl.mEventHandler != null) { + Message m = soundPoolImpl.mEventHandler.obtainMessage(msg, arg1, arg2, obj); + soundPoolImpl.mEventHandler.sendMessage(m); + } + } + + public native final void release(); + + private native final int native_setup(Object weakRef, int maxStreams, int streamType, int srcQuality); + + protected void finalize() { release(); } } /** - * Release the SoundPool resources. - * - * Release all memory and native resources used by the SoundPool - * object. The SoundPool can no longer be used and the reference - * should be set to null. + * No-op implementation of SoundPool. + * Used when media is disabled by the system. + * @hide */ - public native final void release(); + /* package */ static class SoundPoolStub implements SoundPoolDelegate { + public SoundPoolStub() { } + + public int load(String path, int priority) { + return 0; + } + + public int load(Context context, int resId, int priority) { + return 0; + } + + public int load(AssetFileDescriptor afd, int priority) { + return 0; + } + + public int load(FileDescriptor fd, long offset, long length, int priority) { + return 0; + } + + public final boolean unload(int soundID) { + return true; + } + + public final int play(int soundID, float leftVolume, float rightVolume, + int priority, int loop, float rate) { + return 0; + } + + public final void pause(int streamID) { } + + public final void resume(int streamID) { } + + public final void autoPause() { } + + public final void autoResume() { } - private native final int native_setup(Object weakRef, int maxStreams, int streamType, int srcQuality); + public final void stop(int streamID) { } - protected void finalize() { release(); } + public final void setVolume(int streamID, + float leftVolume, float rightVolume) { } + + public void setVolume(int streamID, float volume) { + } + + public final void setPriority(int streamID, int priority) { } + + public final void setLoop(int streamID, int loop) { } + + public final void setRate(int streamID, float rate) { } + + public void setOnLoadCompleteListener(SoundPool.OnLoadCompleteListener listener) { + } + + public final void release() { } + } } diff --git a/media/java/android/media/SubtitleController.java b/media/java/android/media/SubtitleController.java new file mode 100644 index 0000000..13205bc --- /dev/null +++ b/media/java/android/media/SubtitleController.java @@ -0,0 +1,492 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media; + +import java.util.Locale; +import java.util.Vector; + +import android.content.Context; +import android.media.SubtitleTrack.RenderingWidget; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.view.accessibility.CaptioningManager; + +/** + * The subtitle controller provides the architecture to display subtitles for a + * media source. It allows specifying which tracks to display, on which anchor + * to display them, and also allows adding external, out-of-band subtitle tracks. + * + * @hide + */ +public class SubtitleController { + private MediaTimeProvider mTimeProvider; + private Vector<Renderer> mRenderers; + private Vector<SubtitleTrack> mTracks; + private SubtitleTrack mSelectedTrack; + private boolean mShowing; + private CaptioningManager mCaptioningManager; + private Handler mHandler; + + private static final int WHAT_SHOW = 1; + private static final int WHAT_HIDE = 2; + private static final int WHAT_SELECT_TRACK = 3; + private static final int WHAT_SELECT_DEFAULT_TRACK = 4; + + private final Handler.Callback mCallback = new Handler.Callback() { + @Override + public boolean handleMessage(Message msg) { + switch (msg.what) { + case WHAT_SHOW: + doShow(); + return true; + case WHAT_HIDE: + doHide(); + return true; + case WHAT_SELECT_TRACK: + doSelectTrack((SubtitleTrack)msg.obj); + return true; + case WHAT_SELECT_DEFAULT_TRACK: + doSelectDefaultTrack(); + return true; + default: + return false; + } + } + }; + + private CaptioningManager.CaptioningChangeListener mCaptioningChangeListener = + new CaptioningManager.CaptioningChangeListener() { + /** @hide */ + @Override + public void onEnabledChanged(boolean enabled) { + selectDefaultTrack(); + } + + /** @hide */ + @Override + public void onLocaleChanged(Locale locale) { + selectDefaultTrack(); + } + }; + + /** + * Creates a subtitle controller for a media playback object that implements + * the MediaTimeProvider interface. + * + * @param timeProvider + */ + public SubtitleController( + Context context, + MediaTimeProvider timeProvider, + Listener listener) { + mTimeProvider = timeProvider; + mListener = listener; + + mRenderers = new Vector<Renderer>(); + mShowing = false; + mTracks = new Vector<SubtitleTrack>(); + mCaptioningManager = + (CaptioningManager)context.getSystemService(Context.CAPTIONING_SERVICE); + } + + @Override + protected void finalize() throws Throwable { + mCaptioningManager.removeCaptioningChangeListener( + mCaptioningChangeListener); + super.finalize(); + } + + /** + * @return the available subtitle tracks for this media. These include + * the tracks found by {@link MediaPlayer} as well as any tracks added + * manually via {@link #addTrack}. + */ + public SubtitleTrack[] getTracks() { + synchronized(mTracks) { + SubtitleTrack[] tracks = new SubtitleTrack[mTracks.size()]; + mTracks.toArray(tracks); + return tracks; + } + } + + /** + * @return the currently selected subtitle track + */ + public SubtitleTrack getSelectedTrack() { + return mSelectedTrack; + } + + private RenderingWidget getRenderingWidget() { + if (mSelectedTrack == null) { + return null; + } + return mSelectedTrack.getRenderingWidget(); + } + + /** + * Selects a subtitle track. As a result, this track will receive + * in-band data from the {@link MediaPlayer}. However, this does + * not change the subtitle visibility. + * + * Should be called from the anchor's (UI) thread. {@see #Anchor.getSubtitleLooper} + * + * @param track The subtitle track to select. This must be one of the + * tracks in {@link #getTracks}. + * @return true if the track was successfully selected. + */ + public boolean selectTrack(SubtitleTrack track) { + if (track != null && !mTracks.contains(track)) { + return false; + } + + processOnAnchor(mHandler.obtainMessage(WHAT_SELECT_TRACK, track)); + return true; + } + + private void doSelectTrack(SubtitleTrack track) { + mTrackIsExplicit = true; + if (mSelectedTrack == track) { + return; + } + + if (mSelectedTrack != null) { + mSelectedTrack.hide(); + mSelectedTrack.setTimeProvider(null); + } + + mSelectedTrack = track; + if (mAnchor != null) { + mAnchor.setSubtitleWidget(getRenderingWidget()); + } + + if (mSelectedTrack != null) { + mSelectedTrack.setTimeProvider(mTimeProvider); + mSelectedTrack.show(); + } + + if (mListener != null) { + mListener.onSubtitleTrackSelected(track); + } + } + + /** + * @return the default subtitle track based on system preferences, or null, + * if no such track exists in this manager. + * + * Supports HLS-flags: AUTOSELECT, FORCED & DEFAULT. + * + * 1. If captioning is disabled, only consider FORCED tracks. Otherwise, + * consider all tracks, but prefer non-FORCED ones. + * 2. If user selected "Default" caption language: + * a. If there is a considered track with DEFAULT=yes, returns that track + * (favor the first one in the current language if there are more than + * one default tracks, or the first in general if none of them are in + * the current language). + * b. Otherwise, if there is a track with AUTOSELECT=yes in the current + * language, return that one. + * c. If there are no default tracks, and no autoselectable tracks in the + * current language, return null. + * 3. If there is a track with the caption language, select that one. Prefer + * the one with AUTOSELECT=no. + * + * The default values for these flags are DEFAULT=no, AUTOSELECT=yes + * and FORCED=no. + */ + public SubtitleTrack getDefaultTrack() { + SubtitleTrack bestTrack = null; + int bestScore = -1; + + Locale selectedLocale = mCaptioningManager.getLocale(); + Locale locale = selectedLocale; + if (locale == null) { + locale = Locale.getDefault(); + } + boolean selectForced = !mCaptioningManager.isEnabled(); + + synchronized(mTracks) { + for (SubtitleTrack track: mTracks) { + MediaFormat format = track.getFormat(); + String language = format.getString(MediaFormat.KEY_LANGUAGE); + boolean forced = + format.getInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE, 0) != 0; + boolean autoselect = + format.getInteger(MediaFormat.KEY_IS_AUTOSELECT, 1) != 0; + boolean is_default = + format.getInteger(MediaFormat.KEY_IS_DEFAULT, 0) != 0; + + boolean languageMatches = + (locale == null || + locale.getLanguage().equals("") || + locale.getISO3Language().equals(language) || + locale.getLanguage().equals(language)); + // is_default is meaningless unless caption language is 'default' + int score = (forced ? 0 : 8) + + (((selectedLocale == null) && is_default) ? 4 : 0) + + (autoselect ? 0 : 2) + (languageMatches ? 1 : 0); + + if (selectForced && !forced) { + continue; + } + + // we treat null locale/language as matching any language + if ((selectedLocale == null && is_default) || + (languageMatches && + (autoselect || forced || selectedLocale != null))) { + if (score > bestScore) { + bestScore = score; + bestTrack = track; + } + } + } + } + return bestTrack; + } + + private boolean mTrackIsExplicit = false; + private boolean mVisibilityIsExplicit = false; + + /** @hide - should be called from anchor thread */ + public void selectDefaultTrack() { + processOnAnchor(mHandler.obtainMessage(WHAT_SELECT_DEFAULT_TRACK)); + } + + private void doSelectDefaultTrack() { + if (mTrackIsExplicit) { + // If track selection is explicit, but visibility + // is not, it falls back to the captioning setting + if (!mVisibilityIsExplicit) { + if (mCaptioningManager.isEnabled() || + (mSelectedTrack != null && + mSelectedTrack.getFormat().getInteger( + MediaFormat.KEY_IS_FORCED_SUBTITLE, 0) != 0)) { + show(); + } else { + hide(); + } + mVisibilityIsExplicit = false; + } + return; + } + + // We can have a default (forced) track even if captioning + // is not enabled. This is handled by getDefaultTrack(). + // Show this track unless subtitles were explicitly hidden. + SubtitleTrack track = getDefaultTrack(); + if (track != null) { + selectTrack(track); + mTrackIsExplicit = false; + if (!mVisibilityIsExplicit) { + show(); + mVisibilityIsExplicit = false; + } + } + } + + /** @hide - must be called from anchor thread */ + public void reset() { + checkAnchorLooper(); + hide(); + selectTrack(null); + mTracks.clear(); + mTrackIsExplicit = false; + mVisibilityIsExplicit = false; + mCaptioningManager.removeCaptioningChangeListener( + mCaptioningChangeListener); + } + + /** + * Adds a new, external subtitle track to the manager. + * + * @param format the format of the track that will include at least + * the MIME type {@link MediaFormat@KEY_MIME}. + * @return the created {@link SubtitleTrack} object + */ + public SubtitleTrack addTrack(MediaFormat format) { + synchronized(mRenderers) { + for (Renderer renderer: mRenderers) { + if (renderer.supports(format)) { + SubtitleTrack track = renderer.createTrack(format); + if (track != null) { + synchronized(mTracks) { + if (mTracks.size() == 0) { + mCaptioningManager.addCaptioningChangeListener( + mCaptioningChangeListener); + } + mTracks.add(track); + } + return track; + } + } + } + } + return null; + } + + /** + * Show the selected (or default) subtitle track. + * + * Should be called from the anchor's (UI) thread. {@see #Anchor.getSubtitleLooper} + */ + public void show() { + processOnAnchor(mHandler.obtainMessage(WHAT_SHOW)); + } + + private void doShow() { + mShowing = true; + mVisibilityIsExplicit = true; + if (mSelectedTrack != null) { + mSelectedTrack.show(); + } + } + + /** + * Hide the selected (or default) subtitle track. + * + * Should be called from the anchor's (UI) thread. {@see #Anchor.getSubtitleLooper} + */ + public void hide() { + processOnAnchor(mHandler.obtainMessage(WHAT_HIDE)); + } + + private void doHide() { + mVisibilityIsExplicit = true; + if (mSelectedTrack != null) { + mSelectedTrack.hide(); + } + mShowing = false; + } + + /** + * Interface for supporting a single or multiple subtitle types in {@link + * MediaPlayer}. + */ + public abstract static class Renderer { + /** + * Called by {@link MediaPlayer}'s {@link SubtitleController} when a new + * subtitle track is detected, to see if it should use this object to + * parse and display this subtitle track. + * + * @param format the format of the track that will include at least + * the MIME type {@link MediaFormat@KEY_MIME}. + * + * @return true if and only if the track format is supported by this + * renderer + */ + public abstract boolean supports(MediaFormat format); + + /** + * Called by {@link MediaPlayer}'s {@link SubtitleController} for each + * subtitle track that was detected and is supported by this object to + * create a {@link SubtitleTrack} object. This object will be created + * for each track that was found. If the track is selected for display, + * this object will be used to parse and display the track data. + * + * @param format the format of the track that will include at least + * the MIME type {@link MediaFormat@KEY_MIME}. + * @return a {@link SubtitleTrack} object that will be used to parse + * and render the subtitle track. + */ + public abstract SubtitleTrack createTrack(MediaFormat format); + } + + /** + * Add support for a subtitle format in {@link MediaPlayer}. + * + * @param renderer a {@link SubtitleController.Renderer} object that adds + * support for a subtitle format. + */ + public void registerRenderer(Renderer renderer) { + synchronized(mRenderers) { + // TODO how to get available renderers in the system + if (!mRenderers.contains(renderer)) { + // TODO should added renderers override existing ones (to allow replacing?) + mRenderers.add(renderer); + } + } + } + + /** + * Subtitle anchor, an object that is able to display a subtitle renderer, + * e.g. a VideoView. + */ + public interface Anchor { + /** + * Anchor should use the supplied subtitle rendering widget, or + * none if it is null. + * @hide + */ + public void setSubtitleWidget(RenderingWidget subtitleWidget); + + /** + * Anchors provide the looper on which all track visibility changes + * (track.show/hide, setSubtitleWidget) will take place. + * @hide + */ + public Looper getSubtitleLooper(); + } + + private Anchor mAnchor; + + /** + * @hide - called from anchor's looper (if any, both when unsetting and + * setting) + */ + public void setAnchor(Anchor anchor) { + if (mAnchor == anchor) { + return; + } + + if (mAnchor != null) { + checkAnchorLooper(); + mAnchor.setSubtitleWidget(null); + } + mAnchor = anchor; + mHandler = null; + if (mAnchor != null) { + mHandler = new Handler(mAnchor.getSubtitleLooper(), mCallback); + checkAnchorLooper(); + mAnchor.setSubtitleWidget(getRenderingWidget()); + } + } + + private void checkAnchorLooper() { + assert mHandler != null : "Should have a looper already"; + assert Looper.myLooper() == mHandler.getLooper() : "Must be called from the anchor's looper"; + } + + private void processOnAnchor(Message m) { + assert mHandler != null : "Should have a looper already"; + if (Looper.myLooper() == mHandler.getLooper()) { + mHandler.dispatchMessage(m); + } else { + mHandler.sendMessage(m); + } + } + + public interface Listener { + /** + * Called when a subtitle track has been selected. + * + * @param track selected subtitle track or null + * @hide + */ + public void onSubtitleTrackSelected(SubtitleTrack track); + } + + private Listener mListener; +} diff --git a/media/java/android/media/SubtitleData.java b/media/java/android/media/SubtitleData.java new file mode 100644 index 0000000..f552e82 --- /dev/null +++ b/media/java/android/media/SubtitleData.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2011 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.os.Parcel; +import android.util.Log; + +/** + * @hide + * + * Class to hold the subtitle track's data, including: + * <ul> + * <li> Track index</li> + * <li> Start time (in microseconds) of the data</li> + * <li> Duration (in microseconds) of the data</li> + * <li> A byte-array of the data</li> + * </ul> + * + * <p> To receive the subtitle data, applications need to do the following: + * + * <ul> + * <li> Select a track of type MEDIA_TRACK_TYPE_SUBTITLE with {@link MediaPlayer.selectTrack(int)</li> + * <li> Implement the {@link MediaPlayer.OnSubtitleDataListener} interface</li> + * <li> Register the {@link MediaPlayer.OnSubtitleDataListener} callback on a MediaPlayer object</li> + * </ul> + * + * @see android.media.MediaPlayer + */ +public final class SubtitleData +{ + private static final String TAG = "SubtitleData"; + + private int mTrackIndex; + private long mStartTimeUs; + private long mDurationUs; + private byte[] mData; + + public SubtitleData(Parcel parcel) { + if (!parseParcel(parcel)) { + throw new IllegalArgumentException("parseParcel() fails"); + } + } + + public int getTrackIndex() { + return mTrackIndex; + } + + public long getStartTimeUs() { + return mStartTimeUs; + } + + public long getDurationUs() { + return mDurationUs; + } + + public byte[] getData() { + return mData; + } + + private boolean parseParcel(Parcel parcel) { + parcel.setDataPosition(0); + if (parcel.dataAvail() == 0) { + return false; + } + + mTrackIndex = parcel.readInt(); + mStartTimeUs = parcel.readLong(); + mDurationUs = parcel.readLong(); + mData = new byte[parcel.readInt()]; + parcel.readByteArray(mData); + + return true; + } +} diff --git a/media/java/android/media/SubtitleTrack.java b/media/java/android/media/SubtitleTrack.java new file mode 100644 index 0000000..06063de --- /dev/null +++ b/media/java/android/media/SubtitleTrack.java @@ -0,0 +1,705 @@ +/* + * Copyright (C) 2013 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.Canvas; +import android.os.Handler; +import android.util.Log; +import android.util.LongSparseArray; +import android.util.Pair; + +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.Vector; + +/** + * A subtitle track abstract base class that is responsible for parsing and displaying + * an instance of a particular type of subtitle. + * + * @hide + */ +public abstract class SubtitleTrack implements MediaTimeProvider.OnMediaTimeListener { + private static final String TAG = "SubtitleTrack"; + private long mLastUpdateTimeMs; + private long mLastTimeMs; + + private Runnable mRunnable; + + /** @hide TODO private */ + final protected LongSparseArray<Run> mRunsByEndTime = new LongSparseArray<Run>(); + /** @hide TODO private */ + final protected LongSparseArray<Run> mRunsByID = new LongSparseArray<Run>(); + + /** @hide TODO private */ + protected CueList mCues; + /** @hide TODO private */ + final protected Vector<Cue> mActiveCues = new Vector<Cue>(); + /** @hide */ + protected boolean mVisible; + + /** @hide */ + public boolean DEBUG = false; + + /** @hide */ + protected Handler mHandler = new Handler(); + + private MediaFormat mFormat; + + public SubtitleTrack(MediaFormat format) { + mFormat = format; + mCues = new CueList(); + clearActiveCues(); + mLastTimeMs = -1; + } + + /** @hide */ + public final MediaFormat getFormat() { + return mFormat; + } + + private long mNextScheduledTimeMs = -1; + + /** + * Called when there is input data for the subtitle track. The + * complete subtitle for a track can include multiple whole units + * (runs). Each of these units can have multiple sections. The + * contents of a run are submitted in sequential order, with eos + * indicating the last section of the run. Calls from different + * runs must not be intermixed. + * + * @param data + * @param eos true if this is the last section of the run. + * @param runID mostly-unique ID for this run of data. Subtitle cues + * with runID of 0 are discarded immediately after + * display. Cues with runID of ~0 are discarded + * only at the deletion of the track object. Cues + * with other runID-s are discarded at the end of the + * run, which defaults to the latest timestamp of + * any of its cues (with this runID). + * + * TODO use ByteBuffer + */ + public abstract void onData(String data, boolean eos, long runID); + + /** + * Called when adding the subtitle rendering widget to the view hierarchy, + * as well as when showing or hiding the subtitle track, or when the video + * surface position has changed. + * + * @return the widget that renders this subtitle track. For most renderers + * there should be a single shared instance that is used for all + * tracks supported by that renderer, as at most one subtitle track + * is visible at one time. + */ + public abstract RenderingWidget getRenderingWidget(); + + /** + * Called when the active cues have changed, and the contents of the subtitle + * view should be updated. + * + * @hide + */ + public abstract void updateView(Vector<Cue> activeCues); + + /** @hide */ + protected synchronized void updateActiveCues(boolean rebuild, long timeMs) { + // out-of-order times mean seeking or new active cues being added + // (during their own timespan) + if (rebuild || mLastUpdateTimeMs > timeMs) { + clearActiveCues(); + } + + for(Iterator<Pair<Long, Cue> > it = + mCues.entriesBetween(mLastUpdateTimeMs, timeMs).iterator(); it.hasNext(); ) { + Pair<Long, Cue> event = it.next(); + Cue cue = event.second; + + if (cue.mEndTimeMs == event.first) { + // remove past cues + if (DEBUG) Log.v(TAG, "Removing " + cue); + mActiveCues.remove(cue); + if (cue.mRunID == 0) { + it.remove(); + } + } else if (cue.mStartTimeMs == event.first) { + // add new cues + // TRICKY: this will happen in start order + if (DEBUG) Log.v(TAG, "Adding " + cue); + if (cue.mInnerTimesMs != null) { + cue.onTime(timeMs); + } + mActiveCues.add(cue); + } else if (cue.mInnerTimesMs != null) { + // cue is modified + cue.onTime(timeMs); + } + } + + /* complete any runs */ + while (mRunsByEndTime.size() > 0 && + mRunsByEndTime.keyAt(0) <= timeMs) { + removeRunsByEndTimeIndex(0); // removes element + } + mLastUpdateTimeMs = timeMs; + } + + private void removeRunsByEndTimeIndex(int ix) { + Run run = mRunsByEndTime.valueAt(ix); + while (run != null) { + Cue cue = run.mFirstCue; + while (cue != null) { + mCues.remove(cue); + Cue nextCue = cue.mNextInRun; + cue.mNextInRun = null; + cue = nextCue; + } + mRunsByID.remove(run.mRunID); + Run nextRun = run.mNextRunAtEndTimeMs; + run.mPrevRunAtEndTimeMs = null; + run.mNextRunAtEndTimeMs = null; + run = nextRun; + } + mRunsByEndTime.removeAt(ix); + } + + @Override + protected void finalize() throws Throwable { + /* remove all cues (untangle all cross-links) */ + int size = mRunsByEndTime.size(); + for(int ix = size - 1; ix >= 0; ix--) { + removeRunsByEndTimeIndex(ix); + } + + super.finalize(); + } + + private synchronized void takeTime(long timeMs) { + mLastTimeMs = timeMs; + } + + /** @hide */ + protected synchronized void clearActiveCues() { + if (DEBUG) Log.v(TAG, "Clearing " + mActiveCues.size() + " active cues"); + mActiveCues.clear(); + mLastUpdateTimeMs = -1; + } + + /** @hide */ + protected void scheduleTimedEvents() { + /* get times for the next event */ + if (mTimeProvider != null) { + mNextScheduledTimeMs = mCues.nextTimeAfter(mLastTimeMs); + if (DEBUG) Log.d(TAG, "sched @" + mNextScheduledTimeMs + " after " + mLastTimeMs); + mTimeProvider.notifyAt( + mNextScheduledTimeMs >= 0 ? + (mNextScheduledTimeMs * 1000) : MediaTimeProvider.NO_TIME, + this); + } + } + + /** + * @hide + */ + @Override + public void onTimedEvent(long timeUs) { + if (DEBUG) Log.d(TAG, "onTimedEvent " + timeUs); + synchronized (this) { + long timeMs = timeUs / 1000; + updateActiveCues(false, timeMs); + takeTime(timeMs); + } + updateView(mActiveCues); + scheduleTimedEvents(); + } + + /** + * @hide + */ + @Override + public void onSeek(long timeUs) { + if (DEBUG) Log.d(TAG, "onSeek " + timeUs); + synchronized (this) { + long timeMs = timeUs / 1000; + updateActiveCues(true, timeMs); + takeTime(timeMs); + } + updateView(mActiveCues); + scheduleTimedEvents(); + } + + /** + * @hide + */ + @Override + public void onStop() { + synchronized (this) { + if (DEBUG) Log.d(TAG, "onStop"); + clearActiveCues(); + mLastTimeMs = -1; + } + updateView(mActiveCues); + mNextScheduledTimeMs = -1; + mTimeProvider.notifyAt(MediaTimeProvider.NO_TIME, this); + } + + /** @hide */ + protected MediaTimeProvider mTimeProvider; + + /** @hide */ + public void show() { + if (mVisible) { + return; + } + + mVisible = true; + getRenderingWidget().setVisible(true); + if (mTimeProvider != null) { + mTimeProvider.scheduleUpdate(this); + } + } + + /** @hide */ + public void hide() { + if (!mVisible) { + return; + } + + if (mTimeProvider != null) { + mTimeProvider.cancelNotifications(this); + } + getRenderingWidget().setVisible(false); + mVisible = false; + } + + /** @hide */ + protected synchronized boolean addCue(Cue cue) { + mCues.add(cue); + + if (cue.mRunID != 0) { + Run run = mRunsByID.get(cue.mRunID); + if (run == null) { + run = new Run(); + mRunsByID.put(cue.mRunID, run); + run.mEndTimeMs = cue.mEndTimeMs; + } else if (run.mEndTimeMs < cue.mEndTimeMs) { + run.mEndTimeMs = cue.mEndTimeMs; + } + + // link-up cues in the same run + cue.mNextInRun = run.mFirstCue; + run.mFirstCue = cue; + } + + // if a cue is added that should be visible, need to refresh view + long nowMs = -1; + if (mTimeProvider != null) { + try { + nowMs = mTimeProvider.getCurrentTimeUs( + false /* precise */, true /* monotonic */) / 1000; + } catch (IllegalStateException e) { + // handle as it we are not playing + } + } + + if (DEBUG) Log.v(TAG, "mVisible=" + mVisible + ", " + + cue.mStartTimeMs + " <= " + nowMs + ", " + + cue.mEndTimeMs + " >= " + mLastTimeMs); + + if (mVisible && + cue.mStartTimeMs <= nowMs && + // we don't trust nowMs, so check any cue since last callback + cue.mEndTimeMs >= mLastTimeMs) { + if (mRunnable != null) { + mHandler.removeCallbacks(mRunnable); + } + final SubtitleTrack track = this; + final long thenMs = nowMs; + mRunnable = new Runnable() { + @Override + public void run() { + // even with synchronized, it is possible that we are going + // to do multiple updates as the runnable could be already + // running. + synchronized (track) { + mRunnable = null; + updateActiveCues(true, thenMs); + updateView(mActiveCues); + } + } + }; + // delay update so we don't update view on every cue. TODO why 10? + if (mHandler.postDelayed(mRunnable, 10 /* delay */)) { + if (DEBUG) Log.v(TAG, "scheduling update"); + } else { + if (DEBUG) Log.w(TAG, "failed to schedule subtitle view update"); + } + return true; + } + + if (mVisible && + cue.mEndTimeMs >= mLastTimeMs && + (cue.mStartTimeMs < mNextScheduledTimeMs || + mNextScheduledTimeMs < 0)) { + scheduleTimedEvents(); + } + + return false; + } + + /** @hide */ + public synchronized void setTimeProvider(MediaTimeProvider timeProvider) { + if (mTimeProvider == timeProvider) { + return; + } + if (mTimeProvider != null) { + mTimeProvider.cancelNotifications(this); + } + mTimeProvider = timeProvider; + if (mTimeProvider != null) { + mTimeProvider.scheduleUpdate(this); + } + } + + + /** @hide */ + static class CueList { + private static final String TAG = "CueList"; + // simplistic, inefficient implementation + private SortedMap<Long, Vector<Cue> > mCues; + public boolean DEBUG = false; + + private boolean addEvent(Cue cue, long timeMs) { + Vector<Cue> cues = mCues.get(timeMs); + if (cues == null) { + cues = new Vector<Cue>(2); + mCues.put(timeMs, cues); + } else if (cues.contains(cue)) { + // do not duplicate cues + return false; + } + + cues.add(cue); + return true; + } + + private void removeEvent(Cue cue, long timeMs) { + Vector<Cue> cues = mCues.get(timeMs); + if (cues != null) { + cues.remove(cue); + if (cues.size() == 0) { + mCues.remove(timeMs); + } + } + } + + public void add(Cue cue) { + // ignore non-positive-duration cues + if (cue.mStartTimeMs >= cue.mEndTimeMs) + return; + + if (!addEvent(cue, cue.mStartTimeMs)) { + return; + } + + long lastTimeMs = cue.mStartTimeMs; + if (cue.mInnerTimesMs != null) { + for (long timeMs: cue.mInnerTimesMs) { + if (timeMs > lastTimeMs && timeMs < cue.mEndTimeMs) { + addEvent(cue, timeMs); + lastTimeMs = timeMs; + } + } + } + + addEvent(cue, cue.mEndTimeMs); + } + + public void remove(Cue cue) { + removeEvent(cue, cue.mStartTimeMs); + if (cue.mInnerTimesMs != null) { + for (long timeMs: cue.mInnerTimesMs) { + removeEvent(cue, timeMs); + } + } + removeEvent(cue, cue.mEndTimeMs); + } + + public Iterable<Pair<Long, Cue>> entriesBetween( + final long lastTimeMs, final long timeMs) { + return new Iterable<Pair<Long, Cue> >() { + @Override + public Iterator<Pair<Long, Cue> > iterator() { + if (DEBUG) Log.d(TAG, "slice (" + lastTimeMs + ", " + timeMs + "]="); + try { + return new EntryIterator( + mCues.subMap(lastTimeMs + 1, timeMs + 1)); + } catch(IllegalArgumentException e) { + return new EntryIterator(null); + } + } + }; + } + + public long nextTimeAfter(long timeMs) { + SortedMap<Long, Vector<Cue>> tail = null; + try { + tail = mCues.tailMap(timeMs + 1); + if (tail != null) { + return tail.firstKey(); + } else { + return -1; + } + } catch(IllegalArgumentException e) { + return -1; + } catch(NoSuchElementException e) { + return -1; + } + } + + class EntryIterator implements Iterator<Pair<Long, Cue> > { + @Override + public boolean hasNext() { + return !mDone; + } + + @Override + public Pair<Long, Cue> next() { + if (mDone) { + throw new NoSuchElementException(""); + } + mLastEntry = new Pair<Long, Cue>( + mCurrentTimeMs, mListIterator.next()); + mLastListIterator = mListIterator; + if (!mListIterator.hasNext()) { + nextKey(); + } + return mLastEntry; + } + + @Override + public void remove() { + // only allow removing end tags + if (mLastListIterator == null || + mLastEntry.second.mEndTimeMs != mLastEntry.first) { + throw new IllegalStateException(""); + } + + // remove end-cue + mLastListIterator.remove(); + mLastListIterator = null; + if (mCues.get(mLastEntry.first).size() == 0) { + mCues.remove(mLastEntry.first); + } + + // remove rest of the cues + Cue cue = mLastEntry.second; + removeEvent(cue, cue.mStartTimeMs); + if (cue.mInnerTimesMs != null) { + for (long timeMs: cue.mInnerTimesMs) { + removeEvent(cue, timeMs); + } + } + } + + public EntryIterator(SortedMap<Long, Vector<Cue> > cues) { + if (DEBUG) Log.v(TAG, cues + ""); + mRemainingCues = cues; + mLastListIterator = null; + nextKey(); + } + + private void nextKey() { + do { + try { + if (mRemainingCues == null) { + throw new NoSuchElementException(""); + } + mCurrentTimeMs = mRemainingCues.firstKey(); + mListIterator = + mRemainingCues.get(mCurrentTimeMs).iterator(); + try { + mRemainingCues = + mRemainingCues.tailMap(mCurrentTimeMs + 1); + } catch (IllegalArgumentException e) { + mRemainingCues = null; + } + mDone = false; + } catch (NoSuchElementException e) { + mDone = true; + mRemainingCues = null; + mListIterator = null; + return; + } + } while (!mListIterator.hasNext()); + } + + private long mCurrentTimeMs; + private Iterator<Cue> mListIterator; + private boolean mDone; + private SortedMap<Long, Vector<Cue> > mRemainingCues; + private Iterator<Cue> mLastListIterator; + private Pair<Long,Cue> mLastEntry; + } + + CueList() { + mCues = new TreeMap<Long, Vector<Cue>>(); + } + } + + /** @hide */ + public static class Cue { + public long mStartTimeMs; + public long mEndTimeMs; + public long[] mInnerTimesMs; + public long mRunID; + + /** @hide */ + public Cue mNextInRun; + + public void onTime(long timeMs) { } + } + + /** @hide update mRunsByEndTime (with default end time) */ + protected void finishedRun(long runID) { + if (runID != 0 && runID != ~0) { + Run run = mRunsByID.get(runID); + if (run != null) { + run.storeByEndTimeMs(mRunsByEndTime); + } + } + } + + /** @hide update mRunsByEndTime with given end time */ + public void setRunDiscardTimeMs(long runID, long timeMs) { + if (runID != 0 && runID != ~0) { + Run run = mRunsByID.get(runID); + if (run != null) { + run.mEndTimeMs = timeMs; + run.storeByEndTimeMs(mRunsByEndTime); + } + } + } + + /** @hide */ + private static class Run { + public Cue mFirstCue; + public Run mNextRunAtEndTimeMs; + public Run mPrevRunAtEndTimeMs; + public long mEndTimeMs = -1; + public long mRunID = 0; + private long mStoredEndTimeMs = -1; + + public void storeByEndTimeMs(LongSparseArray<Run> runsByEndTime) { + // remove old value if any + int ix = runsByEndTime.indexOfKey(mStoredEndTimeMs); + if (ix >= 0) { + if (mPrevRunAtEndTimeMs == null) { + assert(this == runsByEndTime.valueAt(ix)); + if (mNextRunAtEndTimeMs == null) { + runsByEndTime.removeAt(ix); + } else { + runsByEndTime.setValueAt(ix, mNextRunAtEndTimeMs); + } + } + removeAtEndTimeMs(); + } + + // add new value + if (mEndTimeMs >= 0) { + mPrevRunAtEndTimeMs = null; + mNextRunAtEndTimeMs = runsByEndTime.get(mEndTimeMs); + if (mNextRunAtEndTimeMs != null) { + mNextRunAtEndTimeMs.mPrevRunAtEndTimeMs = this; + } + runsByEndTime.put(mEndTimeMs, this); + mStoredEndTimeMs = mEndTimeMs; + } + } + + public void removeAtEndTimeMs() { + Run prev = mPrevRunAtEndTimeMs; + + if (mPrevRunAtEndTimeMs != null) { + mPrevRunAtEndTimeMs.mNextRunAtEndTimeMs = mNextRunAtEndTimeMs; + mPrevRunAtEndTimeMs = null; + } + if (mNextRunAtEndTimeMs != null) { + mNextRunAtEndTimeMs.mPrevRunAtEndTimeMs = prev; + mNextRunAtEndTimeMs = null; + } + } + } + + /** + * Interface for rendering subtitles onto a Canvas. + */ + public interface RenderingWidget { + /** + * Sets the widget's callback, which is used to send updates when the + * rendered data has changed. + * + * @param callback update callback + */ + public void setOnChangedListener(OnChangedListener callback); + + /** + * Sets the widget's size. + * + * @param width width in pixels + * @param height height in pixels + */ + public void setSize(int width, int height); + + /** + * Sets whether the widget should draw subtitles. + * + * @param visible true if subtitles should be drawn, false otherwise + */ + public void setVisible(boolean visible); + + /** + * Renders subtitles onto a {@link Canvas}. + * + * @param c canvas on which to render subtitles + */ + public void draw(Canvas c); + + /** + * Called when the widget is attached to a window. + */ + public void onAttachedToWindow(); + + /** + * Called when the widget is detached from a window. + */ + public void onDetachedFromWindow(); + + /** + * Callback used to send updates about changes to rendering data. + */ + public interface OnChangedListener { + /** + * Called when the rendering data has changed. + * + * @param renderingWidget the widget whose data has changed + */ + public void onChanged(RenderingWidget renderingWidget); + } + } +} diff --git a/media/libdrm/mobile1/include/drm_common_types.h b/media/java/android/media/VolumeController.java index c6bea61..2d12bf2 100644 --- a/media/libdrm/mobile1/include/drm_common_types.h +++ b/media/java/android/media/VolumeController.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007 The Android Open Source Project + * Copyright (C) 2013 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. @@ -14,32 +14,16 @@ * limitations under the License. */ -#ifndef __COMMON_TYPES_H__ -#define __COMMON_TYPES_H__ +package android.media; -#ifdef __cplusplus -extern "C" { -#endif - -#include <assert.h> -#include <ctype.h> -#include <stdlib.h> -#include <stdint.h> -#include <stdio.h> -#include <string.h> - -#ifndef TRUE -#define TRUE 1 -#endif +/** + * @hide + */ +public interface VolumeController { -#ifndef FALSE -#define FALSE 0 -#endif + public void postHasNewRemotePlaybackInfo(); -#define Trace(...) + public void postRemoteVolumeChanged(int streamType, int flags); -#ifdef __cplusplus + public void postRemoteSliderVisibility(boolean visible); } -#endif - -#endif /* __COMMON_TYPES_H__ */ diff --git a/media/java/android/media/WebVttRenderer.java b/media/java/android/media/WebVttRenderer.java new file mode 100644 index 0000000..4dec081 --- /dev/null +++ b/media/java/android/media/WebVttRenderer.java @@ -0,0 +1,1842 @@ +/* + * Copyright (C) 2013 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.text.Layout.Alignment; +import android.text.SpannableStringBuilder; +import android.util.ArrayMap; +import android.util.AttributeSet; +import android.util.Log; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.view.accessibility.CaptioningManager; +import android.view.accessibility.CaptioningManager.CaptionStyle; +import android.view.accessibility.CaptioningManager.CaptioningChangeListener; +import android.widget.LinearLayout; + +import com.android.internal.widget.SubtitleView; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Vector; + +/** @hide */ +public class WebVttRenderer extends SubtitleController.Renderer { + private final Context mContext; + + private WebVttRenderingWidget mRenderingWidget; + + public WebVttRenderer(Context context) { + mContext = context; + } + + @Override + public boolean supports(MediaFormat format) { + if (format.containsKey(MediaFormat.KEY_MIME)) { + return format.getString(MediaFormat.KEY_MIME).equals("text/vtt"); + } + return false; + } + + @Override + public SubtitleTrack createTrack(MediaFormat format) { + if (mRenderingWidget == null) { + mRenderingWidget = new WebVttRenderingWidget(mContext); + } + + return new WebVttTrack(mRenderingWidget, format); + } +} + +/** @hide */ +class TextTrackCueSpan { + long mTimestampMs; + boolean mEnabled; + String mText; + TextTrackCueSpan(String text, long timestamp) { + mTimestampMs = timestamp; + mText = text; + // spans with timestamp will be enabled by Cue.onTime + mEnabled = (mTimestampMs < 0); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof TextTrackCueSpan)) { + return false; + } + TextTrackCueSpan span = (TextTrackCueSpan) o; + return mTimestampMs == span.mTimestampMs && + mText.equals(span.mText); + } +} + +/** + * @hide + * + * Extract all text without style, but with timestamp spans. + */ +class UnstyledTextExtractor implements Tokenizer.OnTokenListener { + StringBuilder mLine = new StringBuilder(); + Vector<TextTrackCueSpan[]> mLines = new Vector<TextTrackCueSpan[]>(); + Vector<TextTrackCueSpan> mCurrentLine = new Vector<TextTrackCueSpan>(); + long mLastTimestamp; + + UnstyledTextExtractor() { + init(); + } + + private void init() { + mLine.delete(0, mLine.length()); + mLines.clear(); + mCurrentLine.clear(); + mLastTimestamp = -1; + } + + @Override + public void onData(String s) { + mLine.append(s); + } + + @Override + public void onStart(String tag, String[] classes, String annotation) { } + + @Override + public void onEnd(String tag) { } + + @Override + public void onTimeStamp(long timestampMs) { + // finish any prior span + if (mLine.length() > 0 && timestampMs != mLastTimestamp) { + mCurrentLine.add( + new TextTrackCueSpan(mLine.toString(), mLastTimestamp)); + mLine.delete(0, mLine.length()); + } + mLastTimestamp = timestampMs; + } + + @Override + public void onLineEnd() { + // finish any pending span + if (mLine.length() > 0) { + mCurrentLine.add( + new TextTrackCueSpan(mLine.toString(), mLastTimestamp)); + mLine.delete(0, mLine.length()); + } + + TextTrackCueSpan[] spans = new TextTrackCueSpan[mCurrentLine.size()]; + mCurrentLine.toArray(spans); + mCurrentLine.clear(); + mLines.add(spans); + } + + public TextTrackCueSpan[][] getText() { + // for politeness, finish last cue-line if it ends abruptly + if (mLine.length() > 0 || mCurrentLine.size() > 0) { + onLineEnd(); + } + TextTrackCueSpan[][] lines = new TextTrackCueSpan[mLines.size()][]; + mLines.toArray(lines); + init(); + return lines; + } +} + +/** + * @hide + * + * Tokenizer tokenizes the WebVTT Cue Text into tags and data + */ +class Tokenizer { + private static final String TAG = "Tokenizer"; + private TokenizerPhase mPhase; + private TokenizerPhase mDataTokenizer; + private TokenizerPhase mTagTokenizer; + + private OnTokenListener mListener; + private String mLine; + private int mHandledLen; + + interface TokenizerPhase { + TokenizerPhase start(); + void tokenize(); + } + + class DataTokenizer implements TokenizerPhase { + // includes both WebVTT data && escape state + private StringBuilder mData; + + public TokenizerPhase start() { + mData = new StringBuilder(); + return this; + } + + private boolean replaceEscape(String escape, String replacement, int pos) { + if (mLine.startsWith(escape, pos)) { + mData.append(mLine.substring(mHandledLen, pos)); + mData.append(replacement); + mHandledLen = pos + escape.length(); + pos = mHandledLen - 1; + return true; + } + return false; + } + + @Override + public void tokenize() { + int end = mLine.length(); + for (int pos = mHandledLen; pos < mLine.length(); pos++) { + if (mLine.charAt(pos) == '&') { + if (replaceEscape("&", "&", pos) || + replaceEscape("<", "<", pos) || + replaceEscape(">", ">", pos) || + replaceEscape("‎", "\u200e", pos) || + replaceEscape("‏", "\u200f", pos) || + replaceEscape(" ", "\u00a0", pos)) { + continue; + } + } else if (mLine.charAt(pos) == '<') { + end = pos; + mPhase = mTagTokenizer.start(); + break; + } + } + mData.append(mLine.substring(mHandledLen, end)); + // yield mData + mListener.onData(mData.toString()); + mData.delete(0, mData.length()); + mHandledLen = end; + } + } + + class TagTokenizer implements TokenizerPhase { + private boolean mAtAnnotation; + private String mName, mAnnotation; + + public TokenizerPhase start() { + mName = mAnnotation = ""; + mAtAnnotation = false; + return this; + } + + @Override + public void tokenize() { + if (!mAtAnnotation) + mHandledLen++; + if (mHandledLen < mLine.length()) { + String[] parts; + /** + * Collect annotations and end-tags to closing >. Collect tag + * name to closing bracket or next white-space. + */ + if (mAtAnnotation || mLine.charAt(mHandledLen) == '/') { + parts = mLine.substring(mHandledLen).split(">"); + } else { + parts = mLine.substring(mHandledLen).split("[\t\f >]"); + } + String part = mLine.substring( + mHandledLen, mHandledLen + parts[0].length()); + mHandledLen += parts[0].length(); + + if (mAtAnnotation) { + mAnnotation += " " + part; + } else { + mName = part; + } + } + + mAtAnnotation = true; + + if (mHandledLen < mLine.length() && mLine.charAt(mHandledLen) == '>') { + yield_tag(); + mPhase = mDataTokenizer.start(); + mHandledLen++; + } + } + + private void yield_tag() { + if (mName.startsWith("/")) { + mListener.onEnd(mName.substring(1)); + } else if (mName.length() > 0 && Character.isDigit(mName.charAt(0))) { + // timestamp + try { + long timestampMs = WebVttParser.parseTimestampMs(mName); + mListener.onTimeStamp(timestampMs); + } catch (NumberFormatException e) { + Log.d(TAG, "invalid timestamp tag: <" + mName + ">"); + } + } else { + mAnnotation = mAnnotation.replaceAll("\\s+", " "); + if (mAnnotation.startsWith(" ")) { + mAnnotation = mAnnotation.substring(1); + } + if (mAnnotation.endsWith(" ")) { + mAnnotation = mAnnotation.substring(0, mAnnotation.length() - 1); + } + + String[] classes = null; + int dotAt = mName.indexOf('.'); + if (dotAt >= 0) { + classes = mName.substring(dotAt + 1).split("\\."); + mName = mName.substring(0, dotAt); + } + mListener.onStart(mName, classes, mAnnotation); + } + } + } + + Tokenizer(OnTokenListener listener) { + mDataTokenizer = new DataTokenizer(); + mTagTokenizer = new TagTokenizer(); + reset(); + mListener = listener; + } + + void reset() { + mPhase = mDataTokenizer.start(); + } + + void tokenize(String s) { + mHandledLen = 0; + mLine = s; + while (mHandledLen < mLine.length()) { + mPhase.tokenize(); + } + /* we are finished with a line unless we are in the middle of a tag */ + if (!(mPhase instanceof TagTokenizer)) { + // yield END-OF-LINE + mListener.onLineEnd(); + } + } + + interface OnTokenListener { + void onData(String s); + void onStart(String tag, String[] classes, String annotation); + void onEnd(String tag); + void onTimeStamp(long timestampMs); + void onLineEnd(); + } +} + +/** @hide */ +class TextTrackRegion { + final static int SCROLL_VALUE_NONE = 300; + final static int SCROLL_VALUE_SCROLL_UP = 301; + + String mId; + float mWidth; + int mLines; + float mAnchorPointX, mAnchorPointY; + float mViewportAnchorPointX, mViewportAnchorPointY; + int mScrollValue; + + TextTrackRegion() { + mId = ""; + mWidth = 100; + mLines = 3; + mAnchorPointX = mViewportAnchorPointX = 0.f; + mAnchorPointY = mViewportAnchorPointY = 100.f; + mScrollValue = SCROLL_VALUE_NONE; + } + + public String toString() { + StringBuilder res = new StringBuilder(" {id:\"").append(mId) + .append("\", width:").append(mWidth) + .append(", lines:").append(mLines) + .append(", anchorPoint:(").append(mAnchorPointX) + .append(", ").append(mAnchorPointY) + .append("), viewportAnchorPoints:").append(mViewportAnchorPointX) + .append(", ").append(mViewportAnchorPointY) + .append("), scrollValue:") + .append(mScrollValue == SCROLL_VALUE_NONE ? "none" : + mScrollValue == SCROLL_VALUE_SCROLL_UP ? "scroll_up" : + "INVALID") + .append("}"); + return res.toString(); + } +} + +/** @hide */ +class TextTrackCue extends SubtitleTrack.Cue { + final static int WRITING_DIRECTION_HORIZONTAL = 100; + final static int WRITING_DIRECTION_VERTICAL_RL = 101; + final static int WRITING_DIRECTION_VERTICAL_LR = 102; + + final static int ALIGNMENT_MIDDLE = 200; + final static int ALIGNMENT_START = 201; + final static int ALIGNMENT_END = 202; + final static int ALIGNMENT_LEFT = 203; + final static int ALIGNMENT_RIGHT = 204; + private static final String TAG = "TTCue"; + + String mId; + boolean mPauseOnExit; + int mWritingDirection; + String mRegionId; + boolean mSnapToLines; + Integer mLinePosition; // null means AUTO + boolean mAutoLinePosition; + int mTextPosition; + int mSize; + int mAlignment; + // Vector<String> mText; + String[] mStrings; + TextTrackCueSpan[][] mLines; + TextTrackRegion mRegion; + + TextTrackCue() { + mId = ""; + mPauseOnExit = false; + mWritingDirection = WRITING_DIRECTION_HORIZONTAL; + mRegionId = ""; + mSnapToLines = true; + mLinePosition = null /* AUTO */; + mTextPosition = 50; + mSize = 100; + mAlignment = ALIGNMENT_MIDDLE; + mLines = null; + mRegion = null; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof TextTrackCue)) { + return false; + } + if (this == o) { + return true; + } + + try { + TextTrackCue cue = (TextTrackCue) o; + boolean res = mId.equals(cue.mId) && + mPauseOnExit == cue.mPauseOnExit && + mWritingDirection == cue.mWritingDirection && + mRegionId.equals(cue.mRegionId) && + mSnapToLines == cue.mSnapToLines && + mAutoLinePosition == cue.mAutoLinePosition && + (mAutoLinePosition || mLinePosition == cue.mLinePosition) && + mTextPosition == cue.mTextPosition && + mSize == cue.mSize && + mAlignment == cue.mAlignment && + mLines.length == cue.mLines.length; + if (res == true) { + for (int line = 0; line < mLines.length; line++) { + if (!Arrays.equals(mLines[line], cue.mLines[line])) { + return false; + } + } + } + return res; + } catch(IncompatibleClassChangeError e) { + return false; + } + } + + public StringBuilder appendStringsToBuilder(StringBuilder builder) { + if (mStrings == null) { + builder.append("null"); + } else { + builder.append("["); + boolean first = true; + for (String s: mStrings) { + if (!first) { + builder.append(", "); + } + if (s == null) { + builder.append("null"); + } else { + builder.append("\""); + builder.append(s); + builder.append("\""); + } + first = false; + } + builder.append("]"); + } + return builder; + } + + public StringBuilder appendLinesToBuilder(StringBuilder builder) { + if (mLines == null) { + builder.append("null"); + } else { + builder.append("["); + boolean first = true; + for (TextTrackCueSpan[] spans: mLines) { + if (!first) { + builder.append(", "); + } + if (spans == null) { + builder.append("null"); + } else { + builder.append("\""); + boolean innerFirst = true; + long lastTimestamp = -1; + for (TextTrackCueSpan span: spans) { + if (!innerFirst) { + builder.append(" "); + } + if (span.mTimestampMs != lastTimestamp) { + builder.append("<") + .append(WebVttParser.timeToString( + span.mTimestampMs)) + .append(">"); + lastTimestamp = span.mTimestampMs; + } + builder.append(span.mText); + innerFirst = false; + } + builder.append("\""); + } + first = false; + } + builder.append("]"); + } + return builder; + } + + public String toString() { + StringBuilder res = new StringBuilder(); + + res.append(WebVttParser.timeToString(mStartTimeMs)) + .append(" --> ").append(WebVttParser.timeToString(mEndTimeMs)) + .append(" {id:\"").append(mId) + .append("\", pauseOnExit:").append(mPauseOnExit) + .append(", direction:") + .append(mWritingDirection == WRITING_DIRECTION_HORIZONTAL ? "horizontal" : + mWritingDirection == WRITING_DIRECTION_VERTICAL_LR ? "vertical_lr" : + mWritingDirection == WRITING_DIRECTION_VERTICAL_RL ? "vertical_rl" : + "INVALID") + .append(", regionId:\"").append(mRegionId) + .append("\", snapToLines:").append(mSnapToLines) + .append(", linePosition:").append(mAutoLinePosition ? "auto" : + mLinePosition) + .append(", textPosition:").append(mTextPosition) + .append(", size:").append(mSize) + .append(", alignment:") + .append(mAlignment == ALIGNMENT_END ? "end" : + mAlignment == ALIGNMENT_LEFT ? "left" : + mAlignment == ALIGNMENT_MIDDLE ? "middle" : + mAlignment == ALIGNMENT_RIGHT ? "right" : + mAlignment == ALIGNMENT_START ? "start" : "INVALID") + .append(", text:"); + appendStringsToBuilder(res).append("}"); + return res.toString(); + } + + @Override + public int hashCode() { + return toString().hashCode(); + } + + @Override + public void onTime(long timeMs) { + for (TextTrackCueSpan[] line: mLines) { + for (TextTrackCueSpan span: line) { + span.mEnabled = timeMs >= span.mTimestampMs; + } + } + } +} + +/** @hide */ +class WebVttParser { + private static final String TAG = "WebVttParser"; + private Phase mPhase; + private TextTrackCue mCue; + private Vector<String> mCueTexts; + private WebVttCueListener mListener; + private String mBuffer; + + WebVttParser(WebVttCueListener listener) { + mPhase = mParseStart; + mBuffer = ""; /* mBuffer contains up to 1 incomplete line */ + mListener = listener; + mCueTexts = new Vector<String>(); + } + + /* parsePercentageString */ + public static float parseFloatPercentage(String s) + throws NumberFormatException { + if (!s.endsWith("%")) { + throw new NumberFormatException("does not end in %"); + } + s = s.substring(0, s.length() - 1); + // parseFloat allows an exponent or a sign + if (s.matches(".*[^0-9.].*")) { + throw new NumberFormatException("contains an invalid character"); + } + + try { + float value = Float.parseFloat(s); + if (value < 0.0f || value > 100.0f) { + throw new NumberFormatException("is out of range"); + } + return value; + } catch (NumberFormatException e) { + throw new NumberFormatException("is not a number"); + } + } + + public static int parseIntPercentage(String s) throws NumberFormatException { + if (!s.endsWith("%")) { + throw new NumberFormatException("does not end in %"); + } + s = s.substring(0, s.length() - 1); + // parseInt allows "-0" that returns 0, so check for non-digits + if (s.matches(".*[^0-9].*")) { + throw new NumberFormatException("contains an invalid character"); + } + + try { + int value = Integer.parseInt(s); + if (value < 0 || value > 100) { + throw new NumberFormatException("is out of range"); + } + return value; + } catch (NumberFormatException e) { + throw new NumberFormatException("is not a number"); + } + } + + public static long parseTimestampMs(String s) throws NumberFormatException { + if (!s.matches("(\\d+:)?[0-5]\\d:[0-5]\\d\\.\\d{3}")) { + throw new NumberFormatException("has invalid format"); + } + + String[] parts = s.split("\\.", 2); + long value = 0; + for (String group: parts[0].split(":")) { + value = value * 60 + Long.parseLong(group); + } + return value * 1000 + Long.parseLong(parts[1]); + } + + public static String timeToString(long timeMs) { + return String.format("%d:%02d:%02d.%03d", + timeMs / 3600000, (timeMs / 60000) % 60, + (timeMs / 1000) % 60, timeMs % 1000); + } + + public void parse(String s) { + boolean trailingCR = false; + mBuffer = (mBuffer + s.replace("\0", "\ufffd")).replace("\r\n", "\n"); + + /* keep trailing '\r' in case matching '\n' arrives in next packet */ + if (mBuffer.endsWith("\r")) { + trailingCR = true; + mBuffer = mBuffer.substring(0, mBuffer.length() - 1); + } + + String[] lines = mBuffer.split("[\r\n]"); + for (int i = 0; i < lines.length - 1; i++) { + mPhase.parse(lines[i]); + } + + mBuffer = lines[lines.length - 1]; + if (trailingCR) + mBuffer += "\r"; + } + + public void eos() { + if (mBuffer.endsWith("\r")) { + mBuffer = mBuffer.substring(0, mBuffer.length() - 1); + } + + mPhase.parse(mBuffer); + mBuffer = ""; + + yieldCue(); + mPhase = mParseStart; + } + + public void yieldCue() { + if (mCue != null && mCueTexts.size() > 0) { + mCue.mStrings = new String[mCueTexts.size()]; + mCueTexts.toArray(mCue.mStrings); + mCueTexts.clear(); + mListener.onCueParsed(mCue); + } + mCue = null; + } + + interface Phase { + void parse(String line); + } + + final private Phase mSkipRest = new Phase() { + @Override + public void parse(String line) { } + }; + + final private Phase mParseStart = new Phase() { // 5-9 + @Override + public void parse(String line) { + if (line.startsWith("\ufeff")) { + line = line.substring(1); + } + if (!line.equals("WEBVTT") && + !line.startsWith("WEBVTT ") && + !line.startsWith("WEBVTT\t")) { + log_warning("Not a WEBVTT header", line); + mPhase = mSkipRest; + } else { + mPhase = mParseHeader; + } + } + }; + + final private Phase mParseHeader = new Phase() { // 10-13 + TextTrackRegion parseRegion(String s) { + TextTrackRegion region = new TextTrackRegion(); + for (String setting: s.split(" +")) { + int equalAt = setting.indexOf('='); + if (equalAt <= 0 || equalAt == setting.length() - 1) { + continue; + } + + String name = setting.substring(0, equalAt); + String value = setting.substring(equalAt + 1); + if (name.equals("id")) { + region.mId = value; + } else if (name.equals("width")) { + try { + region.mWidth = parseFloatPercentage(value); + } catch (NumberFormatException e) { + log_warning("region setting", name, + "has invalid value", e.getMessage(), value); + } + } else if (name.equals("lines")) { + try { + int lines = Integer.parseInt(value); + if (lines >= 0) { + region.mLines = lines; + } else { + log_warning("region setting", name, "is negative", value); + } + } catch (NumberFormatException e) { + log_warning("region setting", name, "is not numeric", value); + } + } else if (name.equals("regionanchor") || + name.equals("viewportanchor")) { + int commaAt = value.indexOf(","); + if (commaAt < 0) { + log_warning("region setting", name, "contains no comma", value); + continue; + } + + String anchorX = value.substring(0, commaAt); + String anchorY = value.substring(commaAt + 1); + float x, y; + + try { + x = parseFloatPercentage(anchorX); + } catch (NumberFormatException e) { + log_warning("region setting", name, + "has invalid x component", e.getMessage(), anchorX); + continue; + } + try { + y = parseFloatPercentage(anchorY); + } catch (NumberFormatException e) { + log_warning("region setting", name, + "has invalid y component", e.getMessage(), anchorY); + continue; + } + + if (name.charAt(0) == 'r') { + region.mAnchorPointX = x; + region.mAnchorPointY = y; + } else { + region.mViewportAnchorPointX = x; + region.mViewportAnchorPointY = y; + } + } else if (name.equals("scroll")) { + if (value.equals("up")) { + region.mScrollValue = + TextTrackRegion.SCROLL_VALUE_SCROLL_UP; + } else { + log_warning("region setting", name, "has invalid value", value); + } + } + } + return region; + } + + @Override + public void parse(String line) { + if (line.length() == 0) { + mPhase = mParseCueId; + } else if (line.contains("-->")) { + mPhase = mParseCueTime; + mPhase.parse(line); + } else { + int colonAt = line.indexOf(':'); + if (colonAt <= 0 || colonAt >= line.length() - 1) { + log_warning("meta data header has invalid format", line); + } + String name = line.substring(0, colonAt); + String value = line.substring(colonAt + 1); + + if (name.equals("Region")) { + TextTrackRegion region = parseRegion(value); + mListener.onRegionParsed(region); + } + } + } + }; + + final private Phase mParseCueId = new Phase() { + @Override + public void parse(String line) { + if (line.length() == 0) { + return; + } + + assert(mCue == null); + + if (line.equals("NOTE") || line.startsWith("NOTE ")) { + mPhase = mParseCueText; + } + + mCue = new TextTrackCue(); + mCueTexts.clear(); + + mPhase = mParseCueTime; + if (line.contains("-->")) { + mPhase.parse(line); + } else { + mCue.mId = line; + } + } + }; + + final private Phase mParseCueTime = new Phase() { + @Override + public void parse(String line) { + int arrowAt = line.indexOf("-->"); + if (arrowAt < 0) { + mCue = null; + mPhase = mParseCueId; + return; + } + + String start = line.substring(0, arrowAt).trim(); + // convert only initial and first other white-space to space + String rest = line.substring(arrowAt + 3) + .replaceFirst("^\\s+", "").replaceFirst("\\s+", " "); + int spaceAt = rest.indexOf(' '); + String end = spaceAt > 0 ? rest.substring(0, spaceAt) : rest; + rest = spaceAt > 0 ? rest.substring(spaceAt + 1) : ""; + + mCue.mStartTimeMs = parseTimestampMs(start); + mCue.mEndTimeMs = parseTimestampMs(end); + for (String setting: rest.split(" +")) { + int colonAt = setting.indexOf(':'); + if (colonAt <= 0 || colonAt == setting.length() - 1) { + continue; + } + String name = setting.substring(0, colonAt); + String value = setting.substring(colonAt + 1); + + if (name.equals("region")) { + mCue.mRegionId = value; + } else if (name.equals("vertical")) { + if (value.equals("rl")) { + mCue.mWritingDirection = + TextTrackCue.WRITING_DIRECTION_VERTICAL_RL; + } else if (value.equals("lr")) { + mCue.mWritingDirection = + TextTrackCue.WRITING_DIRECTION_VERTICAL_LR; + } else { + log_warning("cue setting", name, "has invalid value", value); + } + } else if (name.equals("line")) { + try { + int linePosition; + /* TRICKY: we know that there are no spaces in value */ + assert(value.indexOf(' ') < 0); + if (value.endsWith("%")) { + linePosition = Integer.parseInt( + value.substring(0, value.length() - 1)); + if (linePosition < 0 || linePosition > 100) { + log_warning("cue setting", name, "is out of range", value); + continue; + } + mCue.mSnapToLines = false; + mCue.mLinePosition = linePosition; + } else { + mCue.mSnapToLines = true; + mCue.mLinePosition = Integer.parseInt(value); + } + } catch (NumberFormatException e) { + log_warning("cue setting", name, + "is not numeric or percentage", value); + } + } else if (name.equals("position")) { + try { + mCue.mTextPosition = parseIntPercentage(value); + } catch (NumberFormatException e) { + log_warning("cue setting", name, + "is not numeric or percentage", value); + } + } else if (name.equals("size")) { + try { + mCue.mSize = parseIntPercentage(value); + } catch (NumberFormatException e) { + log_warning("cue setting", name, + "is not numeric or percentage", value); + } + } else if (name.equals("align")) { + if (value.equals("start")) { + mCue.mAlignment = TextTrackCue.ALIGNMENT_START; + } else if (value.equals("middle")) { + mCue.mAlignment = TextTrackCue.ALIGNMENT_MIDDLE; + } else if (value.equals("end")) { + mCue.mAlignment = TextTrackCue.ALIGNMENT_END; + } else if (value.equals("left")) { + mCue.mAlignment = TextTrackCue.ALIGNMENT_LEFT; + } else if (value.equals("right")) { + mCue.mAlignment = TextTrackCue.ALIGNMENT_RIGHT; + } else { + log_warning("cue setting", name, "has invalid value", value); + continue; + } + } + } + + if (mCue.mLinePosition != null || + mCue.mSize != 100 || + (mCue.mWritingDirection != + TextTrackCue.WRITING_DIRECTION_HORIZONTAL)) { + mCue.mRegionId = ""; + } + + mPhase = mParseCueText; + } + }; + + /* also used for notes */ + final private Phase mParseCueText = new Phase() { + @Override + public void parse(String line) { + if (line.length() == 0) { + yieldCue(); + mPhase = mParseCueId; + return; + } else if (mCue != null) { + mCueTexts.add(line); + } + } + }; + + private void log_warning( + String nameType, String name, String message, + String subMessage, String value) { + Log.w(this.getClass().getName(), nameType + " '" + name + "' " + + message + " ('" + value + "' " + subMessage + ")"); + } + + private void log_warning( + String nameType, String name, String message, String value) { + Log.w(this.getClass().getName(), nameType + " '" + name + "' " + + message + " ('" + value + "')"); + } + + private void log_warning(String message, String value) { + Log.w(this.getClass().getName(), message + " ('" + value + "')"); + } +} + +/** @hide */ +interface WebVttCueListener { + void onCueParsed(TextTrackCue cue); + void onRegionParsed(TextTrackRegion region); +} + +/** @hide */ +class WebVttTrack extends SubtitleTrack implements WebVttCueListener { + private static final String TAG = "WebVttTrack"; + + private final WebVttParser mParser = new WebVttParser(this); + private final UnstyledTextExtractor mExtractor = + new UnstyledTextExtractor(); + private final Tokenizer mTokenizer = new Tokenizer(mExtractor); + private final Vector<Long> mTimestamps = new Vector<Long>(); + private final WebVttRenderingWidget mRenderingWidget; + + private final Map<String, TextTrackRegion> mRegions = + new HashMap<String, TextTrackRegion>(); + private Long mCurrentRunID; + + WebVttTrack(WebVttRenderingWidget renderingWidget, MediaFormat format) { + super(format); + + mRenderingWidget = renderingWidget; + } + + @Override + public WebVttRenderingWidget getRenderingWidget() { + return mRenderingWidget; + } + + @Override + public void onData(String data, boolean eos, long runID) { + // implement intermixing restriction for WebVTT only for now + synchronized(mParser) { + if (mCurrentRunID != null && runID != mCurrentRunID) { + throw new IllegalStateException( + "Run #" + mCurrentRunID + + " in progress. Cannot process run #" + runID); + } + mCurrentRunID = runID; + mParser.parse(data); + if (eos) { + finishedRun(runID); + mParser.eos(); + mRegions.clear(); + mCurrentRunID = null; + } + } + } + + @Override + public void onCueParsed(TextTrackCue cue) { + synchronized (mParser) { + // resolve region + if (cue.mRegionId.length() != 0) { + cue.mRegion = mRegions.get(cue.mRegionId); + } + + if (DEBUG) Log.v(TAG, "adding cue " + cue); + + // tokenize text track string-lines into lines of spans + mTokenizer.reset(); + for (String s: cue.mStrings) { + mTokenizer.tokenize(s); + } + cue.mLines = mExtractor.getText(); + if (DEBUG) Log.v(TAG, cue.appendLinesToBuilder( + cue.appendStringsToBuilder( + new StringBuilder()).append(" simplified to: ")) + .toString()); + + // extract inner timestamps + for (TextTrackCueSpan[] line: cue.mLines) { + for (TextTrackCueSpan span: line) { + if (span.mTimestampMs > cue.mStartTimeMs && + span.mTimestampMs < cue.mEndTimeMs && + !mTimestamps.contains(span.mTimestampMs)) { + mTimestamps.add(span.mTimestampMs); + } + } + } + + if (mTimestamps.size() > 0) { + cue.mInnerTimesMs = new long[mTimestamps.size()]; + for (int ix=0; ix < mTimestamps.size(); ++ix) { + cue.mInnerTimesMs[ix] = mTimestamps.get(ix); + } + mTimestamps.clear(); + } else { + cue.mInnerTimesMs = null; + } + + cue.mRunID = mCurrentRunID; + } + + addCue(cue); + } + + @Override + public void onRegionParsed(TextTrackRegion region) { + synchronized(mParser) { + mRegions.put(region.mId, region); + } + } + + @Override + public void updateView(Vector<SubtitleTrack.Cue> activeCues) { + if (!mVisible) { + // don't keep the state if we are not visible + return; + } + + if (DEBUG && mTimeProvider != null) { + try { + Log.d(TAG, "at " + + (mTimeProvider.getCurrentTimeUs(false, true) / 1000) + + " ms the active cues are:"); + } catch (IllegalStateException e) { + Log.d(TAG, "at (illegal state) the active cues are:"); + } + } + + mRenderingWidget.setActiveCues(activeCues); + } +} + +/** + * Widget capable of rendering WebVTT captions. + * + * @hide + */ +class WebVttRenderingWidget extends ViewGroup implements SubtitleTrack.RenderingWidget { + private static final boolean DEBUG = false; + private static final int DEBUG_REGION_BACKGROUND = 0x800000FF; + private static final int DEBUG_CUE_BACKGROUND = 0x80FF0000; + + /** WebVtt specifies line height as 5.3% of the viewport height. */ + private static final float LINE_HEIGHT_RATIO = 0.0533f; + + /** Map of active regions, used to determine enter/exit. */ + private final ArrayMap<TextTrackRegion, RegionLayout> mRegionBoxes = + new ArrayMap<TextTrackRegion, RegionLayout>(); + + /** Map of active cues, used to determine enter/exit. */ + private final ArrayMap<TextTrackCue, CueLayout> mCueBoxes = + new ArrayMap<TextTrackCue, CueLayout>(); + + /** Captioning manager, used to obtain and track caption properties. */ + private final CaptioningManager mManager; + + /** Callback for rendering changes. */ + private OnChangedListener mListener; + + /** Current caption style. */ + private CaptionStyle mCaptionStyle; + + /** Current font size, computed from font scaling factor and height. */ + private float mFontSize; + + /** Whether a caption style change listener is registered. */ + private boolean mHasChangeListener; + + public WebVttRenderingWidget(Context context) { + this(context, null); + } + + public WebVttRenderingWidget(Context context, AttributeSet attrs) { + this(context, null, 0); + } + + public WebVttRenderingWidget(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + // Cannot render text over video when layer type is hardware. + setLayerType(View.LAYER_TYPE_SOFTWARE, null); + + mManager = (CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE); + mCaptionStyle = mManager.getUserStyle(); + mFontSize = mManager.getFontScale() * getHeight() * LINE_HEIGHT_RATIO; + } + + @Override + public void setSize(int width, int height) { + final int widthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY); + final int heightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); + + measure(widthSpec, heightSpec); + layout(0, 0, width, height); + } + + @Override + public void onAttachedToWindow() { + super.onAttachedToWindow(); + + manageChangeListener(); + } + + @Override + public void onDetachedFromWindow() { + super.onDetachedFromWindow(); + + manageChangeListener(); + } + + @Override + public void setOnChangedListener(OnChangedListener listener) { + mListener = listener; + } + + @Override + public void setVisible(boolean visible) { + if (visible) { + setVisibility(View.VISIBLE); + } else { + setVisibility(View.GONE); + } + + manageChangeListener(); + } + + /** + * Manages whether this renderer is listening for caption style changes. + */ + private void manageChangeListener() { + final boolean needsListener = isAttachedToWindow() && getVisibility() == View.VISIBLE; + if (mHasChangeListener != needsListener) { + mHasChangeListener = needsListener; + + if (needsListener) { + mManager.addCaptioningChangeListener(mCaptioningListener); + + final CaptionStyle captionStyle = mManager.getUserStyle(); + final float fontSize = mManager.getFontScale() * getHeight() * LINE_HEIGHT_RATIO; + setCaptionStyle(captionStyle, fontSize); + } else { + mManager.removeCaptioningChangeListener(mCaptioningListener); + } + } + } + + public void setActiveCues(Vector<SubtitleTrack.Cue> activeCues) { + final Context context = getContext(); + final CaptionStyle captionStyle = mCaptionStyle; + final float fontSize = mFontSize; + + prepForPrune(); + + // Ensure we have all necessary cue and region boxes. + final int count = activeCues.size(); + for (int i = 0; i < count; i++) { + final TextTrackCue cue = (TextTrackCue) activeCues.get(i); + final TextTrackRegion region = cue.mRegion; + if (region != null) { + RegionLayout regionBox = mRegionBoxes.get(region); + if (regionBox == null) { + regionBox = new RegionLayout(context, region, captionStyle, fontSize); + mRegionBoxes.put(region, regionBox); + addView(regionBox, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + } + regionBox.put(cue); + } else { + CueLayout cueBox = mCueBoxes.get(cue); + if (cueBox == null) { + cueBox = new CueLayout(context, cue, captionStyle, fontSize); + mCueBoxes.put(cue, cueBox); + addView(cueBox, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + } + cueBox.update(); + cueBox.setOrder(i); + } + } + + prune(); + + // Force measurement and layout. + final int width = getWidth(); + final int height = getHeight(); + setSize(width, height); + + if (mListener != null) { + mListener.onChanged(this); + } + } + + private void setCaptionStyle(CaptionStyle captionStyle, float fontSize) { + mCaptionStyle = captionStyle; + mFontSize = fontSize; + + final int cueCount = mCueBoxes.size(); + for (int i = 0; i < cueCount; i++) { + final CueLayout cueBox = mCueBoxes.valueAt(i); + cueBox.setCaptionStyle(captionStyle, fontSize); + } + + final int regionCount = mRegionBoxes.size(); + for (int i = 0; i < regionCount; i++) { + final RegionLayout regionBox = mRegionBoxes.valueAt(i); + regionBox.setCaptionStyle(captionStyle, fontSize); + } + } + + /** + * Remove inactive cues and regions. + */ + private void prune() { + int regionCount = mRegionBoxes.size(); + for (int i = 0; i < regionCount; i++) { + final RegionLayout regionBox = mRegionBoxes.valueAt(i); + if (regionBox.prune()) { + removeView(regionBox); + mRegionBoxes.removeAt(i); + regionCount--; + i--; + } + } + + int cueCount = mCueBoxes.size(); + for (int i = 0; i < cueCount; i++) { + final CueLayout cueBox = mCueBoxes.valueAt(i); + if (!cueBox.isActive()) { + removeView(cueBox); + mCueBoxes.removeAt(i); + cueCount--; + i--; + } + } + } + + /** + * Reset active cues and regions. + */ + private void prepForPrune() { + final int regionCount = mRegionBoxes.size(); + for (int i = 0; i < regionCount; i++) { + final RegionLayout regionBox = mRegionBoxes.valueAt(i); + regionBox.prepForPrune(); + } + + final int cueCount = mCueBoxes.size(); + for (int i = 0; i < cueCount; i++) { + final CueLayout cueBox = mCueBoxes.valueAt(i); + cueBox.prepForPrune(); + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + final int regionCount = mRegionBoxes.size(); + for (int i = 0; i < regionCount; i++) { + final RegionLayout regionBox = mRegionBoxes.valueAt(i); + regionBox.measureForParent(widthMeasureSpec, heightMeasureSpec); + } + + final int cueCount = mCueBoxes.size(); + for (int i = 0; i < cueCount; i++) { + final CueLayout cueBox = mCueBoxes.valueAt(i); + cueBox.measureForParent(widthMeasureSpec, heightMeasureSpec); + } + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + final int viewportWidth = r - l; + final int viewportHeight = b - t; + + setCaptionStyle(mCaptionStyle, + mManager.getFontScale() * LINE_HEIGHT_RATIO * viewportHeight); + + final int regionCount = mRegionBoxes.size(); + for (int i = 0; i < regionCount; i++) { + final RegionLayout regionBox = mRegionBoxes.valueAt(i); + layoutRegion(viewportWidth, viewportHeight, regionBox); + } + + final int cueCount = mCueBoxes.size(); + for (int i = 0; i < cueCount; i++) { + final CueLayout cueBox = mCueBoxes.valueAt(i); + layoutCue(viewportWidth, viewportHeight, cueBox); + } + } + + /** + * Lays out a region within the viewport. The region handles layout for + * contained cues. + */ + private void layoutRegion( + int viewportWidth, int viewportHeight, + RegionLayout regionBox) { + final TextTrackRegion region = regionBox.getRegion(); + final int regionHeight = regionBox.getMeasuredHeight(); + final int regionWidth = regionBox.getMeasuredWidth(); + + // TODO: Account for region anchor point. + final float x = region.mViewportAnchorPointX; + final float y = region.mViewportAnchorPointY; + final int left = (int) (x * (viewportWidth - regionWidth) / 100); + final int top = (int) (y * (viewportHeight - regionHeight) / 100); + + regionBox.layout(left, top, left + regionWidth, top + regionHeight); + } + + /** + * Lays out a cue within the viewport. + */ + private void layoutCue( + int viewportWidth, int viewportHeight, CueLayout cueBox) { + final TextTrackCue cue = cueBox.getCue(); + final int direction = getLayoutDirection(); + final int absAlignment = resolveCueAlignment(direction, cue.mAlignment); + final boolean cueSnapToLines = cue.mSnapToLines; + + int size = 100 * cueBox.getMeasuredWidth() / viewportWidth; + + // Determine raw x-position. + int xPosition; + switch (absAlignment) { + case TextTrackCue.ALIGNMENT_LEFT: + xPosition = cue.mTextPosition; + break; + case TextTrackCue.ALIGNMENT_RIGHT: + xPosition = cue.mTextPosition - size; + break; + case TextTrackCue.ALIGNMENT_MIDDLE: + default: + xPosition = cue.mTextPosition - size / 2; + break; + } + + // Adjust x-position for layout. + if (direction == LAYOUT_DIRECTION_RTL) { + xPosition = 100 - xPosition; + } + + // If the text track cue snap-to-lines flag is set, adjust + // x-position and size for padding. This is equivalent to placing the + // cue within the title-safe area. + if (cueSnapToLines) { + final int paddingLeft = 100 * getPaddingLeft() / viewportWidth; + final int paddingRight = 100 * getPaddingRight() / viewportWidth; + if (xPosition < paddingLeft && xPosition + size > paddingLeft) { + xPosition += paddingLeft; + size -= paddingLeft; + } + final float rightEdge = 100 - paddingRight; + if (xPosition < rightEdge && xPosition + size > rightEdge) { + size -= paddingRight; + } + } + + // Compute absolute left position and width. + final int left = xPosition * viewportWidth / 100; + final int width = size * viewportWidth / 100; + + // Determine initial y-position. + final int yPosition = calculateLinePosition(cueBox); + + // Compute absolute final top position and height. + final int height = cueBox.getMeasuredHeight(); + final int top; + if (yPosition < 0) { + // TODO: This needs to use the actual height of prior boxes. + top = viewportHeight + yPosition * height; + } else { + top = yPosition * (viewportHeight - height) / 100; + } + + // Layout cue in final position. + cueBox.layout(left, top, left + width, top + height); + } + + /** + * Calculates the line position for a cue. + * <p> + * If the resulting position is negative, it represents a bottom-aligned + * position relative to the number of active cues. Otherwise, it represents + * a percentage [0-100] of the viewport height. + */ + private int calculateLinePosition(CueLayout cueBox) { + final TextTrackCue cue = cueBox.getCue(); + final Integer linePosition = cue.mLinePosition; + final boolean snapToLines = cue.mSnapToLines; + final boolean autoPosition = (linePosition == null); + + if (!snapToLines && !autoPosition && (linePosition < 0 || linePosition > 100)) { + // Invalid line position defaults to 100. + return 100; + } else if (!autoPosition) { + // Use the valid, supplied line position. + return linePosition; + } else if (!snapToLines) { + // Automatic, non-snapped line position defaults to 100. + return 100; + } else { + // Automatic snapped line position uses active cue order. + return -(cueBox.mOrder + 1); + } + } + + /** + * Resolves cue alignment according to the specified layout direction. + */ + private static int resolveCueAlignment(int layoutDirection, int alignment) { + switch (alignment) { + case TextTrackCue.ALIGNMENT_START: + return layoutDirection == View.LAYOUT_DIRECTION_LTR ? + TextTrackCue.ALIGNMENT_LEFT : TextTrackCue.ALIGNMENT_RIGHT; + case TextTrackCue.ALIGNMENT_END: + return layoutDirection == View.LAYOUT_DIRECTION_LTR ? + TextTrackCue.ALIGNMENT_RIGHT : TextTrackCue.ALIGNMENT_LEFT; + } + return alignment; + } + + private final CaptioningChangeListener mCaptioningListener = new CaptioningChangeListener() { + @Override + public void onFontScaleChanged(float fontScale) { + final float fontSize = fontScale * getHeight() * LINE_HEIGHT_RATIO; + setCaptionStyle(mCaptionStyle, fontSize); + } + + @Override + public void onUserStyleChanged(CaptionStyle userStyle) { + setCaptionStyle(userStyle, mFontSize); + } + }; + + /** + * A text track region represents a portion of the video viewport and + * provides a rendering area for text track cues. + */ + private static class RegionLayout extends LinearLayout { + private final ArrayList<CueLayout> mRegionCueBoxes = new ArrayList<CueLayout>(); + private final TextTrackRegion mRegion; + + private CaptionStyle mCaptionStyle; + private float mFontSize; + + public RegionLayout(Context context, TextTrackRegion region, CaptionStyle captionStyle, + float fontSize) { + super(context); + + mRegion = region; + mCaptionStyle = captionStyle; + mFontSize = fontSize; + + // TODO: Add support for vertical text + setOrientation(VERTICAL); + + if (DEBUG) { + setBackgroundColor(DEBUG_REGION_BACKGROUND); + } + } + + public void setCaptionStyle(CaptionStyle captionStyle, float fontSize) { + mCaptionStyle = captionStyle; + mFontSize = fontSize; + + final int cueCount = mRegionCueBoxes.size(); + for (int i = 0; i < cueCount; i++) { + final CueLayout cueBox = mRegionCueBoxes.get(i); + cueBox.setCaptionStyle(captionStyle, fontSize); + } + } + + /** + * Performs the parent's measurement responsibilities, then + * automatically performs its own measurement. + */ + public void measureForParent(int widthMeasureSpec, int heightMeasureSpec) { + final TextTrackRegion region = mRegion; + final int specWidth = MeasureSpec.getSize(widthMeasureSpec); + final int specHeight = MeasureSpec.getSize(heightMeasureSpec); + final int width = (int) region.mWidth; + + // Determine the absolute maximum region size as the requested size. + final int size = width * specWidth / 100; + + widthMeasureSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST); + heightMeasureSpec = MeasureSpec.makeMeasureSpec(specHeight, MeasureSpec.AT_MOST); + measure(widthMeasureSpec, heightMeasureSpec); + } + + /** + * Prepares this region for pruning by setting all tracks as inactive. + * <p> + * Tracks that are added or updated using {@link #put(TextTrackCue)} + * after this calling this method will be marked as active. + */ + public void prepForPrune() { + final int cueCount = mRegionCueBoxes.size(); + for (int i = 0; i < cueCount; i++) { + final CueLayout cueBox = mRegionCueBoxes.get(i); + cueBox.prepForPrune(); + } + } + + /** + * Adds a {@link TextTrackCue} to this region. If the track had already + * been added, updates its active state. + * + * @param cue + */ + public void put(TextTrackCue cue) { + final int cueCount = mRegionCueBoxes.size(); + for (int i = 0; i < cueCount; i++) { + final CueLayout cueBox = mRegionCueBoxes.get(i); + if (cueBox.getCue() == cue) { + cueBox.update(); + return; + } + } + + final CueLayout cueBox = new CueLayout(getContext(), cue, mCaptionStyle, mFontSize); + mRegionCueBoxes.add(cueBox); + addView(cueBox, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + + if (getChildCount() > mRegion.mLines) { + removeViewAt(0); + } + } + + /** + * Remove all inactive tracks from this region. + * + * @return true if this region is empty and should be pruned + */ + public boolean prune() { + int cueCount = mRegionCueBoxes.size(); + for (int i = 0; i < cueCount; i++) { + final CueLayout cueBox = mRegionCueBoxes.get(i); + if (!cueBox.isActive()) { + mRegionCueBoxes.remove(i); + removeView(cueBox); + cueCount--; + i--; + } + } + + return mRegionCueBoxes.isEmpty(); + } + + /** + * @return the region data backing this layout + */ + public TextTrackRegion getRegion() { + return mRegion; + } + } + + /** + * A text track cue is the unit of time-sensitive data in a text track, + * corresponding for instance for subtitles and captions to the text that + * appears at a particular time and disappears at another time. + * <p> + * A single cue may contain multiple {@link SpanLayout}s, each representing a + * single line of text. + */ + private static class CueLayout extends LinearLayout { + public final TextTrackCue mCue; + + private CaptionStyle mCaptionStyle; + private float mFontSize; + + private boolean mActive; + private int mOrder; + + public CueLayout( + Context context, TextTrackCue cue, CaptionStyle captionStyle, float fontSize) { + super(context); + + mCue = cue; + mCaptionStyle = captionStyle; + mFontSize = fontSize; + + // TODO: Add support for vertical text. + final boolean horizontal = cue.mWritingDirection + == TextTrackCue.WRITING_DIRECTION_HORIZONTAL; + setOrientation(horizontal ? VERTICAL : HORIZONTAL); + + switch (cue.mAlignment) { + case TextTrackCue.ALIGNMENT_END: + setGravity(Gravity.END); + break; + case TextTrackCue.ALIGNMENT_LEFT: + setGravity(Gravity.LEFT); + break; + case TextTrackCue.ALIGNMENT_MIDDLE: + setGravity(horizontal + ? Gravity.CENTER_HORIZONTAL : Gravity.CENTER_VERTICAL); + break; + case TextTrackCue.ALIGNMENT_RIGHT: + setGravity(Gravity.RIGHT); + break; + case TextTrackCue.ALIGNMENT_START: + setGravity(Gravity.START); + break; + } + + if (DEBUG) { + setBackgroundColor(DEBUG_CUE_BACKGROUND); + } + + update(); + } + + public void setCaptionStyle(CaptionStyle style, float fontSize) { + mCaptionStyle = style; + mFontSize = fontSize; + + final int n = getChildCount(); + for (int i = 0; i < n; i++) { + final View child = getChildAt(i); + if (child instanceof SpanLayout) { + ((SpanLayout) child).setCaptionStyle(style, fontSize); + } + } + } + + public void prepForPrune() { + mActive = false; + } + + public void update() { + mActive = true; + + removeAllViews(); + + final int cueAlignment = resolveCueAlignment(getLayoutDirection(), mCue.mAlignment); + final Alignment alignment; + switch (cueAlignment) { + case TextTrackCue.ALIGNMENT_LEFT: + alignment = Alignment.ALIGN_LEFT; + break; + case TextTrackCue.ALIGNMENT_RIGHT: + alignment = Alignment.ALIGN_RIGHT; + break; + case TextTrackCue.ALIGNMENT_MIDDLE: + default: + alignment = Alignment.ALIGN_CENTER; + } + + final CaptionStyle captionStyle = mCaptionStyle; + final float fontSize = mFontSize; + final TextTrackCueSpan[][] lines = mCue.mLines; + final int lineCount = lines.length; + for (int i = 0; i < lineCount; i++) { + final SpanLayout lineBox = new SpanLayout(getContext(), lines[i]); + lineBox.setAlignment(alignment); + lineBox.setCaptionStyle(captionStyle, fontSize); + + addView(lineBox, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + + /** + * Performs the parent's measurement responsibilities, then + * automatically performs its own measurement. + */ + public void measureForParent(int widthMeasureSpec, int heightMeasureSpec) { + final TextTrackCue cue = mCue; + final int specWidth = MeasureSpec.getSize(widthMeasureSpec); + final int specHeight = MeasureSpec.getSize(heightMeasureSpec); + final int direction = getLayoutDirection(); + final int absAlignment = resolveCueAlignment(direction, cue.mAlignment); + + // Determine the maximum size of cue based on its starting position + // and the direction in which it grows. + final int maximumSize; + switch (absAlignment) { + case TextTrackCue.ALIGNMENT_LEFT: + maximumSize = 100 - cue.mTextPosition; + break; + case TextTrackCue.ALIGNMENT_RIGHT: + maximumSize = cue.mTextPosition; + break; + case TextTrackCue.ALIGNMENT_MIDDLE: + if (cue.mTextPosition <= 50) { + maximumSize = cue.mTextPosition * 2; + } else { + maximumSize = (100 - cue.mTextPosition) * 2; + } + break; + default: + maximumSize = 0; + } + + // Determine absolute maximum cue size as the smaller of the + // requested size and the maximum theoretical size. + final int size = Math.min(cue.mSize, maximumSize) * specWidth / 100; + widthMeasureSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST); + heightMeasureSpec = MeasureSpec.makeMeasureSpec(specHeight, MeasureSpec.AT_MOST); + measure(widthMeasureSpec, heightMeasureSpec); + } + + /** + * Sets the order of this cue in the list of active cues. + * + * @param order the order of this cue in the list of active cues + */ + public void setOrder(int order) { + mOrder = order; + } + + /** + * @return whether this cue is marked as active + */ + public boolean isActive() { + return mActive; + } + + /** + * @return the cue data backing this layout + */ + public TextTrackCue getCue() { + return mCue; + } + } + + /** + * A text track line represents a single line of text within a cue. + * <p> + * A single line may contain multiple spans, each representing a section of + * text that may be enabled or disabled at a particular time. + */ + private static class SpanLayout extends SubtitleView { + private final SpannableStringBuilder mBuilder = new SpannableStringBuilder(); + private final TextTrackCueSpan[] mSpans; + + public SpanLayout(Context context, TextTrackCueSpan[] spans) { + super(context); + + mSpans = spans; + + update(); + } + + public void update() { + final SpannableStringBuilder builder = mBuilder; + final TextTrackCueSpan[] spans = mSpans; + + builder.clear(); + builder.clearSpans(); + + final int spanCount = spans.length; + for (int i = 0; i < spanCount; i++) { + final TextTrackCueSpan span = spans[i]; + if (span.mEnabled) { + builder.append(spans[i].mText); + } + } + + setText(builder); + } + + public void setCaptionStyle(CaptionStyle captionStyle, float fontSize) { + setBackgroundColor(captionStyle.backgroundColor); + setForegroundColor(captionStyle.foregroundColor); + setEdgeColor(captionStyle.edgeColor); + setEdgeType(captionStyle.edgeType); + setTypeface(captionStyle.getTypeface()); + setTextSize(fontSize); + } + } +} diff --git a/media/java/android/media/audiofx/AudioEffect.java b/media/java/android/media/audiofx/AudioEffect.java index 52c0c2d..12f7bd9 100644 --- a/media/java/android/media/audiofx/AudioEffect.java +++ b/media/java/android/media/audiofx/AudioEffect.java @@ -120,6 +120,14 @@ public class AudioEffect { .fromString("58b4b260-8e06-11e0-aa8e-0002a5d5c51b"); /** + * @hide + * CANDIDATE FOR PUBLIC API + * UUID for Loudness Enhancer + */ + public static final UUID EFFECT_TYPE_LOUDNESS_ENHANCER = UUID + .fromString("fe3199be-aed0-413f-87bb-11260eb63cf1"); + + /** * Null effect UUID. Used when the UUID for effect type of * @hide */ diff --git a/media/java/android/media/audiofx/LoudnessEnhancer.java b/media/java/android/media/audiofx/LoudnessEnhancer.java new file mode 100644 index 0000000..eb2fb75 --- /dev/null +++ b/media/java/android/media/audiofx/LoudnessEnhancer.java @@ -0,0 +1,291 @@ +/* + * Copyright (C) 2013 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.audiofx; + +import android.media.AudioTrack; +import android.media.MediaPlayer; +import android.media.audiofx.AudioEffect; +import android.util.Log; + +import java.util.StringTokenizer; + + +/** + * LoudnessEnhancer is an audio effect for increasing audio loudness. + * The processing is parametrized by a target gain value, which determines the maximum amount + * by which an audio signal will be amplified; signals amplified outside of the sample + * range supported by the platform are compressed. + * An application creates a LoudnessEnhancer object to instantiate and control a + * this audio effect in the audio framework. + * To attach the LoudnessEnhancer to a particular AudioTrack or MediaPlayer, + * specify the audio session ID of this AudioTrack or MediaPlayer when constructing the effect + * (see {@link AudioTrack#getAudioSessionId()} and {@link MediaPlayer#getAudioSessionId()}). + */ + +public class LoudnessEnhancer extends AudioEffect { + + private final static String TAG = "LoudnessEnhancer"; + + // These parameter constants must be synchronized with those in + // /system/media/audio_effects/include/audio_effects/effect_loudnessenhancer.h + /** + * The maximum gain applied applied to the signal to process. + * It is expressed in millibels (100mB = 1dB) where 0mB corresponds to no amplification. + */ + public static final int PARAM_TARGET_GAIN_MB = 0; + + /** + * Registered listener for parameter changes. + */ + private OnParameterChangeListener mParamListener = null; + + /** + * Listener used internally to to receive raw parameter change events + * from AudioEffect super class + */ + private BaseParameterListener mBaseParamListener = null; + + /** + * Lock for access to mParamListener + */ + private final Object mParamListenerLock = new Object(); + + /** + * @hide + * Class constructor. + * @param audioSession system-wide unique audio session identifier. The LoudnessEnhancer + * will be attached to the MediaPlayer or AudioTrack in the same audio session. + * + * @throws java.lang.IllegalStateException + * @throws java.lang.IllegalArgumentException + * @throws java.lang.UnsupportedOperationException + * @throws java.lang.RuntimeException + */ + public LoudnessEnhancer(int audioSession) + throws IllegalStateException, IllegalArgumentException, + UnsupportedOperationException, RuntimeException { + super(EFFECT_TYPE_LOUDNESS_ENHANCER, EFFECT_TYPE_NULL, 0, audioSession); + + if (audioSession == 0) { + Log.w(TAG, "WARNING: attaching a LoudnessEnhancer to global output mix is deprecated!"); + } + } + + /** + * @hide + * Class constructor for the LoudnessEnhancer audio effect. + * @param priority the priority level requested by the application for controlling the + * LoudnessEnhancer engine. As the same engine can be shared by several applications, + * this parameter indicates how much the requesting application needs control of effect + * parameters. The normal priority is 0, above normal is a positive number, below normal a + * negative number. + * @param audioSession system-wide unique audio session identifier. The LoudnessEnhancer + * will be attached to the MediaPlayer or AudioTrack in the same audio session. + * + * @throws java.lang.IllegalStateException + * @throws java.lang.IllegalArgumentException + * @throws java.lang.UnsupportedOperationException + * @throws java.lang.RuntimeException + */ + public LoudnessEnhancer(int priority, int audioSession) + throws IllegalStateException, IllegalArgumentException, + UnsupportedOperationException, RuntimeException { + super(EFFECT_TYPE_LOUDNESS_ENHANCER, EFFECT_TYPE_NULL, priority, audioSession); + + if (audioSession == 0) { + Log.w(TAG, "WARNING: attaching a LoudnessEnhancer to global output mix is deprecated!"); + } + } + + /** + * Set the target gain for the audio effect. + * The target gain is the maximum value by which a sample value will be amplified when the + * effect is enabled. + * @param gainmB the effect target gain expressed in mB. 0mB corresponds to no amplification. + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public void setTargetGain(int gainmB) + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + checkStatus(setParameter(PARAM_TARGET_GAIN_MB, gainmB)); + } + + /** + * Return the target gain. + * @return the effect target gain expressed in mB. + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public float getTargetGain() + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + int[] value = new int[1]; + checkStatus(getParameter(PARAM_TARGET_GAIN_MB, value)); + return value[0]; + } + + /** + * @hide + * The OnParameterChangeListener interface defines a method called by the LoudnessEnhancer + * when a parameter value has changed. + */ + public interface OnParameterChangeListener { + /** + * Method called when a parameter value has changed. The method is called only if the + * parameter was changed by another application having the control of the same + * LoudnessEnhancer engine. + * @param effect the LoudnessEnhancer on which the interface is registered. + * @param param ID of the modified parameter. See {@link #PARAM_GENERIC_PARAM1} ... + * @param value the new parameter value. + */ + void onParameterChange(LoudnessEnhancer effect, int param, int value); + } + + /** + * Listener used internally to receive unformatted parameter change events from AudioEffect + * super class. + */ + private class BaseParameterListener implements AudioEffect.OnParameterChangeListener { + private BaseParameterListener() { + + } + public void onParameterChange(AudioEffect effect, int status, byte[] param, byte[] value) { + // only notify when the parameter was successfully change + if (status != AudioEffect.SUCCESS) { + return; + } + OnParameterChangeListener l = null; + synchronized (mParamListenerLock) { + if (mParamListener != null) { + l = mParamListener; + } + } + if (l != null) { + int p = -1; + int v = Integer.MIN_VALUE; + + if (param.length == 4) { + p = byteArrayToInt(param, 0); + } + if (value.length == 4) { + v = byteArrayToInt(value, 0); + } + if (p != -1 && v != Integer.MIN_VALUE) { + l.onParameterChange(LoudnessEnhancer.this, p, v); + } + } + } + } + + /** + * @hide + * Registers an OnParameterChangeListener interface. + * @param listener OnParameterChangeListener interface registered + */ + public void setParameterListener(OnParameterChangeListener listener) { + synchronized (mParamListenerLock) { + if (mParamListener == null) { + mBaseParamListener = new BaseParameterListener(); + super.setParameterListener(mBaseParamListener); + } + mParamListener = listener; + } + } + + /** + * @hide + * The Settings class regroups the LoudnessEnhancer parameters. It is used in + * conjunction with the getProperties() and setProperties() methods to backup and restore + * all parameters in a single call. + */ + public static class Settings { + public int targetGainmB; + + public Settings() { + } + + /** + * Settings class constructor from a key=value; pairs formatted string. The string is + * typically returned by Settings.toString() method. + * @throws IllegalArgumentException if the string is not correctly formatted. + */ + public Settings(String settings) { + StringTokenizer st = new StringTokenizer(settings, "=;"); + //int tokens = st.countTokens(); + if (st.countTokens() != 3) { + throw new IllegalArgumentException("settings: " + settings); + } + String key = st.nextToken(); + if (!key.equals("LoudnessEnhancer")) { + throw new IllegalArgumentException( + "invalid settings for LoudnessEnhancer: " + key); + } + try { + key = st.nextToken(); + if (!key.equals("targetGainmB")) { + throw new IllegalArgumentException("invalid key name: " + key); + } + targetGainmB = Integer.parseInt(st.nextToken()); + } catch (NumberFormatException nfe) { + throw new IllegalArgumentException("invalid value for key: " + key); + } + } + + @Override + public String toString() { + String str = new String ( + "LoudnessEnhancer"+ + ";targetGainmB="+Integer.toString(targetGainmB) + ); + return str; + } + }; + + + /** + * @hide + * Gets the LoudnessEnhancer properties. This method is useful when a snapshot of current + * effect settings must be saved by the application. + * @return a LoudnessEnhancer.Settings object containing all current parameters values + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public LoudnessEnhancer.Settings getProperties() + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + Settings settings = new Settings(); + int[] value = new int[1]; + checkStatus(getParameter(PARAM_TARGET_GAIN_MB, value)); + settings.targetGainmB = value[0]; + return settings; + } + + /** + * @hide + * Sets the LoudnessEnhancer properties. This method is useful when bass boost settings + * have to be applied from a previous backup. + * @param settings a LoudnessEnhancer.Settings object containing the properties to apply + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public void setProperties(LoudnessEnhancer.Settings settings) + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + checkStatus(setParameter(PARAM_TARGET_GAIN_MB, settings.targetGainmB)); + } +} diff --git a/media/java/android/media/audiofx/Visualizer.java b/media/java/android/media/audiofx/Visualizer.java index 9197ed8..fb7f718 100644 --- a/media/java/android/media/audiofx/Visualizer.java +++ b/media/java/android/media/audiofx/Visualizer.java @@ -57,6 +57,11 @@ import android.os.Message; * anymore to free up native resources associated to the Visualizer instance. * <p>Creating a Visualizer on the output mix (audio session 0) requires permission * {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS} + * <p>The Visualizer class can also be used to perform measurements on the audio being played back. + * The measurements to perform are defined by setting a mask of the requested measurement modes with + * {@link #setMeasurementMode(int)}. Supported values are {@link #MEASUREMENT_MODE_NONE} to cancel + * any measurement, and {@link #MEASUREMENT_MODE_PEAK_RMS} for peak and RMS monitoring. + * Measurements can be retrieved through {@link #getMeasurementPeakRms(MeasurementPeakRms)}. */ public class Visualizer { @@ -93,6 +98,19 @@ public class Visualizer { */ public static final int SCALING_MODE_AS_PLAYED = 1; + /** + * Defines a measurement mode in which no measurements are performed. + */ + public static final int MEASUREMENT_MODE_NONE = 0; + + /** + * Defines a measurement mode which computes the peak and RMS value in mB, where 0mB is the + * maximum sample value, and -9600mB is the minimum value. + * Values for peak and RMS can be retrieved with + * {@link #getMeasurementPeakRms(MeasurementPeakRms)}. + */ + public static final int MEASUREMENT_MODE_PEAK_RMS = 1 << 0; + // to keep in sync with frameworks/base/media/jni/audioeffect/android_media_Visualizer.cpp private static final int NATIVE_EVENT_PCM_CAPTURE = 0; private static final int NATIVE_EVENT_FFT_CAPTURE = 1; @@ -350,6 +368,43 @@ public class Visualizer { } /** + * Sets the combination of measurement modes to be performed by this audio effect. + * @param mode a mask of the measurements to perform. The valid values are + * {@link #MEASUREMENT_MODE_NONE} (to cancel any measurement) + * or {@link #MEASUREMENT_MODE_PEAK_RMS}. + * @return {@link #SUCCESS} in case of success, {@link #ERROR_BAD_VALUE} in case of failure. + * @throws IllegalStateException + */ + public int setMeasurementMode(int mode) + throws IllegalStateException { + synchronized (mStateLock) { + if (mState == STATE_UNINITIALIZED) { + throw(new IllegalStateException("setMeasurementMode() called in wrong state: " + + mState)); + } + return native_setMeasurementMode(mode); + } + } + + /** + * Returns the current measurement modes performed by this audio effect + * @return the mask of the measurements, + * {@link #MEASUREMENT_MODE_NONE} (when no measurements are performed) + * or {@link #MEASUREMENT_MODE_PEAK_RMS}. + * @throws IllegalStateException + */ + public int getMeasurementMode() + throws IllegalStateException { + synchronized (mStateLock) { + if (mState == STATE_UNINITIALIZED) { + throw(new IllegalStateException("getMeasurementMode() called in wrong state: " + + mState)); + } + return native_getMeasurementMode(); + } + } + + /** * Returns the sampling rate of the captured audio. * @return the sampling rate in milliHertz. */ @@ -437,6 +492,46 @@ public class Visualizer { } } + /** + * A class to store peak and RMS values. + * Peak and RMS are expressed in mB, as described in the + * {@link Visualizer#MEASUREMENT_MODE_PEAK_RMS} measurement mode. + */ + public static final class MeasurementPeakRms { + /** + * The peak value in mB. + */ + public int mPeak; + /** + * The RMS value in mB. + */ + public int mRms; + } + + /** + * Retrieves the latest peak and RMS measurement. + * Sets the peak and RMS fields of the supplied {@link Visualizer.MeasurementPeakRms} to the + * latest measured values. + * @param measurement a non-null {@link Visualizer.MeasurementPeakRms} instance to store + * the measurement values. + * @return {@link #SUCCESS} in case of success, {@link #ERROR_BAD_VALUE}, + * {@link #ERROR_NO_MEMORY}, {@link #ERROR_INVALID_OPERATION} or {@link #ERROR_DEAD_OBJECT} + * in case of failure. + */ + public int getMeasurementPeakRms(MeasurementPeakRms measurement) { + if (measurement == null) { + Log.e(TAG, "Cannot store measurements in a null object"); + return ERROR_BAD_VALUE; + } + synchronized (mStateLock) { + if (mState != STATE_ENABLED) { + throw (new IllegalStateException("getMeasurementPeakRms() called in wrong state: " + + mState)); + } + return native_getPeakRms(measurement); + } + } + //--------------------------------------------------------- // Interface definitions //-------------------- @@ -640,12 +735,18 @@ public class Visualizer { private native final int native_getScalingMode(); + private native final int native_setMeasurementMode(int mode); + + private native final int native_getMeasurementMode(); + private native final int native_getSamplingRate(); private native final int native_getWaveForm(byte[] waveform); private native final int native_getFft(byte[] fft); + private native final int native_getPeakRms(MeasurementPeakRms measurement); + private native final int native_setPeriodicCapture(int rate, boolean waveForm, boolean fft); //--------------------------------------------------------- diff --git a/media/jni/Android.mk b/media/jni/Android.mk index 416a2a1..63a61e2 100644 --- a/media/jni/Android.mk +++ b/media/jni/Android.mk @@ -2,6 +2,7 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ + android_media_ImageReader.cpp \ android_media_MediaCrypto.cpp \ android_media_MediaCodec.cpp \ android_media_MediaCodecList.cpp \ @@ -56,6 +57,8 @@ LOCAL_C_INCLUDES += \ frameworks/av/media/libstagefright/codecs/amrnb/common/include \ frameworks/av/media/mtp \ frameworks/native/include/media/openmax \ + $(call include-path-for, libhardware)/hardware \ + system/media/camera/include \ $(PV_INCLUDES) \ $(JNI_H_INCLUDE) \ $(call include-path-for, corecg graphics) diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp new file mode 100644 index 0000000..0030dbd --- /dev/null +++ b/media/jni/android_media_ImageReader.cpp @@ -0,0 +1,879 @@ +/* + * Copyright 2013 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. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "ImageReader_JNI" +#include <utils/Log.h> +#include <utils/misc.h> +#include <utils/List.h> +#include <utils/String8.h> + +#include <cstdio> + +#include <gui/CpuConsumer.h> +#include <gui/Surface.h> +#include <camera3.h> + +#include <android_runtime/AndroidRuntime.h> +#include <android_runtime/android_view_Surface.h> + +#include <jni.h> +#include <JNIHelp.h> + +#define ALIGN(x, mask) ( ((x) + (mask) - 1) & ~((mask) - 1) ) + +#define ANDROID_MEDIA_IMAGEREADER_CTX_JNI_ID "mNativeContext" +#define ANDROID_MEDIA_SURFACEIMAGE_BUFFER_JNI_ID "mLockedBuffer" +#define ANDROID_MEDIA_SURFACEIMAGE_TS_JNI_ID "mTimestamp" + +// ---------------------------------------------------------------------------- + +using namespace android; + +enum { + IMAGE_READER_MAX_NUM_PLANES = 3, +}; + +enum { + ACQUIRE_SUCCESS = 0, + ACQUIRE_NO_BUFFERS = 1, + ACQUIRE_MAX_IMAGES = 2, +}; + +static struct { + jfieldID mNativeContext; + jmethodID postEventFromNative; +} gImageReaderClassInfo; + +static struct { + jfieldID mLockedBuffer; + jfieldID mTimestamp; +} gSurfaceImageClassInfo; + +static struct { + jclass clazz; + jmethodID ctor; +} gSurfacePlaneClassInfo; + +// ---------------------------------------------------------------------------- + +class JNIImageReaderContext : public CpuConsumer::FrameAvailableListener +{ +public: + JNIImageReaderContext(JNIEnv* env, jobject weakThiz, jclass clazz, int maxImages); + + virtual ~JNIImageReaderContext(); + + virtual void onFrameAvailable(); + + CpuConsumer::LockedBuffer* getLockedBuffer(); + + void returnLockedBuffer(CpuConsumer::LockedBuffer* buffer); + + void setCpuConsumer(const sp<CpuConsumer>& consumer) { mConsumer = consumer; } + CpuConsumer* getCpuConsumer() { return mConsumer.get(); } + + void setBufferQueue(const sp<BufferQueue>& bq) { mBufferQueue = bq; } + BufferQueue* getBufferQueue() { return mBufferQueue.get(); } + + void setBufferFormat(int format) { mFormat = format; } + int getBufferFormat() { return mFormat; } + + void setBufferWidth(int width) { mWidth = width; } + int getBufferWidth() { return mWidth; } + + void setBufferHeight(int height) { mHeight = height; } + int getBufferHeight() { return mHeight; } + +private: + static JNIEnv* getJNIEnv(bool* needsDetach); + static void detachJNI(); + + List<CpuConsumer::LockedBuffer*> mBuffers; + sp<CpuConsumer> mConsumer; + sp<BufferQueue> mBufferQueue; + jobject mWeakThiz; + jclass mClazz; + int mFormat; + int mWidth; + int mHeight; +}; + +JNIImageReaderContext::JNIImageReaderContext(JNIEnv* env, + jobject weakThiz, jclass clazz, int maxImages) : + mWeakThiz(env->NewGlobalRef(weakThiz)), + mClazz((jclass)env->NewGlobalRef(clazz)) { + for (int i = 0; i < maxImages; i++) { + CpuConsumer::LockedBuffer *buffer = new CpuConsumer::LockedBuffer; + mBuffers.push_back(buffer); + } +} + +JNIEnv* JNIImageReaderContext::getJNIEnv(bool* needsDetach) { + LOG_ALWAYS_FATAL_IF(needsDetach == NULL, "needsDetach is null!!!"); + *needsDetach = false; + JNIEnv* env = AndroidRuntime::getJNIEnv(); + if (env == NULL) { + JavaVMAttachArgs args = {JNI_VERSION_1_4, NULL, NULL}; + JavaVM* vm = AndroidRuntime::getJavaVM(); + int result = vm->AttachCurrentThread(&env, (void*) &args); + if (result != JNI_OK) { + ALOGE("thread attach failed: %#x", result); + return NULL; + } + *needsDetach = true; + } + return env; +} + +void JNIImageReaderContext::detachJNI() { + JavaVM* vm = AndroidRuntime::getJavaVM(); + int result = vm->DetachCurrentThread(); + if (result != JNI_OK) { + ALOGE("thread detach failed: %#x", result); + } +} + +CpuConsumer::LockedBuffer* JNIImageReaderContext::getLockedBuffer() { + if (mBuffers.empty()) { + return NULL; + } + // Return a LockedBuffer pointer and remove it from the list + List<CpuConsumer::LockedBuffer*>::iterator it = mBuffers.begin(); + CpuConsumer::LockedBuffer* buffer = *it; + mBuffers.erase(it); + return buffer; +} + +void JNIImageReaderContext::returnLockedBuffer(CpuConsumer::LockedBuffer* buffer) { + mBuffers.push_back(buffer); +} + +JNIImageReaderContext::~JNIImageReaderContext() { + bool needsDetach = false; + JNIEnv* env = getJNIEnv(&needsDetach); + if (env != NULL) { + env->DeleteGlobalRef(mWeakThiz); + env->DeleteGlobalRef(mClazz); + } else { + ALOGW("leaking JNI object references"); + } + if (needsDetach) { + detachJNI(); + } + + // Delete LockedBuffers + for (List<CpuConsumer::LockedBuffer *>::iterator it = mBuffers.begin(); + it != mBuffers.end(); it++) { + delete *it; + } + mBuffers.clear(); + mConsumer.clear(); +} + +void JNIImageReaderContext::onFrameAvailable() +{ + ALOGV("%s: frame available", __FUNCTION__); + bool needsDetach = false; + JNIEnv* env = getJNIEnv(&needsDetach); + if (env != NULL) { + env->CallStaticVoidMethod(mClazz, gImageReaderClassInfo.postEventFromNative, mWeakThiz); + } else { + ALOGW("onFrameAvailable event will not posted"); + } + if (needsDetach) { + detachJNI(); + } +} + +// ---------------------------------------------------------------------------- + +extern "C" { + +static JNIImageReaderContext* ImageReader_getContext(JNIEnv* env, jobject thiz) +{ + JNIImageReaderContext *ctx; + ctx = reinterpret_cast<JNIImageReaderContext *> + (env->GetLongField(thiz, gImageReaderClassInfo.mNativeContext)); + return ctx; +} + +static CpuConsumer* ImageReader_getCpuConsumer(JNIEnv* env, jobject thiz) +{ + ALOGV("%s:", __FUNCTION__); + JNIImageReaderContext* const ctx = ImageReader_getContext(env, thiz); + if (ctx == NULL) { + jniThrowRuntimeException(env, "ImageReaderContext is not initialized"); + return NULL; + } + return ctx->getCpuConsumer(); +} + +static BufferQueue* ImageReader_getBufferQueue(JNIEnv* env, jobject thiz) +{ + ALOGV("%s:", __FUNCTION__); + JNIImageReaderContext* const ctx = ImageReader_getContext(env, thiz); + if (ctx == NULL) { + jniThrowRuntimeException(env, "ImageReaderContext is not initialized"); + return NULL; + } + return ctx->getBufferQueue(); +} + +static void ImageReader_setNativeContext(JNIEnv* env, + jobject thiz, sp<JNIImageReaderContext> ctx) +{ + ALOGV("%s:", __FUNCTION__); + JNIImageReaderContext* const p = ImageReader_getContext(env, thiz); + if (ctx != 0) { + ctx->incStrong((void*)ImageReader_setNativeContext); + } + if (p) { + p->decStrong((void*)ImageReader_setNativeContext); + } + env->SetLongField(thiz, gImageReaderClassInfo.mNativeContext, + reinterpret_cast<jlong>(ctx.get())); +} + +static CpuConsumer::LockedBuffer* Image_getLockedBuffer(JNIEnv* env, jobject image) +{ + return reinterpret_cast<CpuConsumer::LockedBuffer*>( + env->GetLongField(image, gSurfaceImageClassInfo.mLockedBuffer)); +} + +static void Image_setBuffer(JNIEnv* env, jobject thiz, + const CpuConsumer::LockedBuffer* buffer) +{ + env->SetLongField(thiz, gSurfaceImageClassInfo.mLockedBuffer, reinterpret_cast<jlong>(buffer)); +} + +// Some formats like JPEG defined with different values between android.graphics.ImageFormat and +// graphics.h, need convert to the one defined in graphics.h here. +static int Image_getPixelFormat(JNIEnv* env, int format) +{ + int jpegFormat, rawSensorFormat; + jfieldID fid; + + ALOGV("%s: format = 0x%x", __FUNCTION__, format); + + jclass imageFormatClazz = env->FindClass("android/graphics/ImageFormat"); + ALOG_ASSERT(imageFormatClazz != NULL); + + fid = env->GetStaticFieldID(imageFormatClazz, "JPEG", "I"); + jpegFormat = env->GetStaticIntField(imageFormatClazz, fid); + fid = env->GetStaticFieldID(imageFormatClazz, "RAW_SENSOR", "I"); + rawSensorFormat = env->GetStaticIntField(imageFormatClazz, fid); + + // Translate the JPEG to BLOB for camera purpose, an add more if more mismatch is found. + if (format == jpegFormat) { + format = HAL_PIXEL_FORMAT_BLOB; + } + // Same thing for RAW_SENSOR format + if (format == rawSensorFormat) { + format = HAL_PIXEL_FORMAT_RAW_SENSOR; + } + + return format; +} + +static uint32_t Image_getJpegSize(CpuConsumer::LockedBuffer* buffer) +{ + ALOG_ASSERT(buffer != NULL, "Input buffer is NULL!!!"); + uint32_t size = 0; + uint32_t width = buffer->width; + uint8_t* jpegBuffer = buffer->data; + + // First check for JPEG transport header at the end of the buffer + uint8_t* header = jpegBuffer + (width - sizeof(struct camera3_jpeg_blob)); + struct camera3_jpeg_blob *blob = (struct camera3_jpeg_blob*)(header); + if (blob->jpeg_blob_id == CAMERA3_JPEG_BLOB_ID) { + size = blob->jpeg_size; + ALOGV("%s: Jpeg size = %d", __FUNCTION__, size); + } + + // failed to find size, default to whole buffer + if (size == 0) { + size = width; + } + + return size; +} + +static void Image_getLockedBufferInfo(JNIEnv* env, CpuConsumer::LockedBuffer* buffer, int idx, + uint8_t **base, uint32_t *size) +{ + ALOG_ASSERT(buffer != NULL, "Input buffer is NULL!!!"); + ALOG_ASSERT(base != NULL, "base is NULL!!!"); + ALOG_ASSERT(size != NULL, "size is NULL!!!"); + ALOG_ASSERT((idx < IMAGE_READER_MAX_NUM_PLANES) && (idx >= 0)); + + ALOGV("%s: buffer: %p", __FUNCTION__, buffer); + + uint32_t dataSize, ySize, cSize, cStride; + uint8_t *cb, *cr; + uint8_t *pData = NULL; + int bytesPerPixel = 0; + + dataSize = ySize = cSize = cStride = 0; + int32_t fmt = buffer->format; + switch (fmt) { + case HAL_PIXEL_FORMAT_YCbCr_420_888: + pData = + (idx == 0) ? + buffer->data : + (idx == 1) ? + buffer->dataCb : + buffer->dataCr; + if (idx == 0) { + dataSize = buffer->stride * buffer->height; + } else { + dataSize = buffer->chromaStride * buffer->height / 2; + } + break; + // NV21 + case HAL_PIXEL_FORMAT_YCrCb_420_SP: + cr = buffer->data + (buffer->stride * buffer->height); + cb = cr + 1; + ySize = buffer->width * buffer->height; + cSize = buffer->width * buffer->height / 2; + + pData = + (idx == 0) ? + buffer->data : + (idx == 1) ? + cb: + cr; + + dataSize = (idx == 0) ? ySize : cSize; + break; + case HAL_PIXEL_FORMAT_YV12: + // Y and C stride need to be 16 pixel aligned. + LOG_ALWAYS_FATAL_IF(buffer->stride % 16, + "Stride is not 16 pixel aligned %d", buffer->stride); + + ySize = buffer->stride * buffer->height; + cStride = ALIGN(buffer->stride / 2, 16); + cr = buffer->data + ySize; + cSize = cStride * buffer->height / 2; + cb = cr + cSize; + + pData = + (idx == 0) ? + buffer->data : + (idx == 1) ? + cb : + cr; + dataSize = (idx == 0) ? ySize : cSize; + break; + case HAL_PIXEL_FORMAT_Y8: + // Single plane, 8bpp. + ALOG_ASSERT(idx == 0, "Wrong index: %d", idx); + + pData = buffer->data; + dataSize = buffer->stride * buffer->height; + break; + case HAL_PIXEL_FORMAT_Y16: + // Single plane, 16bpp, strides are specified in pixels, not in bytes + ALOG_ASSERT(idx == 0, "Wrong index: %d", idx); + + pData = buffer->data; + dataSize = buffer->stride * buffer->height * 2; + break; + case HAL_PIXEL_FORMAT_BLOB: + // Used for JPEG data, height must be 1, width == size, single plane. + ALOG_ASSERT(idx == 0, "Wrong index: %d", idx); + ALOG_ASSERT(buffer->height == 1, "JPEG should has height value %d", buffer->height); + + pData = buffer->data; + dataSize = Image_getJpegSize(buffer); + break; + case HAL_PIXEL_FORMAT_RAW_SENSOR: + // Single plane 16bpp bayer data. + ALOG_ASSERT(idx == 0, "Wrong index: %d", idx); + pData = buffer->data; + dataSize = buffer->width * 2 * buffer->height; + break; + case HAL_PIXEL_FORMAT_RGBA_8888: + case HAL_PIXEL_FORMAT_RGBX_8888: + // Single plane, 32bpp. + bytesPerPixel = 4; + ALOG_ASSERT(idx == 0, "Wrong index: %d", idx); + pData = buffer->data; + dataSize = buffer->stride * buffer->height * bytesPerPixel; + break; + case HAL_PIXEL_FORMAT_RGB_565: + // Single plane, 16bpp. + bytesPerPixel = 2; + ALOG_ASSERT(idx == 0, "Wrong index: %d", idx); + pData = buffer->data; + dataSize = buffer->stride * buffer->height * bytesPerPixel; + break; + case HAL_PIXEL_FORMAT_RGB_888: + // Single plane, 24bpp. + bytesPerPixel = 3; + ALOG_ASSERT(idx == 0, "Wrong index: %d", idx); + pData = buffer->data; + dataSize = buffer->stride * buffer->height * bytesPerPixel; + break; + default: + jniThrowExceptionFmt(env, "java/lang/UnsupportedOperationException", + "Pixel format: 0x%x is unsupported", fmt); + break; + } + + *base = pData; + *size = dataSize; +} + +static jint Image_imageGetPixelStride(JNIEnv* env, CpuConsumer::LockedBuffer* buffer, int idx) +{ + ALOGV("%s: buffer index: %d", __FUNCTION__, idx); + ALOG_ASSERT((idx < IMAGE_READER_MAX_NUM_PLANES) && (idx >= 0), "Index is out of range:%d", idx); + + int pixelStride = 0; + ALOG_ASSERT(buffer != NULL, "buffer is NULL"); + + int32_t fmt = buffer->format; + switch (fmt) { + case HAL_PIXEL_FORMAT_YCbCr_420_888: + pixelStride = (idx == 0) ? 1 : buffer->chromaStep; + break; + case HAL_PIXEL_FORMAT_YCrCb_420_SP: + pixelStride = (idx == 0) ? 1 : 2; + break; + case HAL_PIXEL_FORMAT_Y8: + // Single plane 8bpp data. + ALOG_ASSERT(idx == 0, "Wrong index: %d", idx); + pixelStride; + break; + case HAL_PIXEL_FORMAT_YV12: + pixelStride = 1; + break; + case HAL_PIXEL_FORMAT_BLOB: + // Used for JPEG data, single plane, row and pixel strides are 0 + ALOG_ASSERT(idx == 0, "Wrong index: %d", idx); + pixelStride = 0; + break; + case HAL_PIXEL_FORMAT_Y16: + case HAL_PIXEL_FORMAT_RAW_SENSOR: + case HAL_PIXEL_FORMAT_RGB_565: + // Single plane 16bpp data. + ALOG_ASSERT(idx == 0, "Wrong index: %d", idx); + pixelStride = 2; + break; + case HAL_PIXEL_FORMAT_RGBA_8888: + case HAL_PIXEL_FORMAT_RGBX_8888: + ALOG_ASSERT(idx == 0, "Wrong index: %d", idx); + pixelStride = 4; + break; + case HAL_PIXEL_FORMAT_RGB_888: + // Single plane, 24bpp. + ALOG_ASSERT(idx == 0, "Wrong index: %d", idx); + pixelStride = 3; + break; + default: + jniThrowExceptionFmt(env, "java/lang/UnsupportedOperationException", + "Pixel format: 0x%x is unsupported", fmt); + break; + } + + return pixelStride; +} + +static jint Image_imageGetRowStride(JNIEnv* env, CpuConsumer::LockedBuffer* buffer, int idx) +{ + ALOGV("%s: buffer index: %d", __FUNCTION__, idx); + ALOG_ASSERT((idx < IMAGE_READER_MAX_NUM_PLANES) && (idx >= 0)); + + int rowStride = 0; + ALOG_ASSERT(buffer != NULL, "buffer is NULL"); + + int32_t fmt = buffer->format; + + switch (fmt) { + case HAL_PIXEL_FORMAT_YCbCr_420_888: + rowStride = (idx == 0) ? buffer->stride : buffer->chromaStride; + break; + case HAL_PIXEL_FORMAT_YCrCb_420_SP: + rowStride = buffer->width; + break; + case HAL_PIXEL_FORMAT_YV12: + LOG_ALWAYS_FATAL_IF(buffer->stride % 16, + "Stride is not 16 pixel aligned %d", buffer->stride); + rowStride = (idx == 0) ? buffer->stride : ALIGN(buffer->stride / 2, 16); + break; + case HAL_PIXEL_FORMAT_BLOB: + // Used for JPEG data, single plane, row and pixel strides are 0 + ALOG_ASSERT(idx == 0, "Wrong index: %d", idx); + rowStride = 0; + break; + case HAL_PIXEL_FORMAT_Y8: + ALOG_ASSERT(idx == 0, "Wrong index: %d", idx); + LOG_ALWAYS_FATAL_IF(buffer->stride % 16, + "Stride is not 16 pixel aligned %d", buffer->stride); + rowStride = buffer->stride; + break; + case HAL_PIXEL_FORMAT_Y16: + case HAL_PIXEL_FORMAT_RAW_SENSOR: + // In native side, strides are specified in pixels, not in bytes. + // Single plane 16bpp bayer data. even width/height, + // row stride multiple of 16 pixels (32 bytes) + ALOG_ASSERT(idx == 0, "Wrong index: %d", idx); + LOG_ALWAYS_FATAL_IF(buffer->stride % 16, + "Stride is not 16 pixel aligned %d", buffer->stride); + rowStride = buffer->stride * 2; + break; + case HAL_PIXEL_FORMAT_RGB_565: + ALOG_ASSERT(idx == 0, "Wrong index: %d", idx); + rowStride = buffer->stride * 2; + break; + case HAL_PIXEL_FORMAT_RGBA_8888: + case HAL_PIXEL_FORMAT_RGBX_8888: + ALOG_ASSERT(idx == 0, "Wrong index: %d", idx); + rowStride = buffer->stride * 4; + break; + case HAL_PIXEL_FORMAT_RGB_888: + // Single plane, 24bpp. + ALOG_ASSERT(idx == 0, "Wrong index: %d", idx); + rowStride = buffer->stride * 3; + break; + default: + ALOGE("%s Pixel format: 0x%x is unsupported", __FUNCTION__, fmt); + jniThrowException(env, "java/lang/UnsupportedOperationException", + "unsupported buffer format"); + break; + } + + return rowStride; +} + +// ---------------------------------------------------------------------------- + +static void ImageReader_classInit(JNIEnv* env, jclass clazz) +{ + ALOGV("%s:", __FUNCTION__); + + jclass imageClazz = env->FindClass("android/media/ImageReader$SurfaceImage"); + LOG_ALWAYS_FATAL_IF(imageClazz == NULL, + "can't find android/graphics/ImageReader$SurfaceImage"); + gSurfaceImageClassInfo.mLockedBuffer = env->GetFieldID( + imageClazz, ANDROID_MEDIA_SURFACEIMAGE_BUFFER_JNI_ID, "J"); + LOG_ALWAYS_FATAL_IF(gSurfaceImageClassInfo.mLockedBuffer == NULL, + "can't find android/graphics/ImageReader.%s", + ANDROID_MEDIA_SURFACEIMAGE_BUFFER_JNI_ID); + + gSurfaceImageClassInfo.mTimestamp = env->GetFieldID( + imageClazz, ANDROID_MEDIA_SURFACEIMAGE_TS_JNI_ID, "J"); + LOG_ALWAYS_FATAL_IF(gSurfaceImageClassInfo.mTimestamp == NULL, + "can't find android/graphics/ImageReader.%s", + ANDROID_MEDIA_SURFACEIMAGE_TS_JNI_ID); + + gImageReaderClassInfo.mNativeContext = env->GetFieldID( + clazz, ANDROID_MEDIA_IMAGEREADER_CTX_JNI_ID, "J"); + LOG_ALWAYS_FATAL_IF(gImageReaderClassInfo.mNativeContext == NULL, + "can't find android/graphics/ImageReader.%s", + ANDROID_MEDIA_IMAGEREADER_CTX_JNI_ID); + + gImageReaderClassInfo.postEventFromNative = env->GetStaticMethodID( + clazz, "postEventFromNative", "(Ljava/lang/Object;)V"); + LOG_ALWAYS_FATAL_IF(gImageReaderClassInfo.postEventFromNative == NULL, + "can't find android/graphics/ImageReader.postEventFromNative"); + + jclass planeClazz = env->FindClass("android/media/ImageReader$SurfaceImage$SurfacePlane"); + LOG_ALWAYS_FATAL_IF(planeClazz == NULL, "Can not find SurfacePlane class"); + // FindClass only gives a local reference of jclass object. + gSurfacePlaneClassInfo.clazz = (jclass) env->NewGlobalRef(planeClazz); + gSurfacePlaneClassInfo.ctor = env->GetMethodID(gSurfacePlaneClassInfo.clazz, "<init>", + "(Landroid/media/ImageReader$SurfaceImage;III)V"); + LOG_ALWAYS_FATAL_IF(gSurfacePlaneClassInfo.ctor == NULL, + "Can not find SurfacePlane constructor"); +} + +static void ImageReader_init(JNIEnv* env, jobject thiz, jobject weakThiz, + jint width, jint height, jint format, jint maxImages) +{ + status_t res; + int nativeFormat; + + ALOGV("%s: width:%d, height: %d, format: 0x%x, maxImages:%d", + __FUNCTION__, width, height, format, maxImages); + + nativeFormat = Image_getPixelFormat(env, format); + + sp<BufferQueue> bq = new BufferQueue(); + sp<CpuConsumer> consumer = new CpuConsumer(bq, maxImages, + /*controlledByApp*/true); + // TODO: throw dvm exOutOfMemoryError? + if (consumer == NULL) { + jniThrowRuntimeException(env, "Failed to allocate native CpuConsumer"); + return; + } + + jclass clazz = env->GetObjectClass(thiz); + if (clazz == NULL) { + jniThrowRuntimeException(env, "Can't find android/graphics/ImageReader"); + return; + } + sp<JNIImageReaderContext> ctx(new JNIImageReaderContext(env, weakThiz, clazz, maxImages)); + ctx->setCpuConsumer(consumer); + ctx->setBufferQueue(bq); + consumer->setFrameAvailableListener(ctx); + ImageReader_setNativeContext(env, thiz, ctx); + ctx->setBufferFormat(nativeFormat); + ctx->setBufferWidth(width); + ctx->setBufferHeight(height); + + // Set the width/height/format to the CpuConsumer + res = consumer->setDefaultBufferSize(width, height); + if (res != OK) { + jniThrowException(env, "java/lang/IllegalStateException", + "Failed to set CpuConsumer buffer size"); + return; + } + res = consumer->setDefaultBufferFormat(nativeFormat); + if (res != OK) { + jniThrowException(env, "java/lang/IllegalStateException", + "Failed to set CpuConsumer buffer format"); + } +} + +static void ImageReader_close(JNIEnv* env, jobject thiz) +{ + ALOGV("%s:", __FUNCTION__); + + JNIImageReaderContext* const ctx = ImageReader_getContext(env, thiz); + if (ctx == NULL) { + // ImageReader is already closed. + return; + } + + CpuConsumer* consumer = ImageReader_getCpuConsumer(env, thiz); + if (consumer != NULL) { + consumer->abandon(); + consumer->setFrameAvailableListener(NULL); + } + ImageReader_setNativeContext(env, thiz, NULL); +} + +static void ImageReader_imageRelease(JNIEnv* env, jobject thiz, jobject image) +{ + ALOGV("%s:", __FUNCTION__); + JNIImageReaderContext* ctx = ImageReader_getContext(env, thiz); + if (ctx == NULL) { + ALOGW("ImageReader#close called before Image#close, consider calling Image#close first"); + return; + } + + CpuConsumer* consumer = ctx->getCpuConsumer(); + CpuConsumer::LockedBuffer* buffer = Image_getLockedBuffer(env, image); + if (!buffer) { + ALOGW("Image already released!!!"); + return; + } + consumer->unlockBuffer(*buffer); + Image_setBuffer(env, image, NULL); + ctx->returnLockedBuffer(buffer); +} + +static jint ImageReader_imageSetup(JNIEnv* env, jobject thiz, + jobject image) +{ + ALOGV("%s:", __FUNCTION__); + JNIImageReaderContext* ctx = ImageReader_getContext(env, thiz); + if (ctx == NULL) { + jniThrowRuntimeException(env, "ImageReaderContext is not initialized"); + return -1; + } + + CpuConsumer* consumer = ctx->getCpuConsumer(); + CpuConsumer::LockedBuffer* buffer = ctx->getLockedBuffer(); + if (buffer == NULL) { + ALOGW("Unable to acquire a lockedBuffer, very likely client tries to lock more than" + " maxImages buffers"); + return ACQUIRE_MAX_IMAGES; + } + status_t res = consumer->lockNextBuffer(buffer); + if (res != NO_ERROR) { + if (res != BAD_VALUE /*no buffers*/) { + if (res == NOT_ENOUGH_DATA) { + return ACQUIRE_MAX_IMAGES; + } else { + ALOGE("%s Fail to lockNextBuffer with error: %d ", + __FUNCTION__, res); + jniThrowExceptionFmt(env, "java/lang/AssertionError", + "Unknown error (%d) when we tried to lock buffer.", + res); + } + } + return ACQUIRE_NO_BUFFERS; + } + + if (buffer->format == HAL_PIXEL_FORMAT_YCrCb_420_SP) { + jniThrowException(env, "java/lang/UnsupportedOperationException", + "NV21 format is not supported by ImageReader"); + return -1; + } + + // Check if the left-top corner of the crop rect is origin, we currently assume this point is + // zero, will revist this once this assumption turns out problematic. + Point lt = buffer->crop.leftTop(); + if (lt.x != 0 || lt.y != 0) { + jniThrowExceptionFmt(env, "java/lang/UnsupportedOperationException", + "crop left top corner [%d, %d] need to be at origin", lt.x, lt.y); + return -1; + } + + // Check if the producer buffer configurations match what ImageReader configured. + // We want to fail for the very first image because this case is too bad. + int outputWidth = buffer->width; + int outputHeight = buffer->height; + + // Correct width/height when crop is set. + if (!buffer->crop.isEmpty()) { + outputWidth = buffer->crop.getWidth(); + outputHeight = buffer->crop.getHeight(); + } + + int imageReaderWidth = ctx->getBufferWidth(); + int imageReaderHeight = ctx->getBufferHeight(); + if ((buffer->format != HAL_PIXEL_FORMAT_BLOB) && + (imageReaderWidth != outputWidth || imageReaderHeight > outputHeight)) { + /** + * For video decoder, the buffer height is actually the vertical stride, + * which is always >= actual image height. For future, decoder need provide + * right crop rectangle to CpuConsumer to indicate the actual image height, + * see bug 9563986. After this bug is fixed, we can enforce the height equal + * check. Right now, only make sure buffer height is no less than ImageReader + * height. + */ + jniThrowExceptionFmt(env, "java/lang/IllegalStateException", + "Producer buffer size: %dx%d, doesn't match ImageReader configured size: %dx%d", + outputWidth, outputHeight, imageReaderWidth, imageReaderHeight); + return -1; + } + + if (ctx->getBufferFormat() != buffer->format) { + // Return the buffer to the queue. + consumer->unlockBuffer(*buffer); + ctx->returnLockedBuffer(buffer); + + // Throw exception + ALOGE("Producer output buffer format: 0x%x, ImageReader configured format: 0x%x", + buffer->format, ctx->getBufferFormat()); + String8 msg; + msg.appendFormat("The producer output buffer format 0x%x doesn't " + "match the ImageReader's configured buffer format 0x%x.", + buffer->format, ctx->getBufferFormat()); + jniThrowException(env, "java/lang/UnsupportedOperationException", + msg.string()); + return -1; + } + // Set SurfaceImage instance member variables + Image_setBuffer(env, image, buffer); + env->SetLongField(image, gSurfaceImageClassInfo.mTimestamp, + static_cast<jlong>(buffer->timestamp)); + + return ACQUIRE_SUCCESS; +} + +static jobject ImageReader_getSurface(JNIEnv* env, jobject thiz) +{ + ALOGV("%s: ", __FUNCTION__); + + BufferQueue* bq = ImageReader_getBufferQueue(env, thiz); + if (bq == NULL) { + jniThrowRuntimeException(env, "CpuConsumer is uninitialized"); + return NULL; + } + + // Wrap the IGBP in a Java-language Surface. + return android_view_Surface_createFromIGraphicBufferProducer(env, bq); +} + +static jobject Image_createSurfacePlane(JNIEnv* env, jobject thiz, int idx) +{ + int rowStride, pixelStride; + ALOGV("%s: buffer index: %d", __FUNCTION__, idx); + + CpuConsumer::LockedBuffer* buffer = Image_getLockedBuffer(env, thiz); + + ALOG_ASSERT(buffer != NULL); + if (buffer == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", "Image was released"); + } + rowStride = Image_imageGetRowStride(env, buffer, idx); + pixelStride = Image_imageGetPixelStride(env, buffer, idx); + + jobject surfPlaneObj = env->NewObject(gSurfacePlaneClassInfo.clazz, + gSurfacePlaneClassInfo.ctor, thiz, idx, rowStride, pixelStride); + + return surfPlaneObj; +} + +static jobject Image_getByteBuffer(JNIEnv* env, jobject thiz, int idx) +{ + uint8_t *base = NULL; + uint32_t size = 0; + jobject byteBuffer; + + ALOGV("%s: buffer index: %d", __FUNCTION__, idx); + + CpuConsumer::LockedBuffer* buffer = Image_getLockedBuffer(env, thiz); + + if (buffer == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", "Image was released"); + } + + // Create byteBuffer from native buffer + Image_getLockedBufferInfo(env, buffer, idx, &base, &size); + byteBuffer = env->NewDirectByteBuffer(base, size); + // TODO: throw dvm exOutOfMemoryError? + if ((byteBuffer == NULL) && (env->ExceptionCheck() == false)) { + jniThrowException(env, "java/lang/IllegalStateException", "Failed to allocate ByteBuffer"); + } + + return byteBuffer; +} + +} // extern "C" + +// ---------------------------------------------------------------------------- + +static JNINativeMethod gImageReaderMethods[] = { + {"nativeClassInit", "()V", (void*)ImageReader_classInit }, + {"nativeInit", "(Ljava/lang/Object;IIII)V", (void*)ImageReader_init }, + {"nativeClose", "()V", (void*)ImageReader_close }, + {"nativeReleaseImage", "(Landroid/media/Image;)V", (void*)ImageReader_imageRelease }, + {"nativeImageSetup", "(Landroid/media/Image;)I", (void*)ImageReader_imageSetup }, + {"nativeGetSurface", "()Landroid/view/Surface;", (void*)ImageReader_getSurface }, +}; + +static JNINativeMethod gImageMethods[] = { + {"nativeImageGetBuffer", "(I)Ljava/nio/ByteBuffer;", (void*)Image_getByteBuffer }, + {"nativeCreatePlane", "(I)Landroid/media/ImageReader$SurfaceImage$SurfacePlane;", + (void*)Image_createSurfacePlane }, +}; + +int register_android_media_ImageReader(JNIEnv *env) { + + int ret1 = AndroidRuntime::registerNativeMethods(env, + "android/media/ImageReader", gImageReaderMethods, NELEM(gImageReaderMethods)); + + int ret2 = AndroidRuntime::registerNativeMethods(env, + "android/media/ImageReader$SurfaceImage", gImageMethods, NELEM(gImageMethods)); + + return (ret1 || ret2); +} diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp index cd1d9ce..b8d437c 100644 --- a/media/jni/android_media_MediaCodec.cpp +++ b/media/jni/android_media_MediaCodec.cpp @@ -38,6 +38,8 @@ #include <media/stagefright/foundation/AString.h> #include <media/stagefright/MediaErrors.h> +#include <nativehelper/ScopedLocalRef.h> + #include <system/window.h> namespace android { @@ -49,9 +51,14 @@ enum { DEQUEUE_INFO_OUTPUT_BUFFERS_CHANGED = -3, }; +struct CryptoErrorCodes { + jint cryptoErrorNoKey; + jint cryptoErrorKeyExpired; + jint cryptoErrorResourceBusy; +} gCryptoErrorCodes; + struct fields_t { jfieldID context; - jfieldID cryptoInfoNumSubSamplesID; jfieldID cryptoInfoNumBytesOfClearDataID; jfieldID cryptoInfoNumBytesOfEncryptedDataID; @@ -81,7 +88,7 @@ JMediaCodec::JMediaCodec( mLooper->start( false, // runOnCallingThread false, // canCallJava - PRIORITY_DEFAULT); + PRIORITY_FOREGROUND); if (nameIsType) { mCodec = MediaCodec::CreateByType(mLooper, name, encoder); @@ -115,7 +122,7 @@ status_t JMediaCodec::configure( int flags) { sp<Surface> client; if (bufferProducer != NULL) { - mSurfaceTextureClient = new Surface(bufferProducer); + mSurfaceTextureClient = new Surface(bufferProducer, true /* controlledByApp */); } else { mSurfaceTextureClient.clear(); } @@ -181,9 +188,10 @@ status_t JMediaCodec::dequeueOutputBuffer( return err; } - jclass clazz = env->FindClass("android/media/MediaCodec$BufferInfo"); + ScopedLocalRef<jclass> clazz( + env, env->FindClass("android/media/MediaCodec$BufferInfo")); - jmethodID method = env->GetMethodID(clazz, "set", "(IIJI)V"); + jmethodID method = env->GetMethodID(clazz.get(), "set", "(IIJI)V"); env->CallVoidMethod(bufferInfo, method, offset, size, timeUs, flags); return OK; @@ -222,29 +230,33 @@ status_t JMediaCodec::getBuffers( return err; } - jclass byteBufferClass = env->FindClass("java/nio/ByteBuffer"); - CHECK(byteBufferClass != NULL); + ScopedLocalRef<jclass> byteBufferClass( + env, env->FindClass("java/nio/ByteBuffer")); + + CHECK(byteBufferClass.get() != NULL); jmethodID orderID = env->GetMethodID( - byteBufferClass, + byteBufferClass.get(), "order", "(Ljava/nio/ByteOrder;)Ljava/nio/ByteBuffer;"); CHECK(orderID != NULL); - jclass byteOrderClass = env->FindClass("java/nio/ByteOrder"); - CHECK(byteOrderClass != NULL); + ScopedLocalRef<jclass> byteOrderClass( + env, env->FindClass("java/nio/ByteOrder")); + + CHECK(byteOrderClass.get() != NULL); jmethodID nativeOrderID = env->GetStaticMethodID( - byteOrderClass, "nativeOrder", "()Ljava/nio/ByteOrder;"); + byteOrderClass.get(), "nativeOrder", "()Ljava/nio/ByteOrder;"); CHECK(nativeOrderID != NULL); jobject nativeByteOrderObj = - env->CallStaticObjectMethod(byteOrderClass, nativeOrderID); + env->CallStaticObjectMethod(byteOrderClass.get(), nativeOrderID); CHECK(nativeByteOrderObj != NULL); *bufArray = (jobjectArray)env->NewObjectArray( - buffers.size(), byteBufferClass, NULL); + buffers.size(), byteBufferClass.get(), NULL); if (*bufArray == NULL) { env->DeleteLocalRef(nativeByteOrderObj); return NO_MEMORY; @@ -298,6 +310,10 @@ status_t JMediaCodec::getName(JNIEnv *env, jstring *nameStr) const { return OK; } +status_t JMediaCodec::setParameters(const sp<AMessage> &msg) { + return mCodec->setParameters(msg); +} + void JMediaCodec::setVideoScalingMode(int mode) { if (mSurfaceTextureClient != NULL) { native_window_set_scaling_mode(mSurfaceTextureClient.get(), mode); @@ -333,26 +349,41 @@ static void android_media_MediaCodec_release(JNIEnv *env, jobject thiz) { } static void throwCryptoException(JNIEnv *env, status_t err, const char *msg) { - jclass clazz = env->FindClass("android/media/MediaCodec$CryptoException"); - CHECK(clazz != NULL); + ScopedLocalRef<jclass> clazz( + env, env->FindClass("android/media/MediaCodec$CryptoException")); + CHECK(clazz.get() != NULL); jmethodID constructID = - env->GetMethodID(clazz, "<init>", "(ILjava/lang/String;)V"); + env->GetMethodID(clazz.get(), "<init>", "(ILjava/lang/String;)V"); CHECK(constructID != NULL); jstring msgObj = env->NewStringUTF(msg != NULL ? msg : "Unknown Error"); + /* translate OS errors to Java API CryptoException errorCodes */ + switch (err) { + case ERROR_DRM_NO_LICENSE: + err = gCryptoErrorCodes.cryptoErrorNoKey; + break; + case ERROR_DRM_LICENSE_EXPIRED: + err = gCryptoErrorCodes.cryptoErrorKeyExpired; + break; + case ERROR_DRM_RESOURCE_BUSY: + err = gCryptoErrorCodes.cryptoErrorResourceBusy; + break; + default: + break; + } + jthrowable exception = - (jthrowable)env->NewObject(clazz, constructID, err, msgObj); + (jthrowable)env->NewObject(clazz.get(), constructID, err, msgObj); env->Throw(exception); } static jint throwExceptionAsNecessary( JNIEnv *env, status_t err, const char *msg = NULL) { - if (err >= ERROR_DRM_WV_VENDOR_MIN && err <= ERROR_DRM_WV_VENDOR_MAX) { + if (err >= ERROR_DRM_VENDOR_MIN && err <= ERROR_DRM_VENDOR_MAX) { // We'll throw our custom MediaCodec.CryptoException - throwCryptoException(env, err, msg); return 0; } @@ -370,6 +401,12 @@ static jint throwExceptionAsNecessary( case INFO_OUTPUT_BUFFERS_CHANGED: return DEQUEUE_INFO_OUTPUT_BUFFERS_CHANGED; + case ERROR_DRM_NO_LICENSE: + case ERROR_DRM_LICENSE_EXPIRED: + case ERROR_DRM_RESOURCE_BUSY: + throwCryptoException(env, err, msg); + break; + default: { jniThrowException(env, "java/lang/IllegalStateException", msg); @@ -804,6 +841,27 @@ static jobject android_media_MediaCodec_getName( return NULL; } +static void android_media_MediaCodec_setParameters( + JNIEnv *env, jobject thiz, jobjectArray keys, jobjectArray vals) { + ALOGV("android_media_MediaCodec_setParameters"); + + sp<JMediaCodec> codec = getMediaCodec(env, thiz); + + if (codec == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + + sp<AMessage> params; + status_t err = ConvertKeyValueArraysToMessage(env, keys, vals, ¶ms); + + if (err == OK) { + err = codec->setParameters(params); + } + + throwExceptionAsNecessary(env, err); +} + static void android_media_MediaCodec_setVideoScalingMode( JNIEnv *env, jobject thiz, jint mode) { sp<JMediaCodec> codec = getMediaCodec(env, thiz); @@ -823,35 +881,55 @@ static void android_media_MediaCodec_setVideoScalingMode( } static void android_media_MediaCodec_native_init(JNIEnv *env) { - jclass clazz = env->FindClass("android/media/MediaCodec"); - CHECK(clazz != NULL); + ScopedLocalRef<jclass> clazz( + env, env->FindClass("android/media/MediaCodec")); + CHECK(clazz.get() != NULL); - gFields.context = env->GetFieldID(clazz, "mNativeContext", "I"); + gFields.context = env->GetFieldID(clazz.get(), "mNativeContext", "I"); CHECK(gFields.context != NULL); - clazz = env->FindClass("android/media/MediaCodec$CryptoInfo"); - CHECK(clazz != NULL); + clazz.reset(env->FindClass("android/media/MediaCodec$CryptoInfo")); + CHECK(clazz.get() != NULL); gFields.cryptoInfoNumSubSamplesID = - env->GetFieldID(clazz, "numSubSamples", "I"); + env->GetFieldID(clazz.get(), "numSubSamples", "I"); CHECK(gFields.cryptoInfoNumSubSamplesID != NULL); gFields.cryptoInfoNumBytesOfClearDataID = - env->GetFieldID(clazz, "numBytesOfClearData", "[I"); + env->GetFieldID(clazz.get(), "numBytesOfClearData", "[I"); CHECK(gFields.cryptoInfoNumBytesOfClearDataID != NULL); gFields.cryptoInfoNumBytesOfEncryptedDataID = - env->GetFieldID(clazz, "numBytesOfEncryptedData", "[I"); + env->GetFieldID(clazz.get(), "numBytesOfEncryptedData", "[I"); CHECK(gFields.cryptoInfoNumBytesOfEncryptedDataID != NULL); - gFields.cryptoInfoKeyID = env->GetFieldID(clazz, "key", "[B"); + gFields.cryptoInfoKeyID = env->GetFieldID(clazz.get(), "key", "[B"); CHECK(gFields.cryptoInfoKeyID != NULL); - gFields.cryptoInfoIVID = env->GetFieldID(clazz, "iv", "[B"); + gFields.cryptoInfoIVID = env->GetFieldID(clazz.get(), "iv", "[B"); CHECK(gFields.cryptoInfoIVID != NULL); - gFields.cryptoInfoModeID = env->GetFieldID(clazz, "mode", "I"); + gFields.cryptoInfoModeID = env->GetFieldID(clazz.get(), "mode", "I"); CHECK(gFields.cryptoInfoModeID != NULL); + + clazz.reset(env->FindClass("android/media/MediaCodec$CryptoException")); + CHECK(clazz.get() != NULL); + + jfieldID field; + field = env->GetStaticFieldID(clazz.get(), "ERROR_NO_KEY", "I"); + CHECK(field != NULL); + gCryptoErrorCodes.cryptoErrorNoKey = + env->GetStaticIntField(clazz.get(), field); + + field = env->GetStaticFieldID(clazz.get(), "ERROR_KEY_EXPIRED", "I"); + CHECK(field != NULL); + gCryptoErrorCodes.cryptoErrorKeyExpired = + env->GetStaticIntField(clazz.get(), field); + + field = env->GetStaticFieldID(clazz.get(), "ERROR_RESOURCE_BUSY", "I"); + CHECK(field != NULL); + gCryptoErrorCodes.cryptoErrorResourceBusy = + env->GetStaticIntField(clazz.get(), field); } static void android_media_MediaCodec_native_setup( @@ -933,6 +1011,9 @@ static JNINativeMethod gMethods[] = { { "getName", "()Ljava/lang/String;", (void *)android_media_MediaCodec_getName }, + { "setParameters", "([Ljava/lang/String;[Ljava/lang/Object;)V", + (void *)android_media_MediaCodec_setParameters }, + { "setVideoScalingMode", "(I)V", (void *)android_media_MediaCodec_setVideoScalingMode }, diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h index 282d2c5..2fbbd72 100644 --- a/media/jni/android_media_MediaCodec.h +++ b/media/jni/android_media_MediaCodec.h @@ -87,6 +87,8 @@ struct JMediaCodec : public RefBase { status_t getName(JNIEnv *env, jstring *name) const; + status_t setParameters(const sp<AMessage> ¶ms); + void setVideoScalingMode(int mode); protected: diff --git a/media/jni/android_media_MediaCodecList.cpp b/media/jni/android_media_MediaCodecList.cpp index 04430ec..caa594e 100644 --- a/media/jni/android_media_MediaCodecList.cpp +++ b/media/jni/android_media_MediaCodecList.cpp @@ -110,10 +110,11 @@ static jobject android_media_MediaCodecList_getCodecCapabilities( Vector<MediaCodecList::ProfileLevel> profileLevels; Vector<uint32_t> colorFormats; + uint32_t flags; status_t err = MediaCodecList::getInstance()->getCodecCapabilities( - index, typeStr, &profileLevels, &colorFormats); + index, typeStr, &profileLevels, &colorFormats, &flags); env->ReleaseStringUTFChars(type, typeStr); typeStr = NULL; @@ -127,6 +128,9 @@ static jobject android_media_MediaCodecList_getCodecCapabilities( env->FindClass("android/media/MediaCodecInfo$CodecCapabilities"); CHECK(capsClazz != NULL); + jfieldID flagsField = + env->GetFieldID(capsClazz, "flags", "I"); + jobject caps = env->AllocObject(capsClazz); jclass profileLevelClazz = @@ -163,6 +167,8 @@ static jobject android_media_MediaCodecList_getCodecCapabilities( env->SetObjectField(caps, profileLevelsField, profileLevelArray); + env->SetIntField(caps, flagsField, flags); + env->DeleteLocalRef(profileLevelArray); profileLevelArray = NULL; diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp index 7799ca4..bbb74d2 100644 --- a/media/jni/android_media_MediaDrm.cpp +++ b/media/jni/android_media_MediaDrm.cpp @@ -21,6 +21,7 @@ #include "android_media_MediaDrm.h" #include "android_runtime/AndroidRuntime.h" +#include "android_runtime/Log.h" #include "android_os_Parcel.h" #include "jni.h" #include "JNIHelp.h" @@ -242,6 +243,9 @@ static bool throwExceptionAsNecessary( } else if (err == ERROR_DRM_NOT_PROVISIONED) { jniThrowException(env, "android/media/NotProvisionedException", msg); return true; + } else if (err == ERROR_DRM_RESOURCE_BUSY) { + jniThrowException(env, "android/media/ResourceBusyException", msg); + return true; } else if (err == ERROR_DRM_DEVICE_REVOKED) { jniThrowException(env, "android/media/DeniedByServerException", msg); return true; @@ -345,14 +349,14 @@ void JDrm::notify(DrmPlugin::EventType eventType, int extra, const Parcel *obj) // static -bool JDrm::IsCryptoSchemeSupported(const uint8_t uuid[16]) { +bool JDrm::IsCryptoSchemeSupported(const uint8_t uuid[16], const String8 &mimeType) { sp<IDrm> drm = MakeDrm(); if (drm == NULL) { return false; } - return drm->isCryptoSchemeSupported(uuid); + return drm->isCryptoSchemeSupported(uuid, mimeType); } status_t JDrm::initCheck() const { @@ -608,7 +612,7 @@ static void android_media_MediaDrm_native_finalize( } static jboolean android_media_MediaDrm_isCryptoSchemeSupportedNative( - JNIEnv *env, jobject thiz, jbyteArray uuidObj) { + JNIEnv *env, jobject thiz, jbyteArray uuidObj, jstring jmimeType) { if (uuidObj == NULL) { jniThrowException(env, "java/lang/IllegalArgumentException", NULL); @@ -625,7 +629,12 @@ static jboolean android_media_MediaDrm_isCryptoSchemeSupportedNative( return false; } - return JDrm::IsCryptoSchemeSupported(uuid.array()); + String8 mimeType; + if (jmimeType != NULL) { + mimeType = JStringToString8(env, jmimeType); + } + + return JDrm::IsCryptoSchemeSupported(uuid.array(), mimeType); } static jbyteArray android_media_MediaDrm_openSession( @@ -750,7 +759,9 @@ static jbyteArray android_media_MediaDrm_provideKeyResponse( status_t err = drm->provideKeyResponse(sessionId, response, keySetId); - throwExceptionAsNecessary(env, err, "Failed to handle key response"); + if (throwExceptionAsNecessary(env, err, "Failed to handle key response")) { + return NULL; + } return VectorToJByteArray(env, keySetId); } @@ -1101,7 +1112,9 @@ static jbyteArray android_media_MediaDrm_encryptNative( status_t err = drm->encrypt(sessionId, keyId, input, iv, output); - throwExceptionAsNecessary(env, err, "Failed to encrypt"); + if (throwExceptionAsNecessary(env, err, "Failed to encrypt")) { + return NULL; + } return VectorToJByteArray(env, output); } @@ -1129,7 +1142,9 @@ static jbyteArray android_media_MediaDrm_decryptNative( Vector<uint8_t> output; status_t err = drm->decrypt(sessionId, keyId, input, iv, output); - throwExceptionAsNecessary(env, err, "Failed to decrypt"); + if (throwExceptionAsNecessary(env, err, "Failed to decrypt")) { + return NULL; + } return VectorToJByteArray(env, output); } @@ -1157,7 +1172,9 @@ static jbyteArray android_media_MediaDrm_signNative( status_t err = drm->sign(sessionId, keyId, message, signature); - throwExceptionAsNecessary(env, err, "Failed to sign"); + if (throwExceptionAsNecessary(env, err, "Failed to sign")) { + return NULL; + } return VectorToJByteArray(env, signature); } @@ -1201,7 +1218,7 @@ static JNINativeMethod gMethods[] = { { "native_finalize", "()V", (void *)android_media_MediaDrm_native_finalize }, - { "isCryptoSchemeSupportedNative", "([B)Z", + { "isCryptoSchemeSupportedNative", "([BLjava/lang/String;)Z", (void *)android_media_MediaDrm_isCryptoSchemeSupportedNative }, { "openSession", "()[B", diff --git a/media/jni/android_media_MediaDrm.h b/media/jni/android_media_MediaDrm.h index 9b3917f..620ad28 100644 --- a/media/jni/android_media_MediaDrm.h +++ b/media/jni/android_media_MediaDrm.h @@ -37,7 +37,7 @@ public: }; struct JDrm : public BnDrmClient { - static bool IsCryptoSchemeSupported(const uint8_t uuid[16]); + static bool IsCryptoSchemeSupported(const uint8_t uuid[16], const String8 &mimeType); JDrm(JNIEnv *env, jobject thiz, const uint8_t uuid[16]); diff --git a/media/jni/android_media_MediaExtractor.cpp b/media/jni/android_media_MediaExtractor.cpp index 1704d5c..1ac45d4 100644 --- a/media/jni/android_media_MediaExtractor.cpp +++ b/media/jni/android_media_MediaExtractor.cpp @@ -22,6 +22,7 @@ #include "android_media_Utils.h" #include "android_runtime/AndroidRuntime.h" +#include "android_runtime/Log.h" #include "jni.h" #include "JNIHelp.h" diff --git a/media/jni/android_media_MediaMuxer.cpp b/media/jni/android_media_MediaMuxer.cpp index 7517e85..457b956 100644 --- a/media/jni/android_media_MediaMuxer.cpp +++ b/media/jni/android_media_MediaMuxer.cpp @@ -164,6 +164,18 @@ static void android_media_MediaMuxer_setOrientationHint( } +static void android_media_MediaMuxer_setLocation( + JNIEnv *env, jclass clazz, jint nativeObject, jint latitude, jint longitude) { + MediaMuxer* muxer = reinterpret_cast<MediaMuxer *>(nativeObject); + + status_t res = muxer->setLocation(latitude, longitude); + if (res != OK) { + jniThrowException(env, "java/lang/IllegalStateException", + "Failed to set location"); + return; + } +} + static void android_media_MediaMuxer_start(JNIEnv *env, jclass clazz, jint nativeObject) { sp<MediaMuxer> muxer(reinterpret_cast<MediaMuxer *>(nativeObject)); @@ -216,6 +228,9 @@ static JNINativeMethod gMethods[] = { { "nativeSetOrientationHint", "(II)V", (void *)android_media_MediaMuxer_setOrientationHint}, + { "nativeSetLocation", "(III)V", + (void *)android_media_MediaMuxer_setLocation}, + { "nativeStart", "(I)V", (void *)android_media_MediaMuxer_start}, { "nativeWriteSampleData", "(IILjava/nio/ByteBuffer;IIJI)V", diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp index d06380d..4be9cd6 100644 --- a/media/jni/android_media_MediaPlayer.cpp +++ b/media/jni/android_media_MediaPlayer.cpp @@ -31,6 +31,7 @@ #include "JNIHelp.h" #include "android_runtime/AndroidRuntime.h" #include "android_runtime/android_view_Surface.h" +#include "android_runtime/Log.h" #include "utils/Errors.h" // for status_t #include "utils/KeyedVector.h" #include "utils/String8.h" @@ -526,14 +527,6 @@ android_media_MediaPlayer_setVolume(JNIEnv *env, jobject thiz, float leftVolume, process_media_player_call( env, thiz, mp->setVolume(leftVolume, rightVolume), NULL, NULL ); } -// FIXME: deprecated -static jobject -android_media_MediaPlayer_getFrameAt(JNIEnv *env, jobject thiz, jint msec) -{ - return NULL; -} - - // Sends the request and reply parcels to the media player via the // binder interface. static jint @@ -782,39 +775,6 @@ android_media_MediaPlayer_setRetransmitEndpoint(JNIEnv *env, jobject thiz, return ret; } -static jboolean -android_media_MediaPlayer_setParameter(JNIEnv *env, jobject thiz, jint key, jobject java_request) -{ - ALOGV("setParameter: key %d", key); - sp<MediaPlayer> mp = getMediaPlayer(env, thiz); - if (mp == NULL ) { - jniThrowException(env, "java/lang/IllegalStateException", NULL); - return false; - } - - Parcel *request = parcelForJavaObject(env, java_request); - status_t err = mp->setParameter(key, *request); - if (err == OK) { - return true; - } else { - return false; - } -} - -static void -android_media_MediaPlayer_getParameter(JNIEnv *env, jobject thiz, jint key, jobject java_reply) -{ - ALOGV("getParameter: key %d", key); - sp<MediaPlayer> mp = getMediaPlayer(env, thiz); - if (mp == NULL ) { - jniThrowException(env, "java/lang/IllegalStateException", NULL); - return; - } - - Parcel *reply = parcelForJavaObject(env, java_reply); - process_media_player_call(env, thiz, mp->getParameter(key, reply), NULL, NULL ); -} - static void android_media_MediaPlayer_setNextMediaPlayer(JNIEnv *env, jobject thiz, jobject java_player) { @@ -913,7 +873,6 @@ static JNINativeMethod gMethods[] = { {"setLooping", "(Z)V", (void *)android_media_MediaPlayer_setLooping}, {"isLooping", "()Z", (void *)android_media_MediaPlayer_isLooping}, {"setVolume", "(FF)V", (void *)android_media_MediaPlayer_setVolume}, - {"getFrameAt", "(I)Landroid/graphics/Bitmap;", (void *)android_media_MediaPlayer_getFrameAt}, {"native_invoke", "(Landroid/os/Parcel;Landroid/os/Parcel;)I",(void *)android_media_MediaPlayer_invoke}, {"native_setMetadataFilter", "(Landroid/os/Parcel;)I", (void *)android_media_MediaPlayer_setMetadataFilter}, {"native_getMetadata", "(ZZLandroid/os/Parcel;)Z", (void *)android_media_MediaPlayer_getMetadata}, @@ -925,8 +884,6 @@ static JNINativeMethod gMethods[] = { {"setAuxEffectSendLevel", "(F)V", (void *)android_media_MediaPlayer_setAuxEffectSendLevel}, {"attachAuxEffect", "(I)V", (void *)android_media_MediaPlayer_attachAuxEffect}, {"native_pullBatteryData", "(Landroid/os/Parcel;)I", (void *)android_media_MediaPlayer_pullBatteryData}, - {"setParameter", "(ILandroid/os/Parcel;)Z", (void *)android_media_MediaPlayer_setParameter}, - {"getParameter", "(ILandroid/os/Parcel;)V", (void *)android_media_MediaPlayer_getParameter}, {"native_setRetransmitEndpoint", "(Ljava/lang/String;I)I", (void *)android_media_MediaPlayer_setRetransmitEndpoint}, {"setNextMediaPlayer", "(Landroid/media/MediaPlayer;)V", (void *)android_media_MediaPlayer_setNextMediaPlayer}, {"updateProxyConfig", "(Landroid/net/ProxyProperties;)V", (void *)android_media_MediaPlayer_updateProxyConfig}, @@ -941,6 +898,7 @@ static int register_android_media_MediaPlayer(JNIEnv *env) "android/media/MediaPlayer", gMethods, NELEM(gMethods)); } +extern int register_android_media_ImageReader(JNIEnv *env); extern int register_android_media_Crypto(JNIEnv *env); extern int register_android_media_Drm(JNIEnv *env); extern int register_android_media_MediaCodec(JNIEnv *env); @@ -968,6 +926,11 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) } assert(env != NULL); + if (register_android_media_ImageReader(env) < 0) { + ALOGE("ERROR: ImageReader native registration failed"); + goto bail; + } + if (register_android_media_MediaPlayer(env) < 0) { ALOGE("ERROR: MediaPlayer native registration failed\n"); goto bail; diff --git a/media/jni/android_media_MediaScanner.cpp b/media/jni/android_media_MediaScanner.cpp index 5d27966..4e3d14e 100644 --- a/media/jni/android_media_MediaScanner.cpp +++ b/media/jni/android_media_MediaScanner.cpp @@ -25,6 +25,7 @@ #include "jni.h" #include "JNIHelp.h" #include "android_runtime/AndroidRuntime.h" +#include "android_runtime/Log.h" using namespace android; diff --git a/media/jni/android_media_Utils.cpp b/media/jni/android_media_Utils.cpp index e35ace3..54c5e9b 100644 --- a/media/jni/android_media_Utils.cpp +++ b/media/jni/android_media_Utils.cpp @@ -24,6 +24,8 @@ #include <media/stagefright/foundation/ABuffer.h> #include <media/stagefright/foundation/AMessage.h> +#include <nativehelper/ScopedLocalRef.h> + namespace android { bool ConvertKeyValueArraysToKeyedVector( @@ -76,33 +78,35 @@ bool ConvertKeyValueArraysToKeyedVector( } static jobject makeIntegerObject(JNIEnv *env, int32_t value) { - jclass clazz = env->FindClass("java/lang/Integer"); - CHECK(clazz != NULL); + ScopedLocalRef<jclass> clazz(env, env->FindClass("java/lang/Integer")); + CHECK(clazz.get() != NULL); - jmethodID integerConstructID = env->GetMethodID(clazz, "<init>", "(I)V"); + jmethodID integerConstructID = + env->GetMethodID(clazz.get(), "<init>", "(I)V"); CHECK(integerConstructID != NULL); - return env->NewObject(clazz, integerConstructID, value); + return env->NewObject(clazz.get(), integerConstructID, value); } static jobject makeLongObject(JNIEnv *env, int64_t value) { - jclass clazz = env->FindClass("java/lang/Long"); - CHECK(clazz != NULL); + ScopedLocalRef<jclass> clazz(env, env->FindClass("java/lang/Long")); + CHECK(clazz.get() != NULL); - jmethodID longConstructID = env->GetMethodID(clazz, "<init>", "(J)V"); + jmethodID longConstructID = env->GetMethodID(clazz.get(), "<init>", "(J)V"); CHECK(longConstructID != NULL); - return env->NewObject(clazz, longConstructID, value); + return env->NewObject(clazz.get(), longConstructID, value); } static jobject makeFloatObject(JNIEnv *env, float value) { - jclass clazz = env->FindClass("java/lang/Float"); - CHECK(clazz != NULL); + ScopedLocalRef<jclass> clazz(env, env->FindClass("java/lang/Float")); + CHECK(clazz.get() != NULL); - jmethodID floatConstructID = env->GetMethodID(clazz, "<init>", "(F)V"); + jmethodID floatConstructID = + env->GetMethodID(clazz.get(), "<init>", "(F)V"); CHECK(floatConstructID != NULL); - return env->NewObject(clazz, floatConstructID, value); + return env->NewObject(clazz.get(), floatConstructID, value); } static jobject makeByteBufferObject( @@ -110,15 +114,16 @@ static jobject makeByteBufferObject( jbyteArray byteArrayObj = env->NewByteArray(size); env->SetByteArrayRegion(byteArrayObj, 0, size, (const jbyte *)data); - jclass clazz = env->FindClass("java/nio/ByteBuffer"); - CHECK(clazz != NULL); + ScopedLocalRef<jclass> clazz(env, env->FindClass("java/nio/ByteBuffer")); + CHECK(clazz.get() != NULL); jmethodID byteBufWrapID = - env->GetStaticMethodID(clazz, "wrap", "([B)Ljava/nio/ByteBuffer;"); + env->GetStaticMethodID( + clazz.get(), "wrap", "([B)Ljava/nio/ByteBuffer;"); CHECK(byteBufWrapID != NULL); jobject byteBufObj = env->CallStaticObjectMethod( - clazz, byteBufWrapID, byteArrayObj); + clazz.get(), byteBufWrapID, byteArrayObj); env->DeleteLocalRef(byteArrayObj); byteArrayObj = NULL; @@ -140,14 +145,15 @@ static void SetMapInt32( status_t ConvertMessageToMap( JNIEnv *env, const sp<AMessage> &msg, jobject *map) { - jclass hashMapClazz = env->FindClass("java/util/HashMap"); + ScopedLocalRef<jclass> hashMapClazz( + env, env->FindClass("java/util/HashMap")); - if (hashMapClazz == NULL) { + if (hashMapClazz.get() == NULL) { return -EINVAL; } jmethodID hashMapConstructID = - env->GetMethodID(hashMapClazz, "<init>", "()V"); + env->GetMethodID(hashMapClazz.get(), "<init>", "()V"); if (hashMapConstructID == NULL) { return -EINVAL; @@ -155,7 +161,7 @@ status_t ConvertMessageToMap( jmethodID hashMapPutID = env->GetMethodID( - hashMapClazz, + hashMapClazz.get(), "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); @@ -163,7 +169,7 @@ status_t ConvertMessageToMap( return -EINVAL; } - jobject hashMap = env->NewObject(hashMapClazz, hashMapConstructID); + jobject hashMap = env->NewObject(hashMapClazz.get(), hashMapConstructID); for (size_t i = 0; i < msg->countEntries(); ++i) { AMessage::Type valueType; @@ -276,17 +282,16 @@ status_t ConvertMessageToMap( status_t ConvertKeyValueArraysToMessage( JNIEnv *env, jobjectArray keys, jobjectArray values, sp<AMessage> *out) { - jclass stringClass = env->FindClass("java/lang/String"); - CHECK(stringClass != NULL); - - jclass integerClass = env->FindClass("java/lang/Integer"); - CHECK(integerClass != NULL); - - jclass floatClass = env->FindClass("java/lang/Float"); - CHECK(floatClass != NULL); - - jclass byteBufClass = env->FindClass("java/nio/ByteBuffer"); - CHECK(byteBufClass != NULL); + ScopedLocalRef<jclass> stringClass(env, env->FindClass("java/lang/String")); + CHECK(stringClass.get() != NULL); + ScopedLocalRef<jclass> integerClass(env, env->FindClass("java/lang/Integer")); + CHECK(integerClass.get() != NULL); + ScopedLocalRef<jclass> longClass(env, env->FindClass("java/lang/Long")); + CHECK(longClass.get() != NULL); + ScopedLocalRef<jclass> floatClass(env, env->FindClass("java/lang/Float")); + CHECK(floatClass.get() != NULL); + ScopedLocalRef<jclass> byteBufClass(env, env->FindClass("java/nio/ByteBuffer")); + CHECK(byteBufClass.get() != NULL); sp<AMessage> msg = new AMessage; @@ -309,7 +314,7 @@ status_t ConvertKeyValueArraysToMessage( for (jsize i = 0; i < numEntries; ++i) { jobject keyObj = env->GetObjectArrayElement(keys, i); - if (!env->IsInstanceOf(keyObj, stringClass)) { + if (!env->IsInstanceOf(keyObj, stringClass.get())) { return -EINVAL; } @@ -326,7 +331,7 @@ status_t ConvertKeyValueArraysToMessage( jobject valueObj = env->GetObjectArrayElement(values, i); - if (env->IsInstanceOf(valueObj, stringClass)) { + if (env->IsInstanceOf(valueObj, stringClass.get())) { const char *value = env->GetStringUTFChars((jstring)valueObj, NULL); if (value == NULL) { @@ -337,29 +342,37 @@ status_t ConvertKeyValueArraysToMessage( env->ReleaseStringUTFChars((jstring)valueObj, value); value = NULL; - } else if (env->IsInstanceOf(valueObj, integerClass)) { + } else if (env->IsInstanceOf(valueObj, integerClass.get())) { jmethodID intValueID = - env->GetMethodID(integerClass, "intValue", "()I"); + env->GetMethodID(integerClass.get(), "intValue", "()I"); CHECK(intValueID != NULL); jint value = env->CallIntMethod(valueObj, intValueID); msg->setInt32(key.c_str(), value); - } else if (env->IsInstanceOf(valueObj, floatClass)) { + } else if (env->IsInstanceOf(valueObj, longClass.get())) { + jmethodID longValueID = + env->GetMethodID(longClass.get(), "longValue", "()J"); + CHECK(longValueID != NULL); + + jlong value = env->CallLongMethod(valueObj, longValueID); + + msg->setInt64(key.c_str(), value); + } else if (env->IsInstanceOf(valueObj, floatClass.get())) { jmethodID floatValueID = - env->GetMethodID(floatClass, "floatValue", "()F"); + env->GetMethodID(floatClass.get(), "floatValue", "()F"); CHECK(floatValueID != NULL); jfloat value = env->CallFloatMethod(valueObj, floatValueID); msg->setFloat(key.c_str(), value); - } else if (env->IsInstanceOf(valueObj, byteBufClass)) { + } else if (env->IsInstanceOf(valueObj, byteBufClass.get())) { jmethodID positionID = - env->GetMethodID(byteBufClass, "position", "()I"); + env->GetMethodID(byteBufClass.get(), "position", "()I"); CHECK(positionID != NULL); jmethodID limitID = - env->GetMethodID(byteBufClass, "limit", "()I"); + env->GetMethodID(byteBufClass.get(), "limit", "()I"); CHECK(limitID != NULL); jint position = env->CallIntMethod(valueObj, positionID); @@ -375,7 +388,7 @@ status_t ConvertKeyValueArraysToMessage( buffer->size()); } else { jmethodID arrayID = - env->GetMethodID(byteBufClass, "array", "()[B"); + env->GetMethodID(byteBufClass.get(), "array", "()[B"); CHECK(arrayID != NULL); jbyteArray byteArray = diff --git a/media/jni/android_mtp_MtpDatabase.cpp b/media/jni/android_mtp_MtpDatabase.cpp index fbd5d21..77c7966 100644 --- a/media/jni/android_mtp_MtpDatabase.cpp +++ b/media/jni/android_mtp_MtpDatabase.cpp @@ -26,6 +26,7 @@ #include "jni.h" #include "JNIHelp.h" #include "android_runtime/AndroidRuntime.h" +#include "android_runtime/Log.h" #include "MtpDatabase.h" #include "MtpDataPacket.h" diff --git a/media/jni/android_mtp_MtpDevice.cpp b/media/jni/android_mtp_MtpDevice.cpp index 113784e..b61b66c 100644 --- a/media/jni/android_mtp_MtpDevice.cpp +++ b/media/jni/android_mtp_MtpDevice.cpp @@ -28,6 +28,7 @@ #include "jni.h" #include "JNIHelp.h" #include "android_runtime/AndroidRuntime.h" +#include "android_runtime/Log.h" #include "private/android_filesystem_config.h" #include "MtpTypes.h" diff --git a/media/jni/audioeffect/android_media_Visualizer.cpp b/media/jni/audioeffect/android_media_Visualizer.cpp index 4d77cfd..40cd06b 100644 --- a/media/jni/audioeffect/android_media_Visualizer.cpp +++ b/media/jni/audioeffect/android_media_Visualizer.cpp @@ -43,6 +43,8 @@ using namespace android; // ---------------------------------------------------------------------------- static const char* const kClassPathName = "android/media/audiofx/Visualizer"; +static const char* const kClassPeakRmsPathName = + "android/media/audiofx/Visualizer$MeasurementPeakRms"; struct fields_t { // these fields provide access from C++ to the... @@ -50,6 +52,8 @@ struct fields_t { jmethodID midPostNativeEvent; // event post callback method jfieldID fidNativeVisualizer; // stores in Java the native Visualizer object jfieldID fidJniData; // stores in Java additional resources used by the native Visualizer + jfieldID fidPeak; // to access Visualizer.MeasurementPeakRms.mPeak + jfieldID fidRms; // to access Visualizer.MeasurementPeakRms.mRms }; static fields_t fields; @@ -257,6 +261,14 @@ android_media_visualizer_native_init(JNIEnv *env) fields.clazzEffect = (jclass)env->NewGlobalRef(clazz); + // Get the Visualizer.MeasurementPeakRms class + clazz = env->FindClass(kClassPeakRmsPathName); + if (clazz == NULL) { + ALOGE("Can't find %s", kClassPeakRmsPathName); + return; + } + jclass clazzMeasurementPeakRms = (jclass)env->NewGlobalRef(clazz); + // Get the postEvent method fields.midPostNativeEvent = env->GetStaticMethodID( fields.clazzEffect, @@ -283,7 +295,24 @@ android_media_visualizer_native_init(JNIEnv *env) ALOGE("Can't find Visualizer.%s", "mJniData"); return; } + // fidPeak + fields.fidPeak = env->GetFieldID( + clazzMeasurementPeakRms, + "mPeak", "I"); + if (fields.fidPeak == NULL) { + ALOGE("Can't find Visualizer.MeasurementPeakRms.%s", "mPeak"); + return; + } + // fidRms + fields.fidRms = env->GetFieldID( + clazzMeasurementPeakRms, + "mRms", "I"); + if (fields.fidRms == NULL) { + ALOGE("Can't find Visualizer.MeasurementPeakRms.%s", "mPeak"); + return; + } + env->DeleteGlobalRef(clazzMeasurementPeakRms); } static void android_media_visualizer_effect_callback(int32_t event, @@ -513,6 +542,26 @@ android_media_visualizer_native_getScalingMode(JNIEnv *env, jobject thiz) } static jint +android_media_visualizer_native_setMeasurementMode(JNIEnv *env, jobject thiz, jint mode) +{ + Visualizer* lpVisualizer = getVisualizer(env, thiz); + if (lpVisualizer == NULL) { + return VISUALIZER_ERROR_NO_INIT; + } + return translateError(lpVisualizer->setMeasurementMode(mode)); +} + +static jint +android_media_visualizer_native_getMeasurementMode(JNIEnv *env, jobject thiz) +{ + Visualizer* lpVisualizer = getVisualizer(env, thiz); + if (lpVisualizer == NULL) { + return MEASUREMENT_MODE_NONE; + } + return lpVisualizer->getMeasurementMode(); +} + +static jint android_media_visualizer_native_getSamplingRate(JNIEnv *env, jobject thiz) { Visualizer* lpVisualizer = getVisualizer(env, thiz); @@ -560,6 +609,25 @@ android_media_visualizer_native_getFft(JNIEnv *env, jobject thiz, jbyteArray jFf } static jint +android_media_visualizer_native_getPeakRms(JNIEnv *env, jobject thiz, jobject jPeakRmsObj) +{ + Visualizer* lpVisualizer = getVisualizer(env, thiz); + if (lpVisualizer == NULL) { + return VISUALIZER_ERROR_NO_INIT; + } + int32_t measurements[2]; + jint status = translateError( + lpVisualizer->getIntMeasurements(MEASUREMENT_MODE_PEAK_RMS, + 2, measurements)); + if (status == VISUALIZER_SUCCESS) { + // measurement worked, write the values to the java object + env->SetIntField(jPeakRmsObj, fields.fidPeak, measurements[MEASUREMENT_IDX_PEAK]); + env->SetIntField(jPeakRmsObj, fields.fidRms, measurements[MEASUREMENT_IDX_RMS]); + } + return status; +} + +static jint android_media_setPeriodicCapture(JNIEnv *env, jobject thiz, jint rate, jboolean jWaveform, jboolean jFft) { Visualizer* lpVisualizer = getVisualizer(env, thiz); @@ -606,9 +674,13 @@ static JNINativeMethod gMethods[] = { {"native_getCaptureSize", "()I", (void *)android_media_visualizer_native_getCaptureSize}, {"native_setScalingMode", "(I)I", (void *)android_media_visualizer_native_setScalingMode}, {"native_getScalingMode", "()I", (void *)android_media_visualizer_native_getScalingMode}, + {"native_setMeasurementMode","(I)I", (void *)android_media_visualizer_native_setMeasurementMode}, + {"native_getMeasurementMode","()I", (void *)android_media_visualizer_native_getMeasurementMode}, {"native_getSamplingRate", "()I", (void *)android_media_visualizer_native_getSamplingRate}, {"native_getWaveForm", "([B)I", (void *)android_media_visualizer_native_getWaveForm}, {"native_getFft", "([B)I", (void *)android_media_visualizer_native_getFft}, + {"native_getPeakRms", "(Landroid/media/audiofx/Visualizer$MeasurementPeakRms;)I", + (void *)android_media_visualizer_native_getPeakRms}, {"native_setPeriodicCapture","(IZZ)I",(void *)android_media_setPeriodicCapture}, }; diff --git a/media/jni/mediaeditor/VideoEditorClasses.cpp b/media/jni/mediaeditor/VideoEditorClasses.cpp index 4982a47..d8099dd 100644 --- a/media/jni/mediaeditor/VideoEditorClasses.cpp +++ b/media/jni/mediaeditor/VideoEditorClasses.cpp @@ -14,6 +14,7 @@ * limitations under the License. */ +#define LOG_TAG "VideoEditorClasses" #include <VideoEditorClasses.h> #include <VideoEditorJava.h> diff --git a/media/jni/mediaeditor/VideoEditorJava.cpp b/media/jni/mediaeditor/VideoEditorJava.cpp index bcf9099..fde0fb5 100644 --- a/media/jni/mediaeditor/VideoEditorJava.cpp +++ b/media/jni/mediaeditor/VideoEditorJava.cpp @@ -14,6 +14,8 @@ * limitations under the License. */ +#define LOG_TAG "VideoEditorJava" + #include <VideoEditorClasses.h> #include <VideoEditorJava.h> #include <VideoEditorLogging.h> diff --git a/media/jni/mediaeditor/VideoEditorLogging.h b/media/jni/mediaeditor/VideoEditorLogging.h index 479d8b6..1f1228a 100644 --- a/media/jni/mediaeditor/VideoEditorLogging.h +++ b/media/jni/mediaeditor/VideoEditorLogging.h @@ -17,6 +17,16 @@ #ifndef VIDEO_EDITOR_LOGGING_H #define VIDEO_EDITOR_LOGGING_H +#ifndef LOG_TAG +#error "No LOG_TAG defined!" +#endif + +/* + * This file is used as a proxy for cutils/log.h. Include cutils/log.h here to + * avoid relying on import ordering. + */ +#include <cutils/log.h> + //#define VIDEOEDIT_LOGGING_ENABLED #define VIDEOEDIT_LOG_INDENTATION (3) diff --git a/media/jni/mediaeditor/VideoEditorOsal.cpp b/media/jni/mediaeditor/VideoEditorOsal.cpp index a8c08ac..c12b1f5 100644 --- a/media/jni/mediaeditor/VideoEditorOsal.cpp +++ b/media/jni/mediaeditor/VideoEditorOsal.cpp @@ -14,6 +14,8 @@ * limitations under the License. */ +#define LOG_TAG "VideoEditorOsal" + #include <VideoEditorJava.h> #include <VideoEditorLogging.h> #include <VideoEditorOsal.h> diff --git a/media/jni/mediaeditor/VideoEditorPropertiesMain.cpp b/media/jni/mediaeditor/VideoEditorPropertiesMain.cpp index c8fb263..2f8e357 100644 --- a/media/jni/mediaeditor/VideoEditorPropertiesMain.cpp +++ b/media/jni/mediaeditor/VideoEditorPropertiesMain.cpp @@ -14,6 +14,8 @@ * limitations under the License. */ +#define LOG_TAG "VideoEditorPropertiesMain" + #include <dlfcn.h> #include <stdio.h> #include <unistd.h> diff --git a/media/jni/soundpool/Android.mk b/media/jni/soundpool/Android.mk index 5835b9f..ed8d7c1 100644 --- a/media/jni/soundpool/Android.mk +++ b/media/jni/soundpool/Android.mk @@ -2,7 +2,7 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ - android_media_SoundPool.cpp + android_media_SoundPool_SoundPoolImpl.cpp LOCAL_SHARED_LIBRARIES := \ liblog \ diff --git a/media/jni/soundpool/android_media_SoundPool.cpp b/media/jni/soundpool/android_media_SoundPool_SoundPoolImpl.cpp index 9658856..2604850 100644 --- a/media/jni/soundpool/android_media_SoundPool.cpp +++ b/media/jni/soundpool/android_media_SoundPool_SoundPoolImpl.cpp @@ -39,9 +39,9 @@ static inline SoundPool* MusterSoundPool(JNIEnv *env, jobject thiz) { // ---------------------------------------------------------------------------- static int -android_media_SoundPool_load_URL(JNIEnv *env, jobject thiz, jstring path, jint priority) +android_media_SoundPool_SoundPoolImpl_load_URL(JNIEnv *env, jobject thiz, jstring path, jint priority) { - ALOGV("android_media_SoundPool_load_URL"); + ALOGV("android_media_SoundPool_SoundPoolImpl_load_URL"); SoundPool *ap = MusterSoundPool(env, thiz); if (path == NULL) { jniThrowException(env, "java/lang/IllegalArgumentException", NULL); @@ -54,10 +54,10 @@ android_media_SoundPool_load_URL(JNIEnv *env, jobject thiz, jstring path, jint p } static int -android_media_SoundPool_load_FD(JNIEnv *env, jobject thiz, jobject fileDescriptor, +android_media_SoundPool_SoundPoolImpl_load_FD(JNIEnv *env, jobject thiz, jobject fileDescriptor, jlong offset, jlong length, jint priority) { - ALOGV("android_media_SoundPool_load_FD"); + ALOGV("android_media_SoundPool_SoundPoolImpl_load_FD"); SoundPool *ap = MusterSoundPool(env, thiz); if (ap == NULL) return 0; return ap->load(jniGetFDFromFileDescriptor(env, fileDescriptor), @@ -65,104 +65,104 @@ android_media_SoundPool_load_FD(JNIEnv *env, jobject thiz, jobject fileDescripto } static bool -android_media_SoundPool_unload(JNIEnv *env, jobject thiz, jint sampleID) { - ALOGV("android_media_SoundPool_unload\n"); +android_media_SoundPool_SoundPoolImpl_unload(JNIEnv *env, jobject thiz, jint sampleID) { + ALOGV("android_media_SoundPool_SoundPoolImpl_unload\n"); SoundPool *ap = MusterSoundPool(env, thiz); if (ap == NULL) return 0; return ap->unload(sampleID); } static int -android_media_SoundPool_play(JNIEnv *env, jobject thiz, jint sampleID, +android_media_SoundPool_SoundPoolImpl_play(JNIEnv *env, jobject thiz, jint sampleID, jfloat leftVolume, jfloat rightVolume, jint priority, jint loop, jfloat rate) { - ALOGV("android_media_SoundPool_play\n"); + ALOGV("android_media_SoundPool_SoundPoolImpl_play\n"); SoundPool *ap = MusterSoundPool(env, thiz); if (ap == NULL) return 0; return ap->play(sampleID, leftVolume, rightVolume, priority, loop, rate); } static void -android_media_SoundPool_pause(JNIEnv *env, jobject thiz, jint channelID) +android_media_SoundPool_SoundPoolImpl_pause(JNIEnv *env, jobject thiz, jint channelID) { - ALOGV("android_media_SoundPool_pause"); + ALOGV("android_media_SoundPool_SoundPoolImpl_pause"); SoundPool *ap = MusterSoundPool(env, thiz); if (ap == NULL) return; ap->pause(channelID); } static void -android_media_SoundPool_resume(JNIEnv *env, jobject thiz, jint channelID) +android_media_SoundPool_SoundPoolImpl_resume(JNIEnv *env, jobject thiz, jint channelID) { - ALOGV("android_media_SoundPool_resume"); + ALOGV("android_media_SoundPool_SoundPoolImpl_resume"); SoundPool *ap = MusterSoundPool(env, thiz); if (ap == NULL) return; ap->resume(channelID); } static void -android_media_SoundPool_autoPause(JNIEnv *env, jobject thiz) +android_media_SoundPool_SoundPoolImpl_autoPause(JNIEnv *env, jobject thiz) { - ALOGV("android_media_SoundPool_autoPause"); + ALOGV("android_media_SoundPool_SoundPoolImpl_autoPause"); SoundPool *ap = MusterSoundPool(env, thiz); if (ap == NULL) return; ap->autoPause(); } static void -android_media_SoundPool_autoResume(JNIEnv *env, jobject thiz) +android_media_SoundPool_SoundPoolImpl_autoResume(JNIEnv *env, jobject thiz) { - ALOGV("android_media_SoundPool_autoResume"); + ALOGV("android_media_SoundPool_SoundPoolImpl_autoResume"); SoundPool *ap = MusterSoundPool(env, thiz); if (ap == NULL) return; ap->autoResume(); } static void -android_media_SoundPool_stop(JNIEnv *env, jobject thiz, jint channelID) +android_media_SoundPool_SoundPoolImpl_stop(JNIEnv *env, jobject thiz, jint channelID) { - ALOGV("android_media_SoundPool_stop"); + ALOGV("android_media_SoundPool_SoundPoolImpl_stop"); SoundPool *ap = MusterSoundPool(env, thiz); if (ap == NULL) return; ap->stop(channelID); } static void -android_media_SoundPool_setVolume(JNIEnv *env, jobject thiz, jint channelID, +android_media_SoundPool_SoundPoolImpl_setVolume(JNIEnv *env, jobject thiz, jint channelID, float leftVolume, float rightVolume) { - ALOGV("android_media_SoundPool_setVolume"); + ALOGV("android_media_SoundPool_SoundPoolImpl_setVolume"); SoundPool *ap = MusterSoundPool(env, thiz); if (ap == NULL) return; ap->setVolume(channelID, leftVolume, rightVolume); } static void -android_media_SoundPool_setPriority(JNIEnv *env, jobject thiz, jint channelID, +android_media_SoundPool_SoundPoolImpl_setPriority(JNIEnv *env, jobject thiz, jint channelID, int priority) { - ALOGV("android_media_SoundPool_setPriority"); + ALOGV("android_media_SoundPool_SoundPoolImpl_setPriority"); SoundPool *ap = MusterSoundPool(env, thiz); if (ap == NULL) return; ap->setPriority(channelID, priority); } static void -android_media_SoundPool_setLoop(JNIEnv *env, jobject thiz, jint channelID, +android_media_SoundPool_SoundPoolImpl_setLoop(JNIEnv *env, jobject thiz, jint channelID, int loop) { - ALOGV("android_media_SoundPool_setLoop"); + ALOGV("android_media_SoundPool_SoundPoolImpl_setLoop"); SoundPool *ap = MusterSoundPool(env, thiz); if (ap == NULL) return; ap->setLoop(channelID, loop); } static void -android_media_SoundPool_setRate(JNIEnv *env, jobject thiz, jint channelID, +android_media_SoundPool_SoundPoolImpl_setRate(JNIEnv *env, jobject thiz, jint channelID, float rate) { - ALOGV("android_media_SoundPool_setRate"); + ALOGV("android_media_SoundPool_SoundPoolImpl_setRate"); SoundPool *ap = MusterSoundPool(env, thiz); if (ap == NULL) return; ap->setRate(channelID, rate); @@ -176,9 +176,9 @@ static void android_media_callback(SoundPoolEvent event, SoundPool* soundPool, v } static jint -android_media_SoundPool_native_setup(JNIEnv *env, jobject thiz, jobject weakRef, jint maxChannels, jint streamType, jint srcQuality) +android_media_SoundPool_SoundPoolImpl_native_setup(JNIEnv *env, jobject thiz, jobject weakRef, jint maxChannels, jint streamType, jint srcQuality) { - ALOGV("android_media_SoundPool_native_setup"); + ALOGV("android_media_SoundPool_SoundPoolImpl_native_setup"); SoundPool *ap = new SoundPool(maxChannels, (audio_stream_type_t) streamType, srcQuality); if (ap == NULL) { return -1; @@ -194,9 +194,9 @@ android_media_SoundPool_native_setup(JNIEnv *env, jobject thiz, jobject weakRef, } static void -android_media_SoundPool_release(JNIEnv *env, jobject thiz) +android_media_SoundPool_SoundPoolImpl_release(JNIEnv *env, jobject thiz) { - ALOGV("android_media_SoundPool_release"); + ALOGV("android_media_SoundPool_SoundPoolImpl_release"); SoundPool *ap = MusterSoundPool(env, thiz); if (ap != NULL) { @@ -219,67 +219,67 @@ android_media_SoundPool_release(JNIEnv *env, jobject thiz) static JNINativeMethod gMethods[] = { { "_load", "(Ljava/lang/String;I)I", - (void *)android_media_SoundPool_load_URL + (void *)android_media_SoundPool_SoundPoolImpl_load_URL }, { "_load", "(Ljava/io/FileDescriptor;JJI)I", - (void *)android_media_SoundPool_load_FD + (void *)android_media_SoundPool_SoundPoolImpl_load_FD }, { "unload", "(I)Z", - (void *)android_media_SoundPool_unload + (void *)android_media_SoundPool_SoundPoolImpl_unload }, { "play", "(IFFIIF)I", - (void *)android_media_SoundPool_play + (void *)android_media_SoundPool_SoundPoolImpl_play }, { "pause", "(I)V", - (void *)android_media_SoundPool_pause + (void *)android_media_SoundPool_SoundPoolImpl_pause }, { "resume", "(I)V", - (void *)android_media_SoundPool_resume + (void *)android_media_SoundPool_SoundPoolImpl_resume }, { "autoPause", "()V", - (void *)android_media_SoundPool_autoPause + (void *)android_media_SoundPool_SoundPoolImpl_autoPause }, { "autoResume", "()V", - (void *)android_media_SoundPool_autoResume + (void *)android_media_SoundPool_SoundPoolImpl_autoResume }, { "stop", "(I)V", - (void *)android_media_SoundPool_stop + (void *)android_media_SoundPool_SoundPoolImpl_stop }, { "setVolume", "(IFF)V", - (void *)android_media_SoundPool_setVolume + (void *)android_media_SoundPool_SoundPoolImpl_setVolume }, { "setPriority", "(II)V", - (void *)android_media_SoundPool_setPriority + (void *)android_media_SoundPool_SoundPoolImpl_setPriority }, { "setLoop", "(II)V", - (void *)android_media_SoundPool_setLoop + (void *)android_media_SoundPool_SoundPoolImpl_setLoop }, { "setRate", "(IF)V", - (void *)android_media_SoundPool_setRate + (void *)android_media_SoundPool_SoundPoolImpl_setRate }, { "native_setup", "(Ljava/lang/Object;III)I", - (void*)android_media_SoundPool_native_setup + (void*)android_media_SoundPool_SoundPoolImpl_native_setup }, { "release", "()V", - (void*)android_media_SoundPool_release + (void*)android_media_SoundPool_SoundPoolImpl_release } }; -static const char* const kClassPathName = "android/media/SoundPool"; +static const char* const kClassPathName = "android/media/SoundPool$SoundPoolImpl"; jint JNI_OnLoad(JavaVM* vm, void* reserved) { @@ -301,14 +301,14 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) fields.mNativeContext = env->GetFieldID(clazz, "mNativeContext", "I"); if (fields.mNativeContext == NULL) { - ALOGE("Can't find SoundPool.mNativeContext"); + ALOGE("Can't find SoundPoolImpl.mNativeContext"); goto bail; } fields.mPostEvent = env->GetStaticMethodID(clazz, "postEventFromNative", "(Ljava/lang/Object;IIILjava/lang/Object;)V"); if (fields.mPostEvent == NULL) { - ALOGE("Can't find android/media/SoundPool.postEventFromNative"); + ALOGE("Can't find android/media/SoundPoolImpl.postEventFromNative"); goto bail; } diff --git a/media/libdrm/Android.mk b/media/libdrm/Android.mk deleted file mode 100644 index 5053e7d..0000000 --- a/media/libdrm/Android.mk +++ /dev/null @@ -1 +0,0 @@ -include $(call all-subdir-makefiles) diff --git a/media/libdrm/MODULE_LICENSE_APACHE2 b/media/libdrm/MODULE_LICENSE_APACHE2 deleted file mode 100644 index e69de29..0000000 --- a/media/libdrm/MODULE_LICENSE_APACHE2 +++ /dev/null diff --git a/media/libdrm/NOTICE b/media/libdrm/NOTICE deleted file mode 100644 index c5b1efa..0000000 --- a/media/libdrm/NOTICE +++ /dev/null @@ -1,190 +0,0 @@ - - Copyright (c) 2005-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. - - 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. - - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - diff --git a/media/libdrm/mobile1/Android.mk b/media/libdrm/mobile1/Android.mk deleted file mode 100644 index 7356f46..0000000 --- a/media/libdrm/mobile1/Android.mk +++ /dev/null @@ -1,83 +0,0 @@ -LOCAL_PATH := $(call my-dir) - -# --------------------------------------- -# First project -# -# Build DRM1 core library -# -# Output: libdrm1.so -# --------------------------------------- -include $(CLEAR_VARS) - -ifeq ($(TARGET_ARCH), arm) -LOCAL_DRM_CFLAG = -DDRM_DEVICE_ARCH_ARM -endif - -ifeq ($(TARGET_ARCH), x86) -LOCAL_DRM_CFLAG = -DDRM_DEVICE_ARCH_X86 -endif - -# DRM 1.0 core source files -LOCAL_SRC_FILES := \ - src/objmng/drm_decoder.c \ - src/objmng/drm_file.c \ - src/objmng/drm_i18n.c \ - src/objmng/drm_time.c \ - src/objmng/drm_api.c \ - src/objmng/drm_rights_manager.c \ - src/parser/parser_dcf.c \ - src/parser/parser_dm.c \ - src/parser/parser_rel.c \ - src/xml/xml_tinyparser.c - -# Header files path -LOCAL_C_INCLUDES := \ - $(LOCAL_PATH)/include \ - $(LOCAL_PATH)/include/objmng \ - $(LOCAL_PATH)/include/parser \ - $(LOCAL_PATH)/include/xml \ - external/openssl/include \ - $(call include-path-for, system-core)/cutils - -LOCAL_CFLAGS := $(LOCAL_DRM_CFLAG) - -LOCAL_SHARED_LIBRARIES := \ - libutils \ - libcutils \ - liblog \ - libcrypto - -LOCAL_MODULE := libdrm1 - -include $(BUILD_SHARED_LIBRARY) - -# --------------------------------------- -# Second project -# -# Build DRM1 Java Native Interface(JNI) library -# -# Output: libdrm1_jni.so -# ------------------------------------------------ -include $(CLEAR_VARS) - -# Source files of DRM1 Java Native Interfaces -LOCAL_SRC_FILES := \ - src/jni/drm1_jni.c - -# Header files path -LOCAL_C_INCLUDES := \ - $(LOCAL_PATH)/include \ - $(LOCAL_PATH)/include/parser \ - $(JNI_H_INCLUDE) \ - $(call include-path-for, system-core)/cutils - - -LOCAL_SHARED_LIBRARIES := libdrm1 \ - libnativehelper \ - libutils \ - libcutils \ - liblog - -LOCAL_MODULE := libdrm1_jni - -include $(BUILD_SHARED_LIBRARY) diff --git a/media/libdrm/mobile1/include/jni/drm1_jni.h b/media/libdrm/mobile1/include/jni/drm1_jni.h deleted file mode 100644 index 64e78ad..0000000 --- a/media/libdrm/mobile1/include/jni/drm1_jni.h +++ /dev/null @@ -1,242 +0,0 @@ -/* - * 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. - */ - -#ifndef __DRM1_JNI_H__ -#define __DRM1_JNI_H__ - -#ifdef __cplusplus -extern "C" { -#endif - -/* DO NOT EDIT THIS FILE - it is machine generated */ -#include <jni.h> -/* Header for class android_drm_mobile1_DrmRawContent */ - -#undef android_drm_mobile1_DrmRawContent_DRM_FORWARD_LOCK -#define android_drm_mobile1_DrmRawContent_DRM_FORWARD_LOCK 1L -#undef android_drm_mobile1_DrmRawContent_DRM_COMBINED_DELIVERY -#define android_drm_mobile1_DrmRawContent_DRM_COMBINED_DELIVERY 2L -#undef android_drm_mobile1_DrmRawContent_DRM_SEPARATE_DELIVERY -#define android_drm_mobile1_DrmRawContent_DRM_SEPARATE_DELIVERY 3L -#undef android_drm_mobile1_DrmRawContent_DRM_SEPARATE_DELIVERY_DM -#define android_drm_mobile1_DrmRawContent_DRM_SEPARATE_DELIVERY_DM 4L -#undef android_drm_mobile1_DrmRawContent_DRM_MIMETYPE_MESSAGE -#define android_drm_mobile1_DrmRawContent_DRM_MIMETYPE_MESSAGE 1L -#undef android_drm_mobile1_DrmRawContent_DRM_MIMETYPE_CONTENT -#define android_drm_mobile1_DrmRawContent_DRM_MIMETYPE_CONTENT 2L -#undef android_drm_mobile1_DrmRawContent_JNI_DRM_SUCCESS -#define android_drm_mobile1_DrmRawContent_JNI_DRM_SUCCESS 0L -#undef android_drm_mobile1_DrmRawContent_JNI_DRM_FAILURE -#define android_drm_mobile1_DrmRawContent_JNI_DRM_FAILURE -1L -#undef android_drm_mobile1_DrmRawContent_JNI_DRM_EOF -#define android_drm_mobile1_DrmRawContent_JNI_DRM_EOF -2L -#undef android_drm_mobile1_DrmRawContent_JNI_DRM_UNKNOWN_DATA_LEN -#define android_drm_mobile1_DrmRawContent_JNI_DRM_UNKNOWN_DATA_LEN -3L -/* - * Class: android_drm_mobile1_DrmRawContent - * Method: nativeConstructDrmContent - * Signature: (Ljava/io/InputStream;II)I - */ -JNIEXPORT jint JNICALL Java_android_drm_mobile1_DrmRawContent_nativeConstructDrmContent - (JNIEnv *, jobject, jobject, jint, jint); - -/* - * Class: android_drm_mobile1_DrmRawContent - * Method: nativeGetRightsAddress - * Signature: ()Ljava/lang/String; - */ -JNIEXPORT jstring JNICALL Java_android_drm_mobile1_DrmRawContent_nativeGetRightsAddress - (JNIEnv *, jobject); - -/* - * Class: android_drm_mobile1_DrmRawContent - * Method: nativeGetDeliveryMethod - * Signature: ()I - */ -JNIEXPORT jint JNICALL Java_android_drm_mobile1_DrmRawContent_nativeGetDeliveryMethod - (JNIEnv *, jobject); - -/* - * Class: android_drm_mobile1_DrmRawContent - * Method: nativeReadPieceOfContent - * Signature: ([BIII)I - */ -JNIEXPORT jint JNICALL Java_android_drm_mobile1_DrmRawContent_nativeReadContent - (JNIEnv *, jobject, jbyteArray, jint, jint, jint); - -/* - * Class: android_drm_mobile1_DrmRawContent - * Method: nativeGetContentType - * Signature: ()Ljava/lang/String; - */ -JNIEXPORT jstring JNICALL Java_android_drm_mobile1_DrmRawContent_nativeGetContentType - (JNIEnv *, jobject); - -/* - * Class: android_drm_mobile1_DrmRawContent - * Method: nativeGetContentLength - * Signature: ()I - */ -JNIEXPORT jint JNICALL Java_android_drm_mobile1_DrmRawContent_nativeGetContentLength - (JNIEnv *, jobject); - -/* - * Class: android_drm_mobile1_DrmRawContent - * Method: finalize - * Signature: ()V - */ -JNIEXPORT void JNICALL Java_android_drm_mobile1_DrmRawContent_finalize - (JNIEnv *, jobject); - -/* Header for class android_drm_mobile1_DrmRights */ - -#undef android_drm_mobile1_DrmRights_DRM_PERMISSION_PLAY -#define android_drm_mobile1_DrmRights_DRM_PERMISSION_PLAY 1L -#undef android_drm_mobile1_DrmRights_DRM_PERMISSION_DISPLAY -#define android_drm_mobile1_DrmRights_DRM_PERMISSION_DISPLAY 2L -#undef android_drm_mobile1_DrmRights_DRM_PERMISSION_EXECUTE -#define android_drm_mobile1_DrmRights_DRM_PERMISSION_EXECUTE 3L -#undef android_drm_mobile1_DrmRights_DRM_PERMISSION_PRINT -#define android_drm_mobile1_DrmRights_DRM_PERMISSION_PRINT 4L -#undef android_drm_mobile1_DrmRights_DRM_CONSUME_RIGHTS_SUCCESS -#define android_drm_mobile1_DrmRights_DRM_CONSUME_RIGHTS_SUCCESS 0L -#undef android_drm_mobile1_DrmRights_DRM_CONSUME_RIGHTS_FAILURE -#define android_drm_mobile1_DrmRights_DRM_CONSUME_RIGHTS_FAILURE -1L -#undef android_drm_mobile1_DrmRights_JNI_DRM_SUCCESS -#define android_drm_mobile1_DrmRights_JNI_DRM_SUCCESS 0L -#undef android_drm_mobile1_DrmRights_JNI_DRM_FAILURE -#define android_drm_mobile1_DrmRights_JNI_DRM_FAILURE -1L -/* - * Class: android_drm_mobile1_DrmRights - * Method: nativeGetConstraintInfo - * Signature: (ILandroid/drm/mobile1/DrmConstraintInfo;)I - */ -JNIEXPORT jint JNICALL Java_android_drm_mobile1_DrmRights_nativeGetConstraintInfo - (JNIEnv *, jobject, jint, jobject); - -/* - * Class: android_drm_mobile1_DrmRights - * Method: nativeConsumeRights - * Signature: (I)I - */ -JNIEXPORT jint JNICALL Java_android_drm_mobile1_DrmRights_nativeConsumeRights - (JNIEnv *, jobject, jint); - -/* Header for class android_drm_mobile1_DrmRightsManager */ - -#undef android_drm_mobile1_DrmRightsManager_DRM_MIMETYPE_RIGHTS_XML -#define android_drm_mobile1_DrmRightsManager_DRM_MIMETYPE_RIGHTS_XML 3L -#undef android_drm_mobile1_DrmRightsManager_DRM_MIMETYPE_RIGHTS_WBXML -#define android_drm_mobile1_DrmRightsManager_DRM_MIMETYPE_RIGHTS_WBXML 4L -#undef android_drm_mobile1_DrmRightsManager_DRM_MIMETYPE_MESSAGE -#define android_drm_mobile1_DrmRightsManager_DRM_MIMETYPE_MESSAGE 1L -#undef android_drm_mobile1_DrmRightsManager_JNI_DRM_SUCCESS -#define android_drm_mobile1_DrmRightsManager_JNI_DRM_SUCCESS 0L -#undef android_drm_mobile1_DrmRightsManager_JNI_DRM_FAILURE -#define android_drm_mobile1_DrmRightsManager_JNI_DRM_FAILURE -1L -/* Inaccessible static: singleton */ -/* - * Class: android_drm_mobile1_DrmRightsManager - * Method: nativeInstallDrmRights - * Signature: (Ljava/io/InputStream;IILandroid/drm/mobile1/DrmRights;)I - */ -JNIEXPORT jint JNICALL Java_android_drm_mobile1_DrmRightsManager_nativeInstallDrmRights - (JNIEnv *, jobject, jobject, jint, jint, jobject); - -/* - * Class: android_drm_mobile1_DrmRightsManager - * Method: nativeQueryRights - * Signature: (Landroid/drm/mobile1/DrmRawContent;Landroid/drm/mobile1/DrmRights;)I - */ -JNIEXPORT jint JNICALL Java_android_drm_mobile1_DrmRightsManager_nativeQueryRights - (JNIEnv *, jobject, jobject, jobject); - -/* - * Class: android_drm_mobile1_DrmRightsManager - * Method: nativeGetRightsNumber - * Signature: ()I - */ -JNIEXPORT jint JNICALL Java_android_drm_mobile1_DrmRightsManager_nativeGetNumOfRights - (JNIEnv *, jobject); - -/* - * Class: android_drm_mobile1_DrmRightsManager - * Method: nativeGetRightsList - * Signature: ([Landroid/drm/mobile1/DrmRights;I)I - */ -JNIEXPORT jint JNICALL Java_android_drm_mobile1_DrmRightsManager_nativeGetRightsList - (JNIEnv *, jobject, jobjectArray, jint); - -/* - * Class: android_drm_mobile1_DrmRightsManager - * Method: nativeDeleteRights - * Signature: (Landroid/drm/mobile1/DrmRights;)I - */ -JNIEXPORT jint JNICALL Java_android_drm_mobile1_DrmRightsManager_nativeDeleteRights - (JNIEnv *, jobject, jobject); - -/** - * DRM return value defines - */ -#define JNI_DRM_SUCCESS \ - android_drm_mobile1_DrmRawContent_JNI_DRM_SUCCESS /**< Successful operation */ -#define JNI_DRM_FAILURE \ - android_drm_mobile1_DrmRawContent_JNI_DRM_FAILURE /**< General failure */ -#define JNI_DRM_EOF \ - android_drm_mobile1_DrmRawContent_JNI_DRM_EOF /**< Indicates the end of the DRM content is reached */ -#define JNI_DRM_UNKNOWN_DATA_LEN \ - android_drm_mobile1_DrmRawContent_JNI_DRM_UNKNOWN_DATA_LEN /**< Indicates the data length is unknown */ - -/** - * DRM MIME type defines - */ -#define JNI_DRM_MIMETYPE_MESSAGE \ - android_drm_mobile1_DrmRawContent_DRM_MIMETYPE_MESSAGE /**< The "application/vnd.oma.drm.message" MIME type */ -#define JNI_DRM_MIMETYPE_CONTENT \ - android_drm_mobile1_DrmRawContent_DRM_MIMETYPE_CONTENT /**< The "application/vnd.oma.drm.content" MIME type */ -#define JNI_DRM_MIMETYPE_RIGHTS_XML \ - android_drm_mobile1_DrmRightsManager_DRM_MIMETYPE_RIGHTS_XML /**< The "application/vnd.oma.drm.rights+xml" MIME type */ -#define JNI_DRM_MIMETYPE_RIGHTS_WBXML \ - android_drm_mobile1_DrmRightsManager_DRM_MIMETYPE_RIGHTS_WBXML /**< The "application/vnd.oma.drm.rights+wbxml" MIME type */ - -/** - * DRM permission defines - */ -#define JNI_DRM_PERMISSION_PLAY \ - android_drm_mobile1_DrmRights_DRM_PERMISSION_PLAY /**< The permission to play */ -#define JNI_DRM_PERMISSION_DISPLAY \ - android_drm_mobile1_DrmRights_DRM_PERMISSION_DISPLAY /**< The permission to display */ -#define JNI_DRM_PERMISSION_EXECUTE \ - android_drm_mobile1_DrmRights_DRM_PERMISSION_EXECUTE /**< The permission to execute */ -#define JNI_DRM_PERMISSION_PRINT \ - android_drm_mobile1_DrmRights_DRM_PERMISSION_PRINT /**< The permission to print */ - -/** - * DRM delivery type defines - */ -#define JNI_DRM_FORWARD_LOCK \ - android_drm_mobile1_DrmRawContent_DRM_FORWARD_LOCK /**< forward lock */ -#define JNI_DRM_COMBINED_DELIVERY \ - android_drm_mobile1_DrmRawContent_DRM_COMBINED_DELIVERY /**< combined delivery */ -#define JNI_DRM_SEPARATE_DELIVERY \ - android_drm_mobile1_DrmRawContent_DRM_SEPARATE_DELIVERY /**< separate delivery */ -#define JNI_DRM_SEPARATE_DELIVERY_DM \ - android_drm_mobile1_DrmRawContent_DRM_SEPARATE_DELIVERY_DM /**< separate delivery DRM message */ -#ifdef __cplusplus -} -#endif -#endif /* __DRM1_JNI_H__ */ - diff --git a/media/libdrm/mobile1/include/objmng/drm_decoder.h b/media/libdrm/mobile1/include/objmng/drm_decoder.h deleted file mode 100644 index a769c81..0000000 --- a/media/libdrm/mobile1/include/objmng/drm_decoder.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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. - */ - -/** - * @file drm_decoder.h - * - * provide service to decode base64 data. - * - * <!-- #interface list begin --> - * \section drm decoder interface - * - drm_decodeBase64() - * <!-- #interface list end --> - */ - -#ifndef __DRM_DECODER_H__ -#define __DRM_DECODER_H__ - -#ifdef __cplusplus -extern "C" { -#endif - -#include <drm_common_types.h> - -/** - * Decode base64 - * \param dest dest buffer to save decode base64 data - * \param destLen dest buffer length - * \param src source data to be decoded - * \param srcLen source buffer length, and when return, give out how many bytes has been decoded - * \return - * -when success, return a positive integer of dest buffer length, - * if input dest buffer is NULL or destLen is 0, - * return dest buffer length that user should allocate to save decoding data - * -when failed, return -1 - */ -int32_t drm_decodeBase64(uint8_t * dest, int32_t destLen, uint8_t * src, int32_t * srcLen); - -#ifdef __cplusplus -} -#endif - -#endif /* __DRM_DECODER_H__ */ diff --git a/media/libdrm/mobile1/include/objmng/drm_file.h b/media/libdrm/mobile1/include/objmng/drm_file.h deleted file mode 100644 index b94ddd0..0000000 --- a/media/libdrm/mobile1/include/objmng/drm_file.h +++ /dev/null @@ -1,296 +0,0 @@ -/* - * 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. - */ - - -/** - * File Porting Layer. - */ -#ifndef __DRM_FILE_H__ -#define __DRM_FILE_H__ - -#ifdef __cplusplus -extern "C" { -#endif - -#include <drm_common_types.h> - -/** Type value of a regular file or file name. */ -#define DRM_FILE_ISREG 1 -/** Type value of a directory or directory name. */ -#define DRM_FILE_ISDIR 2 -/** Type value of a filter name */ -#define DRM_FILE_ISFILTER 3 - - -/** Return code that indicates successful completion of an operation. */ -#define DRM_FILE_SUCCESS 0 -/** Indicates that an operation failed. */ -#define DRM_FILE_FAILURE -1 -/** Indicates that the a DRM_file_read() call reached the end of the file. */ -#define DRM_FILE_EOF -2 - - -/** Open for read access. */ -#define DRM_FILE_MODE_READ 1 -/** Open for write access. */ -#define DRM_FILE_MODE_WRITE 2 - - -#ifndef MAX_FILENAME_LEN -/** Maximum number of characters that a filename may have. By default assumes - * that the entry results of DRM_file_listNextEntry() are returned in the async state - * buffer, after the #DRM_file_result_s, and calculates the maximum name - * from that. - */ -#define MAX_FILENAME_LEN 1024 -#endif - - -/** - * Performs one-time initialization of the File System (FS). - * This function is called once during the lifetime of an application, - * and before any call to <code>DRM_file_*</code> functions by this application. - * When several applications are using the file interface, this function may be called - * several times, once per application. - * - * @return #DRM_FILE_SUCCESS, #DRM_FILE_FAILURE. - */ -int32_t DRM_file_startup(void); - -/** - * Returns the length of a file (by name, opened or unopened). - * - * @param name Name of the file, UCS-2 encoded. - * @param nameChars Number characters encoded in name. - * asynchronous operation returns #DRM_FILE_WOULDBLOCK. - * @return #DRM_FILE_WOULDBLOCK, #DRM_FILE_FAILURE or the file length. - */ -int32_t DRM_file_getFileLength(const uint16_t* name, - int32_t nameChars); - -/** - * Initializes a list iteration session. - * - * @param prefix Prefix that must be matched, UCS-2 encoded. * - * @param prefixChars Number characters encoded in prefix. - * @param session List session identifier. - * @param iteration List iteration identifier. - * - * @return #DRM_FILE_WOULDBLOCK, #DRM_FILE_SUCCESS, #DRM_FILE_FAILURE. - */ -int32_t DRM_file_listOpen(const uint16_t* prefix, - int32_t prefixChars, - int32_t* session, - int32_t* iteration); - -/** - * Used to fetch a list of file names that match a given name prefix. - * - * @param prefix See DRM_file_listOpen(). This does not change during the - * iteration session. - * @param prefixChars See DRM_file_listOpen(). This does not change during - * the iteration session. - * @param entry Buffer parameter to return the next file name that matches the - * #prefix parameter, if any, when the function returns a positive number of - * characters. - * @param entryBytes Size of entry in bytes. - * @param session See DRM_file_listOpen(). - * @param iteration See DRM_file_listOpen(). - * @return #DRM_FILE_WOULDBLOCK, #DRM_FILE_FAILURE or the number of - * characters encoded in entry. Returns 0 when the end of the list is reached. - */ -int32_t DRM_file_listNextEntry(const uint16_t* prefix, - int32_t prefixChars, - uint16_t* entry, - int32_t entryBytes, - int32_t* session, - int32_t* iteration); - -/** - * Ends a list iteration session. Notifies the implementation - * that the list session is over and that any session resources - * can be released. - * - * @param session See DRM_file_listOpen(). - * @param iteration See DRM_file_listOpen(). - * @return #DRM_FILE_WOULDBLOCK, #DRM_FILE_SUCCESS, #DRM_FILE_FAILURE. - */ -int32_t DRM_file_listClose(int32_t session, int32_t iteration); - -/** - * Renames a file, given its old name. The file or directory is renamed - * immediately on the actual file system upon invocation of this method. - * Any open handles on the file specified by oldName become invalid after - * this method has been called. - * - * @param oldName Current file name (unopened), UCS-2 encoded. - * @param oldNameChars Number of characters encoded on oldName. - * @param newName New name for the file (unopened), UCS-2 encoded. - * @param newNameChars Number of characters encoded on newName. - * @return #DRM_FILE_WOULDBLOCK, #DRM_FILE_SUCCESS, #DRM_FILE_FAILURE. In particular, - * #DRM_FILE_FAILURE if a file or directory already exists with the new name. - */ -int32_t DRM_file_rename(const uint16_t* oldName, - int32_t oldNameChars, - const uint16_t* newName, - int32_t newNameChars); - -/** - * Tests if a file exists given its name. - * - * @param name Name of the file, UCS-2 encoded. - * @param nameChars Number of characters encoded in name. - * @return #DRM_FILE_WOULDBLOCK, #DRM_FILE_ISREG, #DRM_FILE_ISDIR, #DRM_FILE_FAILURE. If name - * exists, returns #DRM_FILE_ISREG if it is a regular file and #DRM_FILE_ISDIR if it is a directory. - * Returns #DRM_FILE_FAILURE in all other cases, including those where name exists but is neither - * a regular file nor a directory. Platforms that do not support directories MUST NOT return - * #DRM_FILE_ISDIR. - */ -int32_t DRM_file_exists(const uint16_t* name, - int32_t nameChars); - -/** - * Opens a file with the given name and returns its file handle. - * - * @param name Name of the file, UCS-2 encoded. - * @param nameChars Number of characters encoded in name. - * @param mode Any combination of the #DRM_FILE_MODE_READ and - * #DRM_FILE_MODE_WRITE flags. If the file does not exist and mode contains the - * #DRM_FILE_MODE_WRITE flag, then the file is automatically created. If the - * file exists and the mode contains the #DRM_FILE_MODE_WRITE flag, the file is - * opened so it can be modified, but the data is not modified by the open call. - * In all cases the current position is set to the start of the file. - * The following table shows how to map the mode semantics above to UNIX - * fopen-style modes. For brevity in the table, R=#DRM_FILE_MODE_READ, - * W=#DRM_FILE_MODE_WRITE, E=File exists: - * <table> - * <tr><td>RW</td><td>E</td><td>Maps-to</td></tr> - * <tr><td>00</td><td>0</td><td>Return #DRM_FILE_FAILURE</td></tr> - * <tr><td>00</td><td>1</td><td>Return #DRM_FILE_FAILURE</td></tr> - * <tr><td>01</td><td>0</td><td>Use fopen mode "w"</td></tr> - * <tr><td>01</td><td>1</td><td>Use fopen mode "a" and fseek to the start</td></tr> - * <tr><td>10</td><td>0</td><td>Return #DRM_FILE_FAILURE</td></tr> - * <tr><td>10</td><td>1</td><td>Use fopen mode "r"</td></tr> - * <tr><td>11</td><td>0</td><td>Use fopen mode "w+"</td></tr> - * <tr><td>11</td><td>1</td><td>Use fopen mode "r+"</td></tr> - * </table> - * @param handle Pointer where the result handle value is placed when the function - * is called synchronously. - * @return #DRM_FILE_WOULDBLOCK, #DRM_FILE_SUCCESS, #DRM_FILE_FAILURE. - */ -int32_t DRM_file_open(const uint16_t* name, - int32_t nameChars, - int32_t mode, - int32_t* handle); - -/** - * Deletes a file given its name, UCS-2 encoded. The file or directory is - * deleted immediately on the actual file system upon invocation of this - * method. Any open handles on the file specified by name become invalid - * after this method has been called. - * - * If the port needs to ensure that a specific application does not exceed a given storage - * space quota, then the bytes freed by the deletion must be added to the available space for - * that application. - * - * @param name Name of the file, UCS-2 encoded. - * @param nameChars Number of characters encoded in name. - * @return #DRM_FILE_WOULDBLOCK, #DRM_FILE_SUCCESS, #DRM_FILE_FAILURE. - */ -int32_t DRM_file_delete(const uint16_t* name, - int32_t nameChars); - -/** - * Read bytes from a file at the current position to a buffer. Afterwards the - * new file position is the byte after the last byte read. - * DRM_FILE_FAILURE is returned if the handle is invalid (e.g., as a - * consquence of DRM_file_delete, DRM_file_rename, or DRM_file_close). - * - * @param handle File handle as returned by DRM_file_open(). - * @param dst Buffer where the data is to be copied. - * @param length Number of bytes to be copied. - * @return #DRM_FILE_WOULDBLOCK, #DRM_FILE_SUCCESS, #DRM_FILE_FAILURE, #DRM_FILE_EOF - * or the number of bytes that were read, i.e. in the range 0..length. - */ -int32_t DRM_file_read(int32_t handle, - uint8_t* dst, - int32_t length); - -/** - * Write bytes from a buffer to the file at the current position. If the - * current position + number of bytes written > current size of the file, - * then the file is grown. Afterwards the new file position is the byte - * after the last byte written. - * DRM_FILE_FAILURE is returned if the handle is invalid (e.g., as a - * consquence of DRM_file_delete, DRM_file_rename, or DRM_file_close). - * - * @param handle File handle as returned by DRM_file_open(). - * @param src Buffer that contains the bytes to be written. - * @param length Number of bytes to be written. - * If the port needs to ensure that a specific application does not exceed a given storage - * space quota, the implementation must make sure the call does not violate that invariant. - * @return #DRM_FILE_WOULDBLOCK, #DRM_FILE_FAILURE or the number of bytes - * that were written. This number must be in the range 0..length. - * Returns #DRM_FILE_FAILURE when storage is full or exceeds quota. - */ -int32_t DRM_file_write(int32_t handle, - const uint8_t* src, - int32_t length); - -/** - * Closes a file. - * DRM_FILE_SUCCESS is returned if the handle is invalid (e.g., as a - * consquence of DRM_file_delete or DRM_file_rename). - * - * @param handle File handle as returned by DRM_file_open(). - * @return #DRM_FILE_WOULDBLOCK, #DRM_FILE_SUCCESS, #DRM_FILE_FAILURE. - */ -int32_t DRM_file_close(int32_t handle); - -/** - * Sets the current position in an opened file. - * DRM_FILE_FAILURE is returned if the handle is invalid (e.g., as a - * consquence of DRM_file_delete, DRM_file_rename, or DRM_file_close). - * - * @param handle File handle as returned by DRM_file_open(). - * @param value The new current position of the file. If value is greater - * than the length of the file then the file should be extended. The contents - * of the newly extended portion of the file is undefined. - * If the port needs to ensure that a specific application does not exceed a given storage - * space quota, the implementation must make sure the call does not violate that invariant. - * @return #DRM_FILE_WOULDBLOCK, #DRM_FILE_SUCCESS, #DRM_FILE_FAILURE. - * Returns #DRM_FILE_FAILURE when storage is full or exceeds quota. - */ -int32_t DRM_file_setPosition(int32_t handle, int32_t value); - -/** - * Creates a directory with the assigned name and full file permissions on - * the file system. The full path to the new directory must already exist. - * The directory is created immediately on the actual file system upon - * invocation of this method. - * - * @param name Name of the directory, UCS-2 encoded. - * @param nameChars Number of characters encoded in name. - * @return #DRM_FILE_WOULDBLOCK, #DRM_FILE_SUCCESS, #DRM_FILE_FAILURE. - */ -int32_t DRM_file_mkdir(const uint16_t* name, - int32_t nameChars); - -#ifdef __cplusplus -} -#endif - -#endif /* __DRM_FILE_H__ */ diff --git a/media/libdrm/mobile1/include/objmng/drm_i18n.h b/media/libdrm/mobile1/include/objmng/drm_i18n.h deleted file mode 100644 index 7487e9b..0000000 --- a/media/libdrm/mobile1/include/objmng/drm_i18n.h +++ /dev/null @@ -1,107 +0,0 @@ -/* - * 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. - */ - - -#ifndef __DRM_I18N_H__ -#define __DRM_I18N_H__ - -#ifdef __cplusplus -extern "C" { -#endif - -#include <drm_common_types.h> - -/** - * @name Charset value defines - * @ingroup i18n - * - * Charset value defines - * see http://msdn.microsoft.com/library/default.asp?url=/library/en-us/intl/unicode_81rn.asp - */ -typedef enum { - DRM_CHARSET_GBK = 936, /** Simplified Chinese GBK (CP936) */ - DRM_CHARSET_GB2312 = 20936, /** Simplified Chinese GB2312 (CP936) */ - DRM_CHARSET_BIG5 = 950, /** BIG5 (CP950) */ - DRM_CHARSET_LATIN1 = 28591, /** ISO 8859-1, Latin 1 */ - DRM_CHARSET_LATIN2 = 28592, /** ISO 8859-2, Latin 2 */ - DRM_CHARSET_LATIN3 = 28593, /** ISO 8859-3, Latin 3 */ - DRM_CHARSET_LATIN4 = 28594, /** ISO 8859-4, Latin 4 */ - DRM_CHARSET_CYRILLIC = 28595, /** ISO 8859-5, Cyrillic */ - DRM_CHARSET_ARABIC = 28596, /** ISO 8859-6, Arabic */ - DRM_CHARSET_GREEK = 28597, /** ISO 8859-7, Greek */ - DRM_CHARSET_HEBREW = 28598, /** ISO 8859-8, Hebrew */ - DRM_CHARSET_LATIN5 = 28599, /** ISO 8859-9, Latin 5 */ - DRM_CHARSET_LATIN6 = 865, /** ISO 8859-10, Latin 6 (not sure here) */ - DRM_CHARSET_THAI = 874, /** ISO 8859-11, Thai */ - DRM_CHARSET_LATIN7 = 1257, /** ISO 8859-13, Latin 7 (not sure here) */ - DRM_CHARSET_LATIN8 = 38598, /** ISO 8859-14, Latin 8 (not sure here) */ - DRM_CHARSET_LATIN9 = 28605, /** ISO 8859-15, Latin 9 */ - DRM_CHARSET_LATIN10 = 28606, /** ISO 8859-16, Latin 10 */ - DRM_CHARSET_UTF8 = 65001, /** UTF-8 */ - DRM_CHARSET_UTF16LE = 1200, /** UTF-16 LE */ - DRM_CHARSET_UTF16BE = 1201, /** UTF-16 BE */ - DRM_CHARSET_HINDI = 57002, /** Hindi/Mac Devanagari */ - DRM_CHARSET_UNSUPPORTED = -1 -} DRM_Charset_t; - -/** - * Convert multibyte string of specified charset to unicode string. - * Note NO terminating '\0' will be appended to the output unicode string. - * - * @param charset Charset of the multibyte string. - * @param mbs Multibyte string to be converted. - * @param mbsLen Number of the bytes (in mbs) to be converted. - * @param wcsBuf Buffer for the converted unicode characters. - * If wcsBuf is NULL, the function returns the number of unicode - * characters required for the buffer. - * @param bufSizeInWideChar The size (in wide char) of wcsBuf - * @param bytesConsumed The number of bytes in mbs that have been successfully - * converted. The value of *bytesConsumed is undefined - * if wcsBuf is NULL. - * - * @return Number of the successfully converted unicode characters if wcsBuf - * is not NULL. If wcsBuf is NULL, returns required unicode buffer - * size. -1 for unrecoverable errors. - */ -int32_t DRM_i18n_mbsToWcs(DRM_Charset_t charset, - const uint8_t *mbs, int32_t mbsLen, - uint16_t *wcsBuf, int32_t bufSizeInWideChar, - int32_t *bytesConsumed); - -/** - * Convert unicode string to multibyte string with specified charset. - * Note NO terminating '\0' will be appended to the output multibyte string. - * - * @param charset Charset of the multibyte string to be converted to. - * @param wcs Unicode string to be converted. - * @param wcsLen Number of the unicode characters (in wcs) to be converted. - * @param mbsBuf Buffer for converted multibyte characters. - * If mbsBuf is NULL, the function returns the number of bytes - * required for the buffer. - * @param bufSizeInByte The size (in byte) of mbsBuf. - * - * @return Number of the successfully converted bytes. - */ -int32_t DRM_i18n_wcsToMbs(DRM_Charset_t charset, - const uint16_t *wcs, int32_t wcsLen, - uint8_t *mbsBuf, int32_t bufSizeInByte); - -#ifdef __cplusplus -} -#endif - -#endif - diff --git a/media/libdrm/mobile1/include/objmng/drm_inner.h b/media/libdrm/mobile1/include/objmng/drm_inner.h deleted file mode 100644 index 55234f8..0000000 --- a/media/libdrm/mobile1/include/objmng/drm_inner.h +++ /dev/null @@ -1,90 +0,0 @@ -/* - * 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. - */ - -#ifndef __DRM_INNER_H__ -#define __DRM_INNER_H__ - -#ifdef __cplusplus -extern "C" { -#endif - -#include <drm_common_types.h> - -#define INT_2_YMD_HMS(year, mon, day, date, hour, min, sec, time) do{\ - year = date / 10000;\ - mon = date % 10000 / 100;\ - day = date %100;\ - hour = time / 10000;\ - min = time % 10000 / 100;\ - sec = time % 100;\ -}while(0) - -/** - * Define the max malloc length for a DRM. - */ -#define DRM_MAX_MALLOC_LEN (50 * 1024) /* 50K */ - -#define DRM_ONE_AES_BLOCK_LEN 16 -#define DRM_TWO_AES_BLOCK_LEN 32 - -typedef struct _T_DRM_DM_Binary_Node { - uint8_t boundary[256]; -} T_DRM_DM_Binary_Node; - -typedef struct _T_DRM_DM_Base64_Node { - uint8_t boundary[256]; - uint8_t b64DecodeData[4]; - int32_t b64DecodeDataLen; -} T_DRM_DM_Base64_Node; - -typedef struct _T_DRM_Dcf_Node { - uint8_t rightsIssuer[256]; - int32_t encContentLength; - uint8_t aesDecData[16]; - int32_t aesDecDataLen; - int32_t aesDecDataOff; - uint8_t aesBackupBuf[16]; - int32_t bAesBackupBuf; -} T_DRM_Dcf_Node; - -typedef struct _T_DRM_Session_Node { - int32_t sessionId; - int32_t inputHandle; - int32_t mimeType; - int32_t (*getInputDataLengthFunc)(int32_t inputHandle); - int32_t (*readInputDataFunc)(int32_t inputHandle, uint8_t* buf, int32_t bufLen); - int32_t (*seekInputDataFunc)(int32_t inputHandle, int32_t offset); - int32_t deliveryMethod; - int32_t transferEncoding; - uint8_t contentType[64]; - int32_t contentLength; - int32_t contentOffset; - uint8_t contentID[256]; - uint8_t* rawContent; - int32_t rawContentLen; - int32_t bEndData; - uint8_t* readBuf; - int32_t readBufLen; - int32_t readBufOff; - void* infoStruct; - struct _T_DRM_Session_Node* next; -} T_DRM_Session_Node; - -#ifdef __cplusplus -} -#endif - -#endif /* __DRM_INNER_H__ */ diff --git a/media/libdrm/mobile1/include/objmng/drm_rights_manager.h b/media/libdrm/mobile1/include/objmng/drm_rights_manager.h deleted file mode 100644 index d81e7a1..0000000 --- a/media/libdrm/mobile1/include/objmng/drm_rights_manager.h +++ /dev/null @@ -1,183 +0,0 @@ -/* - * 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. - */ - -#ifndef __DRM_RIGHTS_MANAGER_H__ -#define __DRM_RIGHTS_MANAGER_H__ - -#ifdef __cplusplus -extern "C" { -#endif - -#include <openssl/aes.h> -#include <drm_common_types.h> -#include <parser_rel.h> - -#ifdef DRM_DEVICE_ARCH_ARM -#define ANDROID_DRM_CORE_PATH "/data/drm/rights/" -#define DRM_UID_FILE_PATH "/data/drm/rights/uid.txt" -#else -#define ANDROID_DRM_CORE_PATH "/home/user/golf/esmertec/device/out/debug/host/linux-x86/product/sim/data/data/com.android.drm.mobile1/" -#define DRM_UID_FILE_PATH "/home/user/golf/esmertec/device/out/debug/host/linux-x86/product/sim/data/data/com.android.drm.mobile1/uid.txt" -#endif - -#define EXTENSION_NAME_INFO ".info" - -#define GET_ID 1 -#define GET_UID 2 - -#define GET_ROAMOUNT 1 -#define GET_ALL_RO 2 -#define SAVE_ALL_RO 3 -#define GET_A_RO 4 -#define SAVE_A_RO 5 - -/** - * Get the id or uid from the "uid.txt" file. - * - * \param Uid The content id for a specially DRM object. - * \param id The id number managed by DRM engine for a specially DRM object. - * \param option The option to get id or uid, the value includes: GET_ID, GET_UID. - * - * \return - * -TRUE, if the operation successfully. - * -FALSE, if the operation failed. - */ -int32_t drm_readFromUidTxt(uint8_t* Uid, int32_t* id, int32_t option); - -/** - * Save or read the rights information on the "id.info" file. - * - * \param id The id number managed by DRM engine for a specially DRM object. - * \param Ro The rights structure to save the rights information. - * \param RoAmount The number of rights for this DRM object. - * \param option The option include: GET_ROAMOUNT, GET_ALL_RO, SAVE_ALL_RO, GET_A_RO, SAVE_A_RO. - * - * \return - * -TRUE, if the operation successfully. - * -FALSE, if the operation failed. - */ -int32_t drm_writeOrReadInfo(int32_t id, T_DRM_Rights* Ro, int32_t* RoAmount, int32_t option); - -/** - * Append a rights information to DRM engine storage. - * - * \param Ro The rights structure to save the rights information. - * - * return - * -TRUE, if the operation successfully. - * -FALSE, if the operation failed. - */ -int32_t drm_appendRightsInfo(T_DRM_Rights* rights); - -/** - * Get the mex id number from the "uid.txt" file. - * - * \return - * -an integer to indicate the max id number. - * -(-1), if the operation failed. - */ -int32_t drm_getMaxIdFromUidTxt(); - -/** - * Remove the "id.info" file if all the rights for this DRM object has been deleted. - * - * \param id The id number managed by DRM engine for a specially DRM object. - * - * \return - * -TRUE, if the operation successfully. - * -FALSE, if the operation failed. - */ -int32_t drm_removeIdInfoFile(int32_t id); - -/** - * Update the "uid.txt" file when delete the rights object. - * - * \param id The id number managed by DRM engine for a specially DRM object. - * - * \return - * -TRUE, if the operation successfully. - * -FALSE, if the operation failed. - */ -int32_t drm_updateUidTxtWhenDelete(int32_t id); - -/** - * Get the CEK according the given content id. - * - * \param uid The content id for a specially DRM object. - * \param KeyValue The buffer to save the CEK. - * - * \return - * -TRUE, if the operation successfully. - * -FALSE, if the operation failed. - */ -int32_t drm_getKey(uint8_t* uid, uint8_t* KeyValue); - -/** - * Discard the padding bytes in DCF decrypted data. - * - * \param decryptedBuf The aes decrypted data buffer to be scanned. - * \param decryptedBufLen The length of the buffer. And save the output result. - * - * \return - * -0 - */ -void drm_discardPaddingByte(uint8_t *decryptedBuf, int32_t *decryptedBufLen); - -/** - * Decrypt the media data according the CEK. - * - * \param Buffer The buffer to decrypted and also used to save the output data. - * \param BufferLen The length of the buffer data and also save the output data length. - * \param key The structure of the CEK. - * - * \return - * -0 - */ -int32_t drm_aesDecBuffer(uint8_t * Buffer, int32_t * BufferLen, AES_KEY *key); - -/** - * Update the DCF data length according the CEK. - * - * \param pDcfLastData The last several byte for the DCF. - * \param keyValue The CEK of the DRM content. - * \param moreBytes Output the more bytes for discarded. - * - * \return - * -TRUE, if the operation successfully. - * -FALSE, if the operation failed. - */ -int32_t drm_updateDcfDataLen(uint8_t* pDcfLastData, uint8_t* keyValue, int32_t* moreBytes); - -/** - * Check and update the rights for a specially DRM content. - * - * \param id The id number managed by DRM engine for a specially DRM object. - * \param permission The permission to be check and updated. - * - * \return - * -DRM_SUCCESS, if there is a valid rights and update it successfully. - * -DRM_NO_RIGHTS, if there is no rights for this content. - * -DRM_RIGHTS_PENDING, if the rights is pending. - * -DRM_RIGHTS_EXPIRED, if the rights has expired. - * -DRM_RIGHTS_FAILURE, if there is some other error occur. - */ -int32_t drm_checkRoAndUpdate(int32_t id, int32_t permission); - -#ifdef __cplusplus -} -#endif - -#endif /* __DRM_RIGHTS_MANAGER_H__ */ diff --git a/media/libdrm/mobile1/include/objmng/drm_time.h b/media/libdrm/mobile1/include/objmng/drm_time.h deleted file mode 100644 index 9b013e6..0000000 --- a/media/libdrm/mobile1/include/objmng/drm_time.h +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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. - */ - - -/** - * @file - * Time Porting Layer - * - * Basic support functions that are needed by time. - * - * <!-- #interface list begin --> - * \section drm_time Interface - * - DRM_time_getElapsedSecondsFrom1970() - * - DRM_time_sleep() - * - DRM_time_getSysTime() - * <!-- #interface list end --> - */ - -#ifndef __DRM_TIME_H__ -#define __DRM_TIME_H__ - -#ifdef __cplusplus -extern "C" { -#endif - -#include <time.h> -#include <drm_common_types.h> - -/** the time format */ -typedef struct __db_system_time_ -{ - uint16_t year; - uint16_t month; - uint16_t day; - uint16_t hour; - uint16_t min; - uint16_t sec; -} T_DB_TIME_SysTime; - -/** - * Get the system time.it's up to UTC - * \return Return the time in elapsed seconds. - */ -uint32_t DRM_time_getElapsedSecondsFrom1970(void); - -/** - * Suspend the execution of the current thread for a specified interval - * \param ms suspended time by millisecond - */ -void DRM_time_sleep(uint32_t ms); - -/** - * function: get current system time - * \param time_ptr[OUT] the system time got - * \attention - * time_ptr must not be NULL - */ -void DRM_time_getSysTime(T_DB_TIME_SysTime *time_ptr); - -#ifdef __cplusplus -} -#endif - -#endif /* __DRM_TIME_H__ */ diff --git a/media/libdrm/mobile1/include/objmng/svc_drm.h b/media/libdrm/mobile1/include/objmng/svc_drm.h deleted file mode 100644 index 789343f..0000000 --- a/media/libdrm/mobile1/include/objmng/svc_drm.h +++ /dev/null @@ -1,376 +0,0 @@ -/* - * 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. - */ - -#ifndef __SVC_DRM_NEW_H__ -#define __SVC_DRM_NEW_H__ - -#ifdef __cplusplus -extern "C" { -#endif - -#include <drm_common_types.h> - -/** - * Define the mime type of DRM data. - */ -#define TYPE_DRM_MESSAGE 0x48 /**< The mime type is "application/vnd.oma.drm.message" */ -#define TYPE_DRM_CONTENT 0x49 /**< The mime type is "application/vnd.oma.drm.content" */ -#define TYPE_DRM_RIGHTS_XML 0x4a /**< The mime type is "application/vnd.oma.drm.rights+xml" */ -#define TYPE_DRM_RIGHTS_WBXML 0x4b /**< The mime type is "application/vnd.oma.drm.rights+wbxml" */ -#define TYPE_DRM_UNKNOWN 0xff /**< The mime type is unknown */ - -/** - * Define the delivery methods. - */ -#define FORWARD_LOCK 1 /**< Forward_lock */ -#define COMBINED_DELIVERY 2 /**< Combined delivery */ -#define SEPARATE_DELIVERY 3 /**< Separate delivery */ -#define SEPARATE_DELIVERY_FL 4 /**< Separate delivery but DCF is forward-lock */ - -/** - * Define the permissions. - */ -#define DRM_PERMISSION_PLAY 0x01 /**< Play */ -#define DRM_PERMISSION_DISPLAY 0x02 /**< Display */ -#define DRM_PERMISSION_EXECUTE 0x04 /**< Execute */ -#define DRM_PERMISSION_PRINT 0x08 /**< Print */ -#define DRM_PERMISSION_FORWARD 0x10 /**< Forward */ - -/** - * Define the constraints. - */ -#define DRM_NO_CONSTRAINT 0x80 /**< Indicate have no constraint, it can use freely */ -#define DRM_END_TIME_CONSTRAINT 0x08 /**< Indicate have end time constraint */ -#define DRM_INTERVAL_CONSTRAINT 0x04 /**< Indicate have interval constraint */ -#define DRM_COUNT_CONSTRAINT 0x02 /**< Indicate have count constraint */ -#define DRM_START_TIME_CONSTRAINT 0x01 /**< Indicate have start time constraint */ -#define DRM_NO_PERMISSION 0x00 /**< Indicate no rights */ - -/** - * Define the return values for those interface. - */ -#define DRM_SUCCESS 0 -#define DRM_FAILURE -1 -#define DRM_MEDIA_EOF -2 -#define DRM_RIGHTS_DATA_INVALID -3 -#define DRM_MEDIA_DATA_INVALID -4 -#define DRM_SESSION_NOT_OPENED -5 -#define DRM_NO_RIGHTS -6 -#define DRM_NOT_SD_METHOD -7 -#define DRM_RIGHTS_PENDING -8 -#define DRM_RIGHTS_EXPIRED -9 -#define DRM_UNKNOWN_DATA_LEN -10 - -/** - * The input DRM data structure, include DM, DCF, DR, DRC. - */ -typedef struct _T_DRM_Input_Data { - /** - * The handle of the input DRM data. - */ - int32_t inputHandle; - - /** - * The mime type of the DRM data, if the mime type set to unknown, DRM engine - * will try to scan the input data to confirm the mime type, but we must say that - * the scan and check of mime type is not strictly precise. - */ - int32_t mimeType; - - /** - * The function to get input data length, this function should be implement by out module, - * and DRM engine will call-back it. - * - * \param inputHandle The handle of the DRM data. - * - * \return - * -A positive integer indicate the length of input data. - * -0, if some error occurred. - */ - int32_t (*getInputDataLength)(int32_t inputHandle); - - /** - * The function to read the input data, this function should be implement by out module, - * and DRM engine will call-back it. - * - * \param inputHandle The handle of the DRM data. - * \param buf The buffer mallocced by DRM engine to save the data. - * \param bufLen The length of the buffer. - * - * \return - * -A positive integer indicate the actually length of byte has been read. - * -0, if some error occurred. - * -(-1), if reach to the end of the data. - */ - int32_t (*readInputData)(int32_t inputHandle, uint8_t* buf, int32_t bufLen); - - /** - * The function to seek the current file pointer, this function should be implement by out module, - * and DRM engine will call-back it. - * - * \param inputHandle The handle of the DRM data. - * \param offset The offset from the start position to be seek. - * - * \return - * -0, if seek operation success. - * -(-1), if seek operation fail. - */ - int32_t (*seekInputData)(int32_t inputHandle, int32_t offset); -} T_DRM_Input_Data; - -/** - * The constraint structure. - */ -typedef struct _T_DRM_Constraint_Info { - uint8_t indicator; /**< Whether there is a right */ - uint8_t unUsed[3]; - int32_t count; /**< The constraint of count */ - int32_t startDate; /**< The constraint of start date */ - int32_t startTime; /**< The constraint of start time */ - int32_t endDate; /**< The constraint of end date */ - int32_t endTime; /**< The constraint of end time */ - int32_t intervalDate; /**< The constraint of interval date */ - int32_t intervalTime; /**< The constraint of interval time */ -} T_DRM_Constraint_Info; - -/** - * The rights permission and constraint information structure. - */ -typedef struct _T_DRM_Rights_Info { - uint8_t roId[256]; /**< The unique id for a specially rights object */ - T_DRM_Constraint_Info playRights; /**< Constraint of play */ - T_DRM_Constraint_Info displayRights; /**< Constraint of display */ - T_DRM_Constraint_Info executeRights; /**< Constraint of execute */ - T_DRM_Constraint_Info printRights; /**< Constraint of print */ -} T_DRM_Rights_Info; - -/** - * The list node of the Rights information structure. - */ -typedef struct _T_DRM_Rights_Info_Node { - T_DRM_Rights_Info roInfo; - struct _T_DRM_Rights_Info_Node *next; -} T_DRM_Rights_Info_Node; - -/** - * Install a rights object to DRM engine, include the rights in Combined Delivery cases. - * Because all the rights object is managed by DRM engine, so every incoming rights object - * must be install to the engine first, or the DRM engine will not recognize it. - * - * \param data The rights object data or Combined Delivery case data. - * \param pRightsInfo The structure to save this rights information. - * - * \return - * -DRM_SUCCESS, when install successfully. - * -DRM_RIGHTS_DATA_INVALID, when the input rights data is invalid. - * -DRM_FAILURE, when some other error occur. - */ -int32_t SVC_drm_installRights(T_DRM_Input_Data data, T_DRM_Rights_Info* pRightsInfo); - -/** - * Open a session for a special DRM object, it will parse the input DRM data, and then user - * can try to get information for this DRM object, or try to use it if the rights is valid. - * - * \param data The DRM object data, DM or DCF. - * - * \return - * -A handle for this opened DRM object session. - * -DRM_MEDIA_DATA_INVALID, when the input DRM object data is invalid. - * -DRM_FAILURE, when some other error occurred. - */ -int32_t SVC_drm_openSession(T_DRM_Input_Data data); - -/** - * Get the delivery method of the DRM object. - * - * \param session The handle for this DRM object session. - * - * \return - * -The delivery method of this DRM object, include: FORWARD_LOCK, COMBINED_DELIVERY, SEPARATE_DELIVERY, SEPARATE_DELIVERY_FL. - * -DRM_FAILURE, when some other error occurred. - */ -int32_t SVC_drm_getDeliveryMethod(int32_t session); - -/** - * Get DRM object media object content type. - * - * \param session The handle for this DRM object session. - * \param mediaType The buffer to save the media type string, 64 bytes is enough. - * - * \return - * -DRM_SUCCESS, when get the media object content type successfully. - * -DRM_SESSION_NOT_OPENED, when the session is not opened or has been closed. - * -DRM_FAILURE, when some other error occured. - */ -int32_t SVC_drm_getContentType(int32_t session, uint8_t* mediaType); - -/** - * Check whether a specific DRM object has the specific permission rights or not. - * - * \param session The handle for this DRM object session. - * \param permission Specify the permission to be checked. - * - * \return - * -DRM_SUCCESS, when it has the rights for the permission. - * -DRM_SESSION_NOT_OPENED, when the session is not opened or has been closed. - * -DRM_NO_RIGHTS, when it has no rights. - * -DRM_RIGHTS_PENDING, when it has the rights, but currently it is pending. - * -DRM_RIGHTS_EXPIRED, when the rights has expired. - * -DRM_FAILURE, when some other error occured. - */ -int32_t SVC_drm_checkRights(int32_t session, int32_t permission); - -/** - * Consume the rights when try to use the DRM object. - * - * \param session The handle for this DRM object session. - * \param permission Specify the permission to be checked. - * - * \return - * -DRM_SUCCESS, when consume rights successfully. - * -DRM_SESSION_NOT_OPENED, when the session is not opened or has been closed. - * -DRM_NO_RIGHTS, when it has no rights. - * -DRM_RIGHTS_PENDING, when it has the rights, but currently it is pending. - * -DRM_RIGHTS_EXPIRED, when the rights has expired. - * -DRM_FAILURE, when some other error occured. - */ -int32_t SVC_drm_consumeRights(int32_t session, int32_t permission); - -/** - * Get DRM media object content data length. - * - * \param session The handle for this DRM object session. - * - * \return - * -A positive integer indicate the length of the media object content data. - * -DRM_SESSION_NOT_OPENED, when the session is not opened or has been closed. - * -DRM_NO_RIGHTS, when the rights object is not existed. - * -DRM_UNKNOWN_DATA_LEN, when DRM object media data length is unknown in case of DCF has no rights. - * -DRM_FAILURE, when some other error occured. - */ -int32_t SVC_drm_getContentLength(int32_t session); - -/** - * Get DRM media object content data. Support get the data piece by piece if the content is too large. - * - * \param session The handle for this DRM object session. - * \param offset The offset to start to get content. - * \param mediaBuf The buffer to save media object data. - * \param mediaBufLen The length of the buffer. - * - * \return - * -A positive integer indicate the actually length of the data has been got. - * -DRM_SESSION_NOT_OPENED, when the session is not opened or has been closed. - * -DRM_NO_RIGHTS, when the rights object is not existed. - * -DRM_MEDIA_EOF, when reach to the end of the media data. - * -DRM_FAILURE, when some other error occured. - */ -int32_t SVC_drm_getContent(int32_t session, int32_t offset, uint8_t* mediaBuf, int32_t mediaBufLen); - -/** - * Get the rights issuer address, this interface is specially for Separate Delivery method. - * - * \param session The handle for this DRM object session. - * \param rightsIssuer The buffer to save rights issuer, 256 bytes are enough. - * - * \return - * -DRM_SUCCESS, when get the rights issuer successfully. - * -DRM_SESSION_NOT_OPENED, when the session is not opened or has been closed. - * -DRM_NOT_SD_METHOD, when it is not a Separate Delivery DRM object. - * -DRM_FAILURE, when some other error occured. - */ -int32_t SVC_drm_getRightsIssuer(int32_t session, uint8_t* rightsIssuer); - -/** - * Get DRM object constraint informations. - * - * \param session The handle for this DRM object session. - * \param rights The structue to save the rights object information. - * - * \return - * -DRM_SUCCESS, when get the rights information successfully. - * -DRM_SESSION_NOT_OPENED, when the session is not opened or has been closed. - * -DRM_NO_RIGHTS, when this DRM object has not rights. - * -DRM_FAILURE, when some other error occured. - */ -int32_t SVC_drm_getRightsInfo(int32_t session, T_DRM_Rights_Info* rights); - -/** - * Close the opened session, after closed, the handle become invalid. - * - * \param session The handle for this DRM object session. - * - * \return - * -DRM_SUCCESS, when close operation success. - * -DRM_SESSION_NOT_OPENED, when the session is not opened or has been closed. - * -DRM_FAILURE, when some other error occured. - */ -int32_t SVC_drm_closeSession(int32_t session); - -/** - * Check and update the given rights according the given permission. - * - * \param contentID The unique id of the rights object. - * \param permission The permission to be updated. - * - * \return - * -DRM_SUCCESS, when update operation success. - * -DRM_NO_RIGHTS, when it has no rights. - * -DRM_RIGHTS_PENDING, when it has the rights, but currently it is pending. - * -DRM_RIGHTS_EXPIRED, when the rights has expired. - * -DRM_FAILURE, when some other error occured. - */ -int32_t SVC_drm_updateRights(uint8_t* contentID, int32_t permission); - -/** - * Scan all the rights object in current DRM engine, and get all their information. - * - * \param ppRightsInfo The pointer to the list structure to save rights info. - * - * \return - * -DRM_SUCCESS, when get information successfully. - * -DRM_FAILURE, when some other error occured. - */ -int32_t SVC_drm_viewAllRights(T_DRM_Rights_Info_Node **ppRightsInfo); - -/** - * Free the allocated memory when call "SVC_drm_viewAllRights". - * - * \param pRightsHeader The header pointer of the list to be free. - * - * \return - * -DRM_SUCCESS, when free operation successfully. - * -DRM_FAILURE, when some other error occured. - */ -int32_t SVC_drm_freeRightsInfoList(T_DRM_Rights_Info_Node *pRightsHeader); - -/** - * Delete a specify rights. - * - * \param roId The unique id of the rights. - * - * \return - * -DRM_SUCCESS, when free operation successfully. - * -DRM_NO_RIGHTS, when there is not this rights object. - * -DRM_FAILURE, when some other error occured. - */ -int32_t SVC_drm_deleteRights(uint8_t* roId); - -#ifdef __cplusplus -} -#endif - -#endif /* __SVC_DRM_NEW_H__ */ diff --git a/media/libdrm/mobile1/include/parser/parser_dcf.h b/media/libdrm/mobile1/include/parser/parser_dcf.h deleted file mode 100644 index c63a195..0000000 --- a/media/libdrm/mobile1/include/parser/parser_dcf.h +++ /dev/null @@ -1,91 +0,0 @@ -/* - * 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. - */ - -#ifndef __PARSER_DCF_H__ -#define __PARSER_DCF_H__ - -#ifdef __cplusplus -extern "C" { -#endif - -#include <drm_common_types.h> - -#define MAX_ENCRYPTION_METHOD_LEN 64 -#define MAX_RIGHTS_ISSUER_LEN 256 -#define MAX_CONTENT_NAME_LEN 64 -#define MAX_CONTENT_DESCRIPTION_LEN 256 -#define MAX_CONTENT_VENDOR_LEN 256 -#define MAX_ICON_URI_LEN 256 -#define MAX_CONTENT_TYPE_LEN 64 -#define MAX_CONTENT_URI_LEN 256 - -#define HEADER_ENCRYPTION_METHOD "Encryption-Method: " -#define HEADER_RIGHTS_ISSUER "Rights-Issuer: " -#define HEADER_CONTENT_NAME "Content-Name: " -#define HEADER_CONTENT_DESCRIPTION "Content-Description: " -#define HEADER_CONTENT_VENDOR "Content-Vendor: " -#define HEADER_ICON_URI "Icon-Uri: " - -#define HEADER_ENCRYPTION_METHOD_LEN 19 -#define HEADER_RIGHTS_ISSUER_LEN 15 -#define HEADER_CONTENT_NAME_LEN 14 -#define HEADER_CONTENT_DESCRIPTION_LEN 21 -#define HEADER_CONTENT_VENDOR_LEN 16 -#define HEADER_ICON_URI_LEN 10 - -#define UINT_VAR_FLAG 0x80 -#define UINT_VAR_DATA 0x7F -#define MAX_UINT_VAR_BYTE 5 -#define DRM_UINT_VAR_ERR -1 - -typedef struct _T_DRM_DCF_Info { - uint8_t Version; - uint8_t ContentTypeLen; /**< Length of the ContentType field */ - uint8_t ContentURILen; /**< Length of the ContentURI field */ - uint8_t unUsed; - uint8_t ContentType[MAX_CONTENT_TYPE_LEN]; /**< The MIME media type of the plaintext data */ - uint8_t ContentURI[MAX_CONTENT_URI_LEN]; /**< The unique identifier of this content object */ - int32_t HeadersLen; /**< Length of the Headers field */ - int32_t EncryptedDataLen; /**< Length of the encrypted data field */ - int32_t DecryptedDataLen; /**< Length of the decrypted data field */ - uint8_t Encryption_Method[MAX_ENCRYPTION_METHOD_LEN]; /**< Encryption method */ - uint8_t Rights_Issuer[MAX_RIGHTS_ISSUER_LEN]; /**< Rights issuer */ - uint8_t Content_Name[MAX_CONTENT_NAME_LEN]; /**< Content name */ - uint8_t ContentDescription[MAX_CONTENT_DESCRIPTION_LEN]; /**< Content description */ - uint8_t ContentVendor[MAX_CONTENT_VENDOR_LEN]; /**< Content vendor */ - uint8_t Icon_URI[MAX_ICON_URI_LEN]; /**< Icon URI */ -} T_DRM_DCF_Info; - -/** - * Parse the DRM content format data - * - * \param buffer (in)Input the DCF format data - * \param bufferLen (in)The input buffer length - * \param pDcfInfo (out)A structure pointer which contain information of DCF headers - * \param ppEncryptedData (out)The location of encrypted data - * - * \return - * -TRUE, when success - * -FALSE, when failed - */ -int32_t drm_dcfParser(uint8_t *buffer, int32_t bufferLen, T_DRM_DCF_Info *pDcfInfo, - uint8_t **ppEncryptedData); - -#ifdef __cplusplus -} -#endif - -#endif /* __PARSER_DCF_H__ */ diff --git a/media/libdrm/mobile1/include/parser/parser_dm.h b/media/libdrm/mobile1/include/parser/parser_dm.h deleted file mode 100644 index ec8b6b2..0000000 --- a/media/libdrm/mobile1/include/parser/parser_dm.h +++ /dev/null @@ -1,101 +0,0 @@ -/* - * 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. - */ - -#ifndef __PARSER_DM_H__ -#define __PARSER_DM_H__ - -#ifdef __cplusplus -extern "C" { -#endif - -#include <drm_common_types.h> - -#define MAX_CONTENT_TYPE_LEN 64 -#define MAX_CONTENT_ID 256 -#define MAX_CONTENT_BOUNDARY_LEN 256 -#define MAX_RIGHTS_ISSUER_LEN 256 - -#define DRM_MIME_TYPE_RIGHTS_XML "application/vnd.oma.drm.rights+xml" -#define DRM_MIME_TYPE_CONTENT "application/vnd.oma.drm.content" - -#define HEADERS_TRANSFER_CODING "Content-Transfer-Encoding:" -#define HEADERS_CONTENT_TYPE "Content-Type:" -#define HEADERS_CONTENT_ID "Content-ID:" - -#define TRANSFER_CODING_TYPE_7BIT "7bit" -#define TRANSFER_CODING_TYPE_8BIT "8bit" -#define TRANSFER_CODING_TYPE_BINARY "binary" -#define TRANSFER_CODING_TYPE_BASE64 "base64" - -#define DRM_UID_TYPE_FORWORD_LOCK "forwardlock" -#define DRM_NEW_LINE_CRLF "\r\n" - -#define HEADERS_TRANSFER_CODING_LEN 26 -#define HEADERS_CONTENT_TYPE_LEN 13 -#define HEADERS_CONTENT_ID_LEN 11 - -#define DRM_MESSAGE_CODING_7BIT 0 /* default */ -#define DRM_MESSAGE_CODING_8BIT 1 -#define DRM_MESSAGE_CODING_BINARY 2 -#define DRM_MESSAGE_CODING_BASE64 3 - -#define DRM_B64_DEC_BLOCK 3 -#define DRM_B64_ENC_BLOCK 4 - -typedef struct _T_DRM_DM_Info { - uint8_t contentType[MAX_CONTENT_TYPE_LEN]; /**< Content type */ - uint8_t contentID[MAX_CONTENT_ID]; /**< Content ID */ - uint8_t boundary[MAX_CONTENT_BOUNDARY_LEN]; /**< DRM message's boundary */ - uint8_t deliveryType; /**< The Delivery type */ - uint8_t transferEncoding; /**< Transfer encoding type */ - int32_t contentOffset; /**< The offset of the media content from the original DRM data */ - int32_t contentLen; /**< The length of the media content */ - int32_t rightsOffset; /**< The offset of the rights object in case of combined delivery */ - int32_t rightsLen; /**< The length of the rights object in case of combined delivery */ - uint8_t rightsIssuer[MAX_RIGHTS_ISSUER_LEN];/**< The rights issuer address in case of separate delivery */ -} T_DRM_DM_Info; - -/** - * Search the string in a limited length. - * - * \param str The original string - * \param strSearch The sub-string to be searched - * \param len The length limited - * - * \return - * -NULL, when there is not the searched string in length - * -The pointer of this sub-string - */ -const uint8_t* drm_strnstr(const uint8_t* str, const uint8_t* strSearch, int32_t len); - -/** - * Parse the DRM message format data. - * - * \param buffer (in)Input the DRM message format data - * \param bufferLen (in)The input buffer length - * \param pDmInfo (out)A structure pointer which contain information of DRM message headers - * - * \return - * -TRUE, when success - * -FALSE, when failed - */ -int32_t drm_parseDM(const uint8_t* buffer, int32_t bufferLen, T_DRM_DM_Info* pDmInfo); - -#ifdef __cplusplus -} -#endif - -#endif /* __PARSER_DM_H__ */ diff --git a/media/libdrm/mobile1/include/parser/parser_rel.h b/media/libdrm/mobile1/include/parser/parser_rel.h deleted file mode 100644 index 8def199..0000000 --- a/media/libdrm/mobile1/include/parser/parser_rel.h +++ /dev/null @@ -1,123 +0,0 @@ -/* - * 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. - */ - -#ifndef __PARSER_REL_H__ -#define __PARSER_REL_H__ - -#ifdef __cplusplus -extern "C" { -#endif - -#include <drm_common_types.h> - -#define WRITE_RO_FLAG(whoIsAble, boolValue, Indicator, RIGHTS) do{\ - whoIsAble = boolValue;\ - Indicator |= RIGHTS;\ -}while(0) - -#define CHECK_VALIDITY(ret) do{\ - if(ret == NULL){\ - if(XML_ERROR_NO_SUCH_NODE != xml_errno)\ - return FALSE;\ - }\ - else\ - {\ - if(XML_ERROR_OK != xml_errno)\ - return FALSE;\ - }\ -}while(0) - -#define YMD_HMS_2_INT(year, mon, day, date, hour, min, sec, time) do{\ - date = year * 10000 + mon * 100 + day;\ - time = hour * 10000 + min * 100 + sec;\ -}while(0) - -#define DRM_UID_LEN 256 -#define DRM_KEY_LEN 16 - -#define XML_DOM_PARSER - -typedef struct _T_DRM_DATETIME { - int32_t date; /**< year * 10000 + mon *100 + day */ - int32_t time; /**< hour * 10000 + min *100 + sec */ -} T_DRM_DATETIME; - -typedef struct _T_DRM_Rights_Constraint { - uint8_t Indicator; /**< Indicate which is constrainted, the first one indicate 0001, second one indicate 0010 */ - uint8_t unUsed[3]; - int32_t Count; /**< The times that can be used */ - T_DRM_DATETIME StartTime; /**< The starting time */ - T_DRM_DATETIME EndTime; /**< The ending time */ - T_DRM_DATETIME Interval; /**< The interval time */ -} T_DRM_Rights_Constraint; - -typedef struct _T_DRM_Rights { - uint8_t Version[8]; /**< Version number */ - uint8_t uid[256]; /**< record the rights object name */ - uint8_t KeyValue[16]; /**< Decode base64 */ - int32_t bIsPlayable; /**< Is playable */ - int32_t bIsDisplayable; /**< Is displayable */ - int32_t bIsExecuteable; /**< Is executeable */ - int32_t bIsPrintable; /**< Is printable */ - T_DRM_Rights_Constraint PlayConstraint; /**< Play constraint */ - T_DRM_Rights_Constraint DisplayConstraint; /**< Display constraint */ - T_DRM_Rights_Constraint ExecuteConstraint; /**< Execute constraint */ - T_DRM_Rights_Constraint PrintConstraint; /**< Print constraint */ -} T_DRM_Rights; - -/** - * Input year and month, return how many days that month have - * \param year (in)Input the year - * \param month (in)Input the month - * \return - * -A positive integer, which is how many days that month have - * -When wrong input, return -1 - */ -int32_t drm_monthDays(int32_t year, int32_t month); - -/** - * Check whether the date and time is valid. - * \param year year of the date - * \param month month of the date - * \param day day of the date - * \param hour hour of the time - * \param min minute of the time - * \param sec second of the time - * \return - * -when it is a valid time, return 0 - * -when it is a invalid time, return -1 - */ -int32_t drm_checkDate(int32_t year, int32_t month, int32_t day, int32_t hour, int32_t min, int32_t sec); - -/** - * Parse the rights object include xml format and wbxml format data - * - * \param buffer (in)Input the DRM rights object data - * \param bufferLen (in)The buffer length - * \param format (in)Which format, xml or wbxml - * \param pRights (out)A structure pointer which save the rights information - * - * \return - * -TRUE, when success - * -FALSE, when failed - */ -int32_t drm_relParser(uint8_t* buffer, int32_t bufferLen, int32_t Format, T_DRM_Rights* pRights); - -#ifdef __cplusplus -} -#endif - -#endif /* __PARSER_REL_H__ */ diff --git a/media/libdrm/mobile1/include/xml/wbxml_tinyparser.h b/media/libdrm/mobile1/include/xml/wbxml_tinyparser.h deleted file mode 100644 index 1c40467..0000000 --- a/media/libdrm/mobile1/include/xml/wbxml_tinyparser.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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. - */ - -#ifndef __WBXML_TINYPARSER_H__ -#define __WBXML_TINYPARSER_H__ - -#ifdef __cplusplus -extern "C" { -#endif - -#include <drm_common_types.h> - -#define REL_TAG_RIGHTS 0x05 -#define REL_TAG_CONTEXT 0x06 -#define REL_TAG_VERSION 0x07 -#define REL_TAG_UID 0x08 -#define REL_TAG_AGREEMENT 0x09 -#define REL_TAG_ASSET 0x0A -#define REL_TAG_KEYINFO 0x0B -#define REL_TAG_KEYVALUE 0x0C -#define REL_TAG_PERMISSION 0x0D -#define REL_TAG_PLAY 0x0E -#define REL_TAG_DISPLAY 0x0F -#define REL_TAG_EXECUTE 0x10 -#define REL_TAG_PRINT 0x11 -#define REL_TAG_CONSTRAINT 0x12 -#define REL_TAG_COUNT 0x13 -#define REL_TAG_DATETIME 0x14 -#define REL_TAG_START 0x15 -#define REL_TAG_END 0x16 -#define REL_TAG_INTERVAL 0x17 - -#define REL_CHECK_WBXML_HEADER(x) ((x != NULL) && (x[0] == 0x03) && (x[1] == 0x0E) && (x[2] == 0x6A)) - -#ifdef __cplusplus -} -#endif - -#endif /* __WBXML_TINYPARSER_H__ */ diff --git a/media/libdrm/mobile1/include/xml/xml_tinyParser.h b/media/libdrm/mobile1/include/xml/xml_tinyParser.h deleted file mode 100644 index 4ad65b8..0000000 --- a/media/libdrm/mobile1/include/xml/xml_tinyParser.h +++ /dev/null @@ -1,171 +0,0 @@ -/* - * 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. - */ - -#ifndef __XML_TINYPARSER_H__ -#define __XML_TINYPARSER_H__ - -#ifdef __cplusplus -extern "C" { -#endif - -#include <drm_common_types.h> - -#define XML_DOM_PARSER -#define WBXML_DOM_PARSER -#define XML_DOM_CHECK_ENDTAG -#define XML_ENABLE_ERRNO -#define WBXML_OLD_VERSION /* for drm only */ - -#ifdef DEBUG_MODE -void XML_PrintMallocInfo(); -#endif /* DEBUG_MODE */ - -#define XML_TRUE 1 -#define XML_FALSE 0 -#define XML_EOF 0 -#define XML_TAG_START 0 -#define XML_TAG_END 1 -#define XML_TAG_SELF 2 - -#define XML_MAX_PROPERTY_LEN 256 -#define XML_MAX_ATTR_NAME_LEN 256 -#define XML_MAX_ATTR_VALUE_LEN 256 -#define XML_MAX_VALUE_LEN 256 - -#define XML_ERROR_OK 0 -#define XML_ERROR_BUFFER_NULL -1 -#define XML_ERROR_ATTR_NAME -2 -#define XML_ERROR_ATTR_MISSED_EQUAL -3 -#define XML_ERROR_PROPERTY_NAME -4 -#define XML_ERROR_ATTR_VALUE -5 -#define XML_ERROR_ENDTAG -6 -#define XML_ERROR_NO_SUCH_NODE -7 -#define XML_ERROR_PROPERTY_END -8 -#define XML_ERROR_VALUE -9 -#define XML_ERROR_NO_START_TAG -14 -#define XML_ERROR_NOVALUE -15 - -#define WBXML_ERROR_MISSED_CONTENT -10 -#define WBXML_ERROR_MBUINT32 -11 -#define WBXML_ERROR_MISSED_STARTTAG -12 -#define WBXML_ERROR_MISSED_ENDTAG -13 - -#ifdef XML_ENABLE_ERRNO -extern int32_t xml_errno; -#define XML_ERROR(x) do { xml_errno = x; } while (0) -#else /* XML_ENABLE_ERRNO */ -#define XML_ERROR -#endif /* XML_ENABLE_ERRNO */ - -#ifdef XML_DOM_PARSER -uint8_t *XML_DOM_getNode(uint8_t *buffer, const uint8_t *const node); -uint8_t *XML_DOM_getNodeValue(uint8_t *buffer, uint8_t *node, - uint8_t **value, int32_t *valueLen); - -uint8_t *XML_DOM_getValue(uint8_t *buffer, uint8_t **pValue, int32_t *valueLen); -uint8_t *XML_DOM_getAttr(uint8_t *buffer, uint8_t **pName, int32_t *nameLen, - uint8_t **pValue, int32_t *valueLen); - -uint8_t *XML_DOM_getNextNode(uint8_t *buffer, uint8_t **pNodeName, - int32_t *nodenameLen); - -uint8_t *XML_DOM_getTag(uint8_t *buffer, int32_t *tagLen, int32_t *tagType); -#endif /* XML_DOM_PARSER */ - -#ifdef WBXML_DOM_PARSER - -#define WBXML_WITH_ATTR 0x80 -#define WBXML_WITH_CONTENT 0x40 -#define WBXML_ATTR_END 0x01 -#define WBXML_CONTENT_END 0x01 - -#define WBXML_SWITCH_PAGE 0x00 -#define WBXML_STR_I 0x03 -#define WBXML_END 0x00 -#define WBXML_OPAUE 0xC3 -#define WBXML_STR_T 0x83 -#define WBXML_OPAQUE 0xC3 - -#define WBXML_GET_TAG(x) ((x) & 0x3F) /* get 6-digits */ -#define WBXML_HAS_ATTR(x) ((x) & WBXML_WITH_ATTR) -#define WBXML_HAS_CONTENT(x) ((x) & WBXML_WITH_CONTENT) - -typedef struct _WBXML { - uint8_t version; - uint8_t unUsed[3]; - uint32_t publicid; - uint32_t charset; - int32_t strTableLen; - uint8_t *strTable; - uint8_t *Content; - uint8_t *End; - uint8_t *curPtr; - int32_t depth; -} WBXML; - -typedef int32_t XML_BOOL; - -#ifdef WBXML_OLD_VERSION -uint8_t *WBXML_DOM_getNode(uint8_t *buffer, int32_t bufferLen, - uint8_t *node); -uint8_t *WBXML_DOM_getNodeValue(uint8_t *buffer, int32_t bufferLen, - uint8_t *node, - uint8_t **value, - int32_t *valueLen); -#endif /* WBXML_OLD_VERSION */ - -XML_BOOL WBXML_DOM_Init(WBXML * pWbxml, uint8_t *buffer, - int32_t bufferLen); -XML_BOOL WBXML_DOM_Eof(WBXML * pWbxml); -uint8_t WBXML_DOM_GetTag(WBXML * pWbxml); -uint8_t WBXML_DOM_GetChar(WBXML * pWbxml); -uint8_t WBXML_DOM_GetUIntVar(WBXML * pWbxml); -void WBXML_DOM_Rewind(WBXML * pWbxml); -void WBXML_DOM_Seek(WBXML * pWbxml, int32_t offset); -int32_t WBXML_GetUintVar(const uint8_t *const buffer, int32_t *len); - -#endif /* WBXML_DOM_PARSER */ - -#ifdef XML_TREE_STRUCTURE - -typedef struct _XML_TREE_ATTR XML_TREE_ATTR; -struct _XML_TREE_ATTR { - uint8_t name[XML_MAX_ATTR_VALUE_LEN]; - uint8_t value[XML_MAX_ATTR_VALUE_LEN]; - XML_TREE_ATTR *next; -}; - -typedef struct _XML_TREE XML_TREE; -struct _XML_TREE { - uint8_t tag[XML_MAX_PROPERTY_LEN]; - uint8_t value[XML_MAX_VALUE_LEN]; - XML_TREE_ATTR *attr; - XML_TREE_ATTR *last_attr; - XML_TREE *brother; - XML_TREE *last_brother; - XML_TREE *child; -}; - -XML_TREE *XML_makeTree(uint8_t **buf); -void XML_freeTree(XML_TREE * pTree); - -#endif /* XML_TREE_STRUCTURE */ - -#ifdef __cplusplus -} -#endif - -#endif /* __XML_TINYPARSER_H__ */ diff --git a/media/libdrm/mobile1/src/jni/drm1_jni.c b/media/libdrm/mobile1/src/jni/drm1_jni.c deleted file mode 100644 index 11353a7..0000000 --- a/media/libdrm/mobile1/src/jni/drm1_jni.c +++ /dev/null @@ -1,1166 +0,0 @@ -/* - * 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. - */ - -/** - * @file drm1_jni.c - * - * This file implement the Java Native Interface - * for supporting OMA DRM 1.0 - */ - -#include <jni/drm1_jni.h> -#include <objmng/svc_drm.h> -#include "log.h" -#include "JNIHelp.h" - - -#define MS_PER_SECOND 1000 /* Milliseconds per second */ -#define MS_PER_MINUTE 60 * MS_PER_SECOND /* Milliseconds per minute */ -#define MS_PER_HOUR 60 * MS_PER_MINUTE /* Milliseconds per hour */ -#define MS_PER_DAY 24 * MS_PER_HOUR /* Milliseconds per day */ - -#define SECONDS_PER_MINUTE 60 /* Seconds per minute*/ -#define SECONDS_PER_HOUR 60 * SECONDS_PER_MINUTE /* Seconds per hour */ -#define SECONDS_PER_DAY 24 * SECONDS_PER_HOUR /* Seconds per day */ - -#define DAY_PER_MONTH 30 /* Days per month */ -#define DAY_PER_YEAR 365 /* Days per year */ - -/** Nonzero if 'y' is a leap year, else zero. */ -#define leap(y) (((y) % 4 == 0 && (y) % 100 != 0) || (y) % 400 == 0) - -/** Number of leap years from 1970 to 'y' (not including 'y' itself). */ -#define nleap(y) (((y) - 1969) / 4 - ((y) - 1901) / 100 + ((y) - 1601) / 400) - -/** Accumulated number of days from 01-Jan up to start of current month. */ -static const int32_t ydays[] = { - 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 -}; - -#define int64_const(s) (s) -#define int64_add(dst, s1, s2) ((void)((dst) = (s1) + (s2))) -#define int64_mul(dst, s1, s2) ((void)((dst) = (int64_t)(s1) * (int64_t)(s2))) - -/** - * DRM data structure - */ -typedef struct _DrmData { - /** - * The id of the DRM content. - */ - int32_t id; - - /** - * The pointer of JNI interface. - */ - JNIEnv* env; - - /** - * The pointer of DRM raw content InputStream object. - */ - jobject* pInData; - - /** - * The len of the InputStream object. - */ - int32_t len; - - /** - * The next DRM data. - */ - struct _DrmData *next; -} DrmData; - -/** The table to hold all the DRM data. */ -static DrmData *drmTable = NULL; - -/** - * Allocate a new item of DrmData. - * - * \return a pointer to a DrmData item if allocate successfully, - * otherwise return NULL - */ -static DrmData * newItem(void) -{ - DrmData *d = (DrmData *)malloc(sizeof(DrmData)); - - if (d != NULL) { - d->id = -1; - d->next = NULL; - } - - return d; -} - -/** - * Free the memory of the specified DrmData item <code>d</code>. - * - * \param d - a pointer to DrmData - */ -static void freeItem(DrmData *d) -{ - assert(d != NULL); - - free(d); -} - -/** - * Insert a DrmData item with given <code>name</code> into the head of - * the DrmData list. - * - * @param d - the pointer of the JNI interface - * @param pInData - the pointer of the DRM content InputStream object. - * - * @return <code>JNI_DRM_SUCCESS</code> if insert successfully, otherwise - * return <code>JNI_DRM_FAILURE</code> - */ -static int32_t addItem(DrmData* d) -{ - if (NULL == d) - return JNI_DRM_FAILURE; - - if (NULL == drmTable) { - drmTable = d; - return JNI_DRM_SUCCESS; - } - - d->next = drmTable; - drmTable = d; - - return JNI_DRM_SUCCESS; -} - -/** - * Get the item from the DrmData list by the specified <code> - * id</code>. - * - * @param p - the pointer of the DRM content InputStream object. - * - * @return a pointer to the DrmData item if find it successfuly, - * otherwise return NULL - */ -static DrmData * getItem(int32_t id) -{ - DrmData *d; - - if (NULL == drmTable) - return NULL; - - for (d = drmTable; d != NULL; d = d->next) { - if (id == d->id) - return d; - } - - return NULL; -} - -/** - * Remove the specified DrmData item <code>d</code>. - * - * @param p - the pointer of the DRM content InputStream object. - * - * @return <code>JNI_DRM_SUCCESS</code> if remove successfuly, - * otherwise return <code>JNI_DRM_FAILURE</code> - */ -static int32_t removeItem(int32_t id) -{ - DrmData *curItem, *preItem, *dstItem; - - if (NULL == drmTable) - return JNI_DRM_FAILURE; - - preItem = NULL; - for (curItem = drmTable; curItem != NULL; curItem = curItem->next) { - if (id == curItem->id) { - if (curItem == drmTable) - drmTable = curItem->next; - else - preItem->next = curItem->next; - - freeItem(curItem); - - return JNI_DRM_SUCCESS; - } - - preItem = curItem; - } - - return JNI_DRM_FAILURE; -} - - -static int32_t getInputStreamDataLength(int32_t handle) -{ - JNIEnv* env; - jobject* pInputStream; - int32_t len; - DrmData* p; - jclass cls; - jmethodID mid; - - p = (DrmData *)handle; - - if (NULL == p) - return 0; - - env = p->env; - pInputStream = p->pInData; - len = p->len; - - if (NULL == env || p->len <= 0 || NULL == pInputStream) - return 0; - - /* check the original InputStream is available or not */ - cls = (*env)->GetObjectClass(env, *pInputStream); - mid = (*env)->GetMethodID(env, cls, "available", "()I"); - (*env)->DeleteLocalRef(env, cls); - - if (NULL == mid) - return 0; - - if (0 > (*env)->CallIntMethod(env, *pInputStream, mid)) - return 0; - - return len; -} - -static int32_t readInputStreamData(int32_t handle, uint8_t* buf, int32_t bufLen) -{ - JNIEnv* env; - jobject* pInputStream; - int32_t len; - DrmData* p; - jclass cls; - jmethodID mid; - jbyteArray tmp; - int tmpLen; - jbyte* pNativeBuf; - - p = (DrmData *)handle; - - if (NULL == p || NULL == buf || bufLen <- 0) - return 0; - - env = p->env; - pInputStream = p->pInData; - len = p->len; - - if (NULL == env || p->len <= 0 || NULL == pInputStream) - return 0; - - cls = (*env)->GetObjectClass(env, *pInputStream); - mid = (*env)->GetMethodID(env, cls, "read", "([BII)I"); - tmp = (*env)->NewByteArray(env, bufLen); - bufLen = (*env)->CallIntMethod(env, *pInputStream, mid, tmp, 0, bufLen); - - (*env)->DeleteLocalRef(env, cls); - - if (-1 == bufLen) - return -1; - - pNativeBuf = (*env)->GetByteArrayElements(env, tmp, NULL); - memcpy(buf, pNativeBuf, bufLen); - (*env)->ReleaseByteArrayElements(env, tmp, pNativeBuf, 0); - (*env)->DeleteLocalRef(env, tmp); - - return bufLen; -} - -static const T_DRM_Rights_Info_Node *searchRightsObject(const jbyte* roId, const T_DRM_Rights_Info_Node* pRightsList) -{ - const T_DRM_Rights_Info_Node *pTmp; - - if (NULL == roId || NULL == pRightsList) - return NULL; - - pTmp = pRightsList; - - while (NULL != pTmp) { - if(0 == strcmp((char *)roId, (char *)pTmp->roInfo.roId)) - break; - pTmp = pTmp->next; - } - - return pTmp; -} - -/** - * Returns the difference in seconds between the given GMT time - * and 1970-01-01 00:00:00 GMT. - * - * \param year the year (since 1970) - * \param month the month (1 - 12) - * \param day the day (1 - 31) - * \param hour the hour (0 - 23) - * \param minute the minute (0 - 59) - * \param second the second (0 - 59) - * - * \return the difference in seconds between the given GMT time - * and 1970-01-01 00:00:00 GMT. - */ -static int64_t mkgmtime( - uint32_t year, uint32_t month, uint32_t day, - uint32_t hour, uint32_t minute, uint32_t second) -{ - int64_t result; - - /* - * FIXME: It does not check whether the specified days - * is valid based on the specified months. - */ - assert(year >= 1970 - && month > 0 && month <= 12 - && day > 0 && day <= 31 - && hour < 24 && minute < 60 - && second < 60); - - /* Set 'day' to the number of days into the year. */ - day += ydays[month - 1] + (month > 2 && leap (year)) - 1; - - /* Now calculate 'day' to the number of days since Jan 1, 1970. */ - day = day + 365 * (year - 1970) + nleap(year); - - int64_mul(result, int64_const(day), int64_const(SECONDS_PER_DAY)); - int64_add(result, result, int64_const( - SECONDS_PER_HOUR * hour + SECONDS_PER_MINUTE * minute + second)); - - return result; -} - -/** - * Compute the milliseconds by the specified <code>date</code> - * and <code>time</code>. - * - * @param date - the specified date, - * <code>date = year * 10000 + month * 100 + day</code> - * @param time - the specified time, - * <code>time = hour * 10000 + minute * 100 + second</code> - * - * @return the related milliseconds - */ -static int64_t computeTime(int32_t date, int32_t time) -{ - int32_t year, month, day, hour, minute, second; - - year = date / 10000; - month = (date / 100) % 100; - day = date % 100; - hour = time / 10000; - minute = (time / 100) % 100; - second = time % 100; - - /* Adjust the invalid parameters. */ - if (year < 1970) year = 1970; - if (month < 1) month = 1; - if (month > 12) month = 12; - if (day < 1) day = 1; - if (day > 31) day = 31; - if (hour < 0) hour = 0; - if (hour > 23) hour = 23; - if (minute < 0) minute = 0; - if (minute > 59) minute = 59; - if (second < 0) second = 0; - if (second > 59) second = 59; - - return mkgmtime(year, month, day, hour, minute, second) * 1000; -} - -/** - * Compute the milliseconds by the specified <code>date</code> - * and <code>time</code>. - * Note that here we always treat 1 year as 365 days and 1 month as 30 days - * that is not precise. But it should not be a problem since OMA DRM 2.0 - * already restricts the interval representation to be day-based, - * i.e. there will not be an interval with year or month any more in the - * future. - * - * @param date - the specified date, - * <code>date = year * 10000 + month * 100 + day</code> - * @param time - the specified time, - * <code>time = hour * 10000 + minute * 100 + second</code> - * - * @return the related milliseconds - */ -static int64_t computeInterval(int32_t date, int32_t time) -{ - int32_t year, month, day, hour, minute, second; - int64_t milliseconds; - - year = date / 10000; - month = (date / 100) % 100; - day = date % 100; - hour = time / 10000; - minute = (time / 100) % 100; - second = time % 100; - - /* milliseconds = ((((year * 365 + month * 30 + day) * 24 - * + hour) * 60 + minute) * 60 + second) * 1000; - */ - int64_mul(milliseconds, - int64_const(year * DAY_PER_YEAR + month * DAY_PER_MONTH + day), - int64_const(MS_PER_DAY)); - int64_add(milliseconds, milliseconds, - int64_const(hour * MS_PER_HOUR + minute * MS_PER_MINUTE + - second * MS_PER_SECOND)); - - return milliseconds; -} - -static jint getObjectIntField(JNIEnv * env, jobject obj, const char *name, jint * value) -{ - jclass clazz; - jfieldID field; - - clazz = (*env)->GetObjectClass(env, obj); - if (NULL == clazz) - return JNI_DRM_FAILURE; - - field = (*env)->GetFieldID(env, clazz, name, "I"); - (*env)->DeleteLocalRef(env, clazz); - - if (NULL == field) - return JNI_DRM_FAILURE; - - *value = (*env)->GetIntField(env, obj, field); - - return JNI_DRM_SUCCESS; -} - -static jint setObjectIntField(JNIEnv * env, jobject obj, const char *name, jint value) -{ - jclass clazz; - jfieldID field; - - clazz = (*env)->GetObjectClass(env, obj); - if (NULL == clazz) - return JNI_DRM_FAILURE; - - field = (*env)->GetFieldID(env, clazz, name, "I"); - (*env)->DeleteLocalRef(env, clazz); - - if (NULL == field) - return JNI_DRM_FAILURE; - - (*env)->SetIntField(env, obj, field, value); - - return JNI_DRM_SUCCESS; -} - -static jint setObjectLongField(JNIEnv * env, jobject obj, const char *name, jlong value) -{ - jclass clazz; - jfieldID field; - - clazz = (*env)->GetObjectClass(env, obj); - if (NULL == clazz) - return JNI_DRM_FAILURE; - - field = (*env)->GetFieldID(env, clazz, name, "J"); - (*env)->DeleteLocalRef(env, clazz); - - if (NULL == field) - return JNI_DRM_FAILURE; - - (*env)->SetLongField(env, obj, field, value); - - return JNI_DRM_SUCCESS; -} - -static jint setConstraintFields(JNIEnv * env, jobject constraint, T_DRM_Constraint_Info * pConstraint) -{ - /* if no this permission */ - if (pConstraint->indicator == (uint8_t)DRM_NO_RIGHTS) { - if (JNI_DRM_FAILURE == setObjectIntField(env, constraint, "count", 0)) - return JNI_DRM_FAILURE; - - return JNI_DRM_SUCCESS; - } - - /* set count field */ - if (pConstraint->indicator & DRM_COUNT_CONSTRAINT) { - if (JNI_DRM_FAILURE == setObjectIntField(env, constraint, "count", pConstraint->count)) - return JNI_DRM_FAILURE; - } - - /* set start time field */ - if (pConstraint->indicator & DRM_START_TIME_CONSTRAINT) { - int64_t startTime; - - startTime = computeTime(pConstraint->startDate, pConstraint->startTime); - - if (JNI_DRM_FAILURE == setObjectLongField(env, constraint, "startDate", startTime)) - return JNI_DRM_FAILURE; - } - - /* set end time field */ - if (pConstraint->indicator & DRM_END_TIME_CONSTRAINT) { - int64_t endTime; - - endTime = computeTime(pConstraint->endDate, pConstraint->endTime); - - if (JNI_DRM_FAILURE == setObjectLongField(env, constraint, "endDate", endTime)) - return JNI_DRM_FAILURE; - } - - /* set interval field */ - if (pConstraint->indicator & DRM_INTERVAL_CONSTRAINT) { - int64_t interval; - - interval = computeInterval(pConstraint->intervalDate, pConstraint->intervalTime); - - if (JNI_DRM_FAILURE == setObjectLongField(env, constraint, "interval", interval)) - return JNI_DRM_FAILURE; - } - - return JNI_DRM_SUCCESS; -} - -static jint setRightsFields(JNIEnv * env, jobject rights, T_DRM_Rights_Info* pRoInfo) -{ - jclass clazz; - jfieldID field; - jstring str; - jint index; - - clazz = (*env)->GetObjectClass(env, rights); - if (NULL == clazz) - return JNI_DRM_FAILURE; - - /* set roId field */ - field = (*env)->GetFieldID(env, clazz, "roId", "Ljava/lang/String;"); - (*env)->DeleteLocalRef(env, clazz); - - if (NULL == field) - return JNI_DRM_FAILURE; - - str = (*env)->NewStringUTF(env, (char *)pRoInfo->roId); - if (NULL == str) - return JNI_DRM_FAILURE; - - (*env)->SetObjectField(env, rights, field, str); - (*env)->DeleteLocalRef(env, str); - - return JNI_DRM_SUCCESS; -} - -/* native interface */ -JNIEXPORT jint JNICALL -Java_android_drm_mobile1_DrmRawContent_nativeConstructDrmContent - (JNIEnv * env, jobject rawContent, jobject data, jint len, jint mimeType) -{ - int32_t id; - T_DRM_Input_Data inData; - DrmData* drmInData; - - switch (mimeType) { - case JNI_DRM_MIMETYPE_MESSAGE: - mimeType = TYPE_DRM_MESSAGE; - break; - case JNI_DRM_MIMETYPE_CONTENT: - mimeType = TYPE_DRM_CONTENT; - break; - default: - return JNI_DRM_FAILURE; - } - - drmInData = newItem(); - if (NULL == drmInData) - return JNI_DRM_FAILURE; - - drmInData->env = env; - drmInData->pInData = &data; - drmInData->len = len; - - if (JNI_DRM_FAILURE == addItem(drmInData)) - return JNI_DRM_FAILURE; - - inData.inputHandle = (int32_t)drmInData; - inData.mimeType = mimeType; - inData.getInputDataLength = getInputStreamDataLength; - inData.readInputData = readInputStreamData; - - id = SVC_drm_openSession(inData); - if (id < 0) - return JNI_DRM_FAILURE; - - drmInData->id = id; - - return id; -} - -/* native interface */ -JNIEXPORT jstring JNICALL -Java_android_drm_mobile1_DrmRawContent_nativeGetRightsAddress - (JNIEnv * env, jobject rawContent) -{ - jint id; - uint8_t rightsIssuer[256] = {0}; - jstring str = NULL; - - if (JNI_DRM_FAILURE == getObjectIntField(env, rawContent, "id", &id)) - return NULL; - - if (DRM_SUCCESS == SVC_drm_getRightsIssuer(id, rightsIssuer)) - str = (*env)->NewStringUTF(env, (char *)rightsIssuer); - - return str; -} - -/* native interface */ -JNIEXPORT jint JNICALL -Java_android_drm_mobile1_DrmRawContent_nativeGetDeliveryMethod - (JNIEnv * env, jobject rawContent) -{ - jint id; - int32_t res; - - if (JNI_DRM_FAILURE == getObjectIntField(env, rawContent, "id", &id)) - return JNI_DRM_FAILURE; - - res = SVC_drm_getDeliveryMethod(id); - - switch (res) { - case FORWARD_LOCK: - return JNI_DRM_FORWARD_LOCK; - case COMBINED_DELIVERY: - return JNI_DRM_COMBINED_DELIVERY; - case SEPARATE_DELIVERY: - return JNI_DRM_SEPARATE_DELIVERY; - case SEPARATE_DELIVERY_FL: - return JNI_DRM_SEPARATE_DELIVERY_DM; - default: - return JNI_DRM_FAILURE; - } -} - -/* native interface */ -JNIEXPORT jint JNICALL -Java_android_drm_mobile1_DrmRawContent_nativeReadContent - (JNIEnv * env, jobject rawContent, jbyteArray buf, jint bufOff, jint len, jint mediaOff) -{ - jint id; - jbyte *nativeBuf; - jclass cls; - jmethodID mid; - DrmData* p; - jobject inputStream; - jfieldID field; - - if (NULL == buf) { - jniThrowNullPointerException(env, "b == null"); - return JNI_DRM_FAILURE; - } - - if (len < 0 || bufOff < 0 || len + bufOff > (*env)->GetArrayLength(env, buf)) { - jniThrowException(env, "java/lang/IndexOutOfBoundsException", NULL); - return JNI_DRM_FAILURE; - } - - if (mediaOff < 0 || len == 0) - return JNI_DRM_FAILURE; - - if (JNI_DRM_FAILURE == getObjectIntField(env, rawContent, "id", &id)) - return JNI_DRM_FAILURE; - - p = getItem(id); - if (NULL == p) - return JNI_DRM_FAILURE; - - cls = (*env)->GetObjectClass(env, rawContent); - if (NULL == cls) - return JNI_DRM_FAILURE; - - field = (*env)->GetFieldID(env, cls, "inData", "Ljava/io/BufferedInputStream;"); - (*env)->DeleteLocalRef(env, cls); - - if (NULL == field) - return JNI_DRM_FAILURE; - - inputStream = (*env)->GetObjectField(env, rawContent, field); - - p->env = env; - p->pInData = &inputStream; - - nativeBuf = (*env)->GetByteArrayElements(env, buf, NULL); - - len = SVC_drm_getContent(id, mediaOff, (uint8_t *)nativeBuf + bufOff, len); - - (*env)->ReleaseByteArrayElements(env, buf, nativeBuf, 0); - - if (DRM_MEDIA_EOF == len) - return JNI_DRM_EOF; - if (len <= 0) - return JNI_DRM_FAILURE; - - return len; -} - -/* native interface */ -JNIEXPORT jstring JNICALL -Java_android_drm_mobile1_DrmRawContent_nativeGetContentType - (JNIEnv * env, jobject rawContent) -{ - jint id; - uint8_t contentType[64] = {0}; - jstring str = NULL; - - if (JNI_DRM_FAILURE == getObjectIntField(env, rawContent, "id", &id)) - return NULL; - - if (DRM_SUCCESS == SVC_drm_getContentType(id, contentType)) - str = (*env)->NewStringUTF(env, (char *)contentType); - - return str; -} - -/* native interface */ -JNIEXPORT jint JNICALL -Java_android_drm_mobile1_DrmRawContent_nativeGetContentLength - (JNIEnv * env, jobject rawContent) -{ - jint id; - int32_t len; - - if (JNI_DRM_FAILURE == getObjectIntField(env, rawContent, "id", &id)) - return JNI_DRM_FAILURE; - - len = SVC_drm_getContentLength(id); - - if (DRM_UNKNOWN_DATA_LEN == len) - return JNI_DRM_UNKNOWN_DATA_LEN; - - if (0 > len) - return JNI_DRM_FAILURE; - - return len; -} - -/* native interface */ -JNIEXPORT void JNICALL -Java_android_drm_mobile1_DrmRawContent_finalize - (JNIEnv * env, jobject rawContent) -{ - jint id; - - if (JNI_DRM_FAILURE == getObjectIntField(env, rawContent, "id", &id)) - return; - - removeItem(id); - - SVC_drm_closeSession(id); -} - -/* native interface */ -JNIEXPORT jint JNICALL -Java_android_drm_mobile1_DrmRights_nativeGetConstraintInfo - (JNIEnv * env, jobject rights, jint permission, jobject constraint) -{ - jclass clazz; - jfieldID field; - jstring str; - uint8_t *nativeStr; - T_DRM_Rights_Info_Node *pRightsList; - T_DRM_Rights_Info_Node *pCurNode; - T_DRM_Constraint_Info *pConstraint; - - clazz = (*env)->GetObjectClass(env, rights); - if (NULL == clazz) - return JNI_DRM_FAILURE; - - field = (*env)->GetFieldID(env, clazz, "roId", "Ljava/lang/String;"); - (*env)->DeleteLocalRef(env, clazz); - - if (NULL == field) - return JNI_DRM_FAILURE; - - str = (*env)->GetObjectField(env, rights, field); - - nativeStr = (uint8_t *)(*env)->GetStringUTFChars(env, str, NULL); - if (NULL == nativeStr) - return JNI_DRM_FAILURE; - - /* this means forward-lock rights */ - if (0 == strcmp((char *)nativeStr, "ForwardLock")) { - (*env)->ReleaseStringUTFChars(env, str, (char *)nativeStr); - return JNI_DRM_SUCCESS; - } - - if (DRM_FAILURE == SVC_drm_viewAllRights(&pRightsList)) { - (*env)->ReleaseStringUTFChars(env, str, (char *)nativeStr); - return JNI_DRM_FAILURE; - } - - pCurNode = searchRightsObject((jbyte *)nativeStr, pRightsList); - if (NULL == pCurNode) { - (*env)->ReleaseStringUTFChars(env, str, (char *)nativeStr); - SVC_drm_freeRightsInfoList(pRightsList); - return JNI_DRM_FAILURE; - } - (*env)->ReleaseStringUTFChars(env, str, (char *)nativeStr); - - switch (permission) { - case JNI_DRM_PERMISSION_PLAY: - pConstraint = &(pCurNode->roInfo.playRights); - break; - case JNI_DRM_PERMISSION_DISPLAY: - pConstraint = &(pCurNode->roInfo.displayRights); - break; - case JNI_DRM_PERMISSION_EXECUTE: - pConstraint = &(pCurNode->roInfo.executeRights); - break; - case JNI_DRM_PERMISSION_PRINT: - pConstraint = &(pCurNode->roInfo.printRights); - break; - default: - SVC_drm_freeRightsInfoList(pRightsList); - return JNI_DRM_FAILURE; - } - - /* set constraint field */ - if (JNI_DRM_FAILURE == setConstraintFields(env, constraint, pConstraint)) { - SVC_drm_freeRightsInfoList(pRightsList); - return JNI_DRM_FAILURE; - } - - SVC_drm_freeRightsInfoList(pRightsList); - - return JNI_DRM_SUCCESS; -} - -/* native interface */ -JNIEXPORT jint JNICALL -Java_android_drm_mobile1_DrmRights_nativeConsumeRights - (JNIEnv * env, jobject rights, jint permission) -{ - jclass clazz; - jfieldID field; - jstring str; - uint8_t *nativeStr; - int32_t id; - - switch (permission) { - case JNI_DRM_PERMISSION_PLAY: - permission = DRM_PERMISSION_PLAY; - break; - case JNI_DRM_PERMISSION_DISPLAY: - permission = DRM_PERMISSION_DISPLAY; - break; - case JNI_DRM_PERMISSION_EXECUTE: - permission = DRM_PERMISSION_EXECUTE; - break; - case JNI_DRM_PERMISSION_PRINT: - permission = DRM_PERMISSION_PRINT; - break; - default: - return JNI_DRM_FAILURE; - } - - clazz = (*env)->GetObjectClass(env, rights); - if (NULL == clazz) - return JNI_DRM_FAILURE; - - field = (*env)->GetFieldID(env, clazz, "roId", "Ljava/lang/String;"); - (*env)->DeleteLocalRef(env, clazz); - - if (NULL == field) - return JNI_DRM_FAILURE; - - str = (*env)->GetObjectField(env, rights, field); - - nativeStr = (uint8_t *)(*env)->GetStringUTFChars(env, str, NULL); - if (NULL == nativeStr) - return JNI_DRM_FAILURE; - - if (0 == strcmp("ForwardLock", (char *)nativeStr)) { - (*env)->ReleaseStringUTFChars(env, str, (char *)nativeStr); - return JNI_DRM_SUCCESS; - } - - if (DRM_SUCCESS != SVC_drm_updateRights(nativeStr, permission)) { - (*env)->ReleaseStringUTFChars(env, str, (char *)nativeStr); - return JNI_DRM_FAILURE; - } - - (*env)->ReleaseStringUTFChars(env, str, (char *)nativeStr); - - return JNI_DRM_SUCCESS; -} - -/* native interface */ -JNIEXPORT jint JNICALL -Java_android_drm_mobile1_DrmRightsManager_nativeInstallDrmRights - (JNIEnv * env, jobject rightsManager, jobject data, jint len, jint mimeType, jobject rights) -{ - int32_t id; - T_DRM_Input_Data inData; - DrmData* drmInData; - jclass cls; - jmethodID mid; - T_DRM_Rights_Info rightsInfo; - - switch (mimeType) { - case JNI_DRM_MIMETYPE_RIGHTS_XML: - mimeType = TYPE_DRM_RIGHTS_XML; - break; - case JNI_DRM_MIMETYPE_RIGHTS_WBXML: - mimeType = TYPE_DRM_RIGHTS_WBXML; - break; - case JNI_DRM_MIMETYPE_MESSAGE: - mimeType = TYPE_DRM_MESSAGE; - break; - default: - return JNI_DRM_FAILURE; - } - - drmInData = newItem(); - if (NULL == drmInData) - return JNI_DRM_FAILURE; - - drmInData->env = env; - drmInData->pInData = &data; - drmInData->len = len; - - inData.inputHandle = (int32_t)drmInData; - inData.mimeType = mimeType; - inData.getInputDataLength = getInputStreamDataLength; - inData.readInputData = readInputStreamData; - - memset(&rightsInfo, 0, sizeof(T_DRM_Rights_Info)); - if (DRM_FAILURE == SVC_drm_installRights(inData, &rightsInfo)) - return JNI_DRM_FAILURE; - - freeItem(drmInData); - - return setRightsFields(env, rights, &rightsInfo); -} - -/* native interface */ -JNIEXPORT jint JNICALL -Java_android_drm_mobile1_DrmRightsManager_nativeQueryRights - (JNIEnv * env, jobject rightsManager, jobject rawContent, jobject rights) -{ - jint id; - T_DRM_Rights_Info rightsInfo; - - if (JNI_DRM_FAILURE == getObjectIntField(env, rawContent, "id", &id)) - return JNI_DRM_FAILURE; - - memset(&rightsInfo, 0, sizeof(T_DRM_Rights_Info)); - if (DRM_SUCCESS != SVC_drm_getRightsInfo(id, &rightsInfo)) - return JNI_DRM_FAILURE; - - return setRightsFields(env, rights, &rightsInfo); -} - -/* native interface */ -JNIEXPORT jint JNICALL -Java_android_drm_mobile1_DrmRightsManager_nativeGetNumOfRights - (JNIEnv * env, jobject rightsManager) -{ - T_DRM_Rights_Info_Node *pRightsList; - T_DRM_Rights_Info_Node *pCurNode; - int32_t num = 0; - - if (DRM_FAILURE == SVC_drm_viewAllRights(&pRightsList)) - return JNI_DRM_FAILURE; - - pCurNode = pRightsList; - while (pCurNode != NULL) { - num++; - pCurNode = pCurNode->next; - } - - SVC_drm_freeRightsInfoList(pRightsList); - - return num; -} - -/* native interface */ -JNIEXPORT jint JNICALL -Java_android_drm_mobile1_DrmRightsManager_nativeGetRightsList - (JNIEnv * env, jobject rightsManager, jobjectArray rightsArray, jint num) -{ - T_DRM_Rights_Info_Node *pRightsList; - T_DRM_Rights_Info_Node *pCurNode; - int32_t index; - - if (DRM_FAILURE == SVC_drm_viewAllRights(&pRightsList)) - return JNI_DRM_FAILURE; - - pCurNode = pRightsList; - for (index = 0; NULL != pCurNode; index++) { - jobject rights = (*env)->GetObjectArrayElement(env, rightsArray, index); - if (NULL == rights) - break; - - if (JNI_DRM_FAILURE == setRightsFields(env, rights, &(pCurNode->roInfo))) - break; - - (*env)->SetObjectArrayElement(env, rightsArray, index, rights); - - pCurNode = pCurNode->next; - } - - SVC_drm_freeRightsInfoList(pRightsList); - - return index; -} - -/* native interface */ -JNIEXPORT jint JNICALL -Java_android_drm_mobile1_DrmRightsManager_nativeDeleteRights - (JNIEnv * env, jobject rightsManager, jobject rights) -{ - jclass clazz; - jfieldID field; - jstring str; - uint8_t *nativeStr; - - clazz = (*env)->GetObjectClass(env, rights); - if (NULL == clazz) - return JNI_DRM_FAILURE; - - field = (*env)->GetFieldID(env, clazz, "roId", "Ljava/lang/String;"); - if (NULL == field) - return JNI_DRM_FAILURE; - - str = (*env)->GetObjectField(env, rights, field); - - nativeStr = (uint8_t *)(*env)->GetStringUTFChars(env, str, NULL); - if (NULL == nativeStr) - return JNI_DRM_FAILURE; - - if (0 == strcmp("ForwardLock", (char *)nativeStr)) - return JNI_DRM_SUCCESS; - - if (DRM_SUCCESS != SVC_drm_deleteRights(nativeStr)) { - (*env)->ReleaseStringUTFChars(env, str, (char *)nativeStr); - return JNI_DRM_FAILURE; - } - - (*env)->ReleaseStringUTFChars(env, str, (char *)nativeStr); - return JNI_DRM_SUCCESS; -} - -/* - * Table of methods associated with the DrmRawContent class. - */ -static JNINativeMethod gDrmRawContentMethods[] = { - /* name, signature, funcPtr */ - {"nativeConstructDrmContent", "(Ljava/io/InputStream;II)I", - (void*)Java_android_drm_mobile1_DrmRawContent_nativeConstructDrmContent}, - {"nativeGetRightsAddress", "()Ljava/lang/String;", - (void*)Java_android_drm_mobile1_DrmRawContent_nativeGetRightsAddress}, - {"nativeGetDeliveryMethod", "()I", - (void*)Java_android_drm_mobile1_DrmRawContent_nativeGetDeliveryMethod}, - {"nativeReadContent", "([BIII)I", - (void*)Java_android_drm_mobile1_DrmRawContent_nativeReadContent}, - {"nativeGetContentType", "()Ljava/lang/String;", - (void*)Java_android_drm_mobile1_DrmRawContent_nativeGetContentType}, - {"nativeGetContentLength", "()I", - (void*)Java_android_drm_mobile1_DrmRawContent_nativeGetContentLength}, - {"finalize", "()V", - (void*)Java_android_drm_mobile1_DrmRawContent_finalize}, -}; - -/* - * Table of methods associated with the DrmRights class. - */ -static JNINativeMethod gDrmRightsMethods[] = { - /* name, signature, funcPtr */ - {"nativeGetConstraintInfo", "(ILandroid/drm/mobile1/DrmConstraintInfo;)I", - (void*)Java_android_drm_mobile1_DrmRights_nativeGetConstraintInfo}, - {"nativeConsumeRights", "(I)I", - (void*)Java_android_drm_mobile1_DrmRights_nativeConsumeRights}, -}; - -/* - * Table of methods associated with the DrmRightsManager class. - */ -static JNINativeMethod gDrmRightsManagerMethods[] = { - /* name, signature, funcPtr */ - {"nativeInstallDrmRights", "(Ljava/io/InputStream;IILandroid/drm/mobile1/DrmRights;)I", - (void*)Java_android_drm_mobile1_DrmRightsManager_nativeInstallDrmRights}, - {"nativeQueryRights", "(Landroid/drm/mobile1/DrmRawContent;Landroid/drm/mobile1/DrmRights;)I", - (void*)Java_android_drm_mobile1_DrmRightsManager_nativeQueryRights}, - {"nativeGetNumOfRights", "()I", - (void*)Java_android_drm_mobile1_DrmRightsManager_nativeGetNumOfRights}, - {"nativeGetRightsList", "([Landroid/drm/mobile1/DrmRights;I)I", - (void*)Java_android_drm_mobile1_DrmRightsManager_nativeGetRightsList}, - {"nativeDeleteRights", "(Landroid/drm/mobile1/DrmRights;)I", - (void*)Java_android_drm_mobile1_DrmRightsManager_nativeDeleteRights}, -}; - -/* - * Register several native methods for one class. - */ -static int registerNativeMethods(JNIEnv* env, const char* className, - JNINativeMethod* gMethods, int numMethods) -{ - jclass clazz; - - clazz = (*env)->FindClass(env, className); - if (clazz == NULL) - return JNI_FALSE; - - if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) - return JNI_FALSE; - - return JNI_TRUE; -} - -/* - * Register native methods for all classes we know about. - */ -static int registerNatives(JNIEnv* env) -{ - if (!registerNativeMethods(env, "android/drm/mobile1/DrmRawContent", - gDrmRawContentMethods, sizeof(gDrmRawContentMethods) / sizeof(gDrmRawContentMethods[0]))) - return JNI_FALSE; - - if (!registerNativeMethods(env, "android/drm/mobile1/DrmRights", - gDrmRightsMethods, sizeof(gDrmRightsMethods) / sizeof(gDrmRightsMethods[0]))) - return JNI_FALSE; - - if (!registerNativeMethods(env, "android/drm/mobile1/DrmRightsManager", - gDrmRightsManagerMethods, sizeof(gDrmRightsManagerMethods) / sizeof(gDrmRightsManagerMethods[0]))) - return JNI_FALSE; - - return JNI_TRUE; -} - -jint JNI_OnLoad(JavaVM* vm, void* reserved) -{ - JNIEnv* env = NULL; - jint result = -1; - - printf("Entering JNI_OnLoad\n"); - - if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) - goto bail; - - assert(env != NULL); - - if (!registerNatives(env)) - goto bail; - - /* success -- return valid version number */ - result = JNI_VERSION_1_4; - -bail: - printf("Leaving JNI_OnLoad (result=0x%x)\n", result); - return result; -} diff --git a/media/libdrm/mobile1/src/objmng/drm_api.c b/media/libdrm/mobile1/src/objmng/drm_api.c deleted file mode 100644 index 232d9f4..0000000 --- a/media/libdrm/mobile1/src/objmng/drm_api.c +++ /dev/null @@ -1,1943 +0,0 @@ -/* - * 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. - */ - -#include <svc_drm.h> -#include <drm_inner.h> -#include <parser_dm.h> -#include <parser_dcf.h> -#include <parser_rel.h> -#include <drm_rights_manager.h> -#include <drm_time.h> -#include <drm_decoder.h> -#include "log.h" - -/** - * Current id. - */ -static int32_t curID = 0; - -/** - * The header pointer for the session list. - */ -static T_DRM_Session_Node* sessionTable = NULL; - -/** - * New a session. - */ -static T_DRM_Session_Node* newSession(T_DRM_Input_Data data) -{ - T_DRM_Session_Node* s = (T_DRM_Session_Node *)malloc(sizeof(T_DRM_Session_Node)); - - if (NULL != s) { - memset(s, 0, sizeof(T_DRM_Session_Node)); - - s->sessionId = curID++; - s->inputHandle = data.inputHandle; - s->mimeType = data.mimeType; - s->getInputDataLengthFunc = data.getInputDataLength; - s->readInputDataFunc = data.readInputData; - s->seekInputDataFunc = data.seekInputData; - } - - return s; -} - -/** - * Free a session. - */ -static void freeSession(T_DRM_Session_Node* s) -{ - if (NULL == s) - return; - - if (NULL != s->rawContent) - free(s->rawContent); - - if (NULL != s->readBuf) - free(s->readBuf); - - if (NULL != s->infoStruct) - free(s->infoStruct); - - free(s); -} - -/** - * Add a session to list. - */ -static int32_t addSession(T_DRM_Session_Node* s) -{ - if (NULL == s) - return -1; - - s->next = sessionTable; - sessionTable = s; - - return s->sessionId; -} - -/** - * Get a session from the list. - */ -static T_DRM_Session_Node* getSession(int32_t sessionId) -{ - T_DRM_Session_Node* s; - - if (sessionId < 0 || NULL == sessionTable) - return NULL; - - for (s = sessionTable; s != NULL; s = s->next) { - if (sessionId == s->sessionId) - return s; - } - - return NULL; -} - -/** - * Remove a session from the list. - */ -static void removeSession(int32_t sessionId) -{ - T_DRM_Session_Node *curS, *preS; - - if (sessionId < 0 || NULL == sessionTable) - return; - - if (sessionId == sessionTable->sessionId) { - curS = sessionTable; - sessionTable = curS->next; - freeSession(curS); - return; - } - - for (preS = sessionTable; preS->next != NULL; preS = preS->next) { - if (preS->next->sessionId == sessionId) - curS = preS->next; - } - - if (NULL == preS->next) - return; - - preS->next = curS->next; - freeSession(curS); -} - -/** - * Try to identify the mimetype according the input DRM data. - */ -static int32_t getMimeType(const uint8_t *buf, int32_t bufLen) -{ - const uint8_t *p; - - if (NULL == buf || bufLen <= 0) - return TYPE_DRM_UNKNOWN; - - p = buf; - - /* check if it is DRM Content Format, only check the first field of Version, it must be "0x01" */ - if (0x01 == *p) - return TYPE_DRM_CONTENT; - - /* check if it is DRM Message, only check the first two bytes, it must be the start flag of boundary: "--" */ - if (bufLen >= 2 && '-' == *p && '-' == *(p + 1)) - return TYPE_DRM_MESSAGE; - - /* check if it is DRM Rights XML format, only check the first several bytes, it must be: "<o-ex:rights" */ - if (bufLen >= 12 && 0 == strncmp("<o-ex:rights", (char *)p, 12)) - return TYPE_DRM_RIGHTS_XML; - - /* check if it is DRM Rights WBXML format, only check the first two bytes, it must be: 0x03, 0x0e */ - if (bufLen >= 2 && 0x03 == *p && 0x0e == *(p + 1)) - return TYPE_DRM_RIGHTS_WBXML; - - return TYPE_DRM_UNKNOWN; -} - -static int32_t drm_skipCRLFinB64(const uint8_t* b64Data, int32_t len) -{ - const uint8_t* p; - int32_t skipLen = 0; - - if (NULL == b64Data || len <= 0) - return -1; - - p = b64Data; - while (p - b64Data < len) { - if ('\r' == *p || '\n'== *p) - skipLen++; - p++; - } - - return skipLen; -} - -static int32_t drm_scanEndBoundary(const uint8_t* pBuf, int32_t len, uint8_t* const boundary) -{ - const uint8_t* p; - int32_t leftLen; - int32_t boundaryLen; - - if (NULL == pBuf || len <=0 || NULL == boundary) - return -1; - - p = pBuf; - boundaryLen = strlen((char *)boundary) + 2; /* 2 means: '\r' and '\n' */ - leftLen = len - (p - pBuf); - while (leftLen > 0) { - if (NULL == (p = memchr(p, '\r', leftLen))) - break; - - leftLen = len - (p - pBuf); - if (leftLen < boundaryLen) - return -2; /* here means may be the boundary has been split */ - - if (('\n' == *(p + 1)) && (0 == memcmp(p + 2, boundary, strlen((char *)boundary)))) - return p - pBuf; /* find the boundary here */ - - p++; - leftLen--; - } - - return len; /* no boundary found */ -} - -static int32_t drm_getLicenseInfo(T_DRM_Rights* pRights, T_DRM_Rights_Info* licenseInfo) -{ - if (NULL != licenseInfo && NULL != pRights) { - strcpy((char *)licenseInfo->roId, (char *)pRights->uid); - - if (1 == pRights->bIsDisplayable) { - licenseInfo->displayRights.indicator = pRights->DisplayConstraint.Indicator; - licenseInfo->displayRights.count = - pRights->DisplayConstraint.Count; - licenseInfo->displayRights.startDate = - pRights->DisplayConstraint.StartTime.date; - licenseInfo->displayRights.startTime = - pRights->DisplayConstraint.StartTime.time; - licenseInfo->displayRights.endDate = - pRights->DisplayConstraint.EndTime.date; - licenseInfo->displayRights.endTime = - pRights->DisplayConstraint.EndTime.time; - licenseInfo->displayRights.intervalDate = - pRights->DisplayConstraint.Interval.date; - licenseInfo->displayRights.intervalTime = - pRights->DisplayConstraint.Interval.time; - } - if (1 == pRights->bIsPlayable) { - licenseInfo->playRights.indicator = pRights->PlayConstraint.Indicator; - licenseInfo->playRights.count = pRights->PlayConstraint.Count; - licenseInfo->playRights.startDate = - pRights->PlayConstraint.StartTime.date; - licenseInfo->playRights.startTime = - pRights->PlayConstraint.StartTime.time; - licenseInfo->playRights.endDate = - pRights->PlayConstraint.EndTime.date; - licenseInfo->playRights.endTime = - pRights->PlayConstraint.EndTime.time; - licenseInfo->playRights.intervalDate = - pRights->PlayConstraint.Interval.date; - licenseInfo->playRights.intervalTime = - pRights->PlayConstraint.Interval.time; - } - if (1 == pRights->bIsExecuteable) { - licenseInfo->executeRights.indicator = pRights->ExecuteConstraint.Indicator; - licenseInfo->executeRights.count = - pRights->ExecuteConstraint.Count; - licenseInfo->executeRights.startDate = - pRights->ExecuteConstraint.StartTime.date; - licenseInfo->executeRights.startTime = - pRights->ExecuteConstraint.StartTime.time; - licenseInfo->executeRights.endDate = - pRights->ExecuteConstraint.EndTime.date; - licenseInfo->executeRights.endTime = - pRights->ExecuteConstraint.EndTime.time; - licenseInfo->executeRights.intervalDate = - pRights->ExecuteConstraint.Interval.date; - licenseInfo->executeRights.intervalTime = - pRights->ExecuteConstraint.Interval.time; - } - if (1 == pRights->bIsPrintable) { - licenseInfo->printRights.indicator = pRights->PrintConstraint.Indicator; - licenseInfo->printRights.count = - pRights->PrintConstraint.Count; - licenseInfo->printRights.startDate = - pRights->PrintConstraint.StartTime.date; - licenseInfo->printRights.startTime = - pRights->PrintConstraint.StartTime.time; - licenseInfo->printRights.endDate = - pRights->PrintConstraint.EndTime.date; - licenseInfo->printRights.endTime = - pRights->PrintConstraint.EndTime.time; - licenseInfo->printRights.intervalDate = - pRights->PrintConstraint.Interval.date; - licenseInfo->printRights.intervalTime = - pRights->PrintConstraint.Interval.time; - } - return TRUE; - } - return FALSE; -} - -static int32_t drm_addRightsNodeToList(T_DRM_Rights_Info_Node **ppRightsHeader, - T_DRM_Rights_Info_Node *pInputRightsNode) -{ - T_DRM_Rights_Info_Node *pRightsNode; - - if (NULL == ppRightsHeader || NULL == pInputRightsNode) - return FALSE; - - pRightsNode = (T_DRM_Rights_Info_Node *)malloc(sizeof(T_DRM_Rights_Info_Node)); - if (NULL == pRightsNode) - return FALSE; - - memcpy(pRightsNode, pInputRightsNode, sizeof(T_DRM_Rights_Info_Node)); - pRightsNode->next = NULL; - - /* this means it is the first node */ - if (NULL == *ppRightsHeader) - *ppRightsHeader = pRightsNode; - else { - T_DRM_Rights_Info_Node *pTmp; - - pTmp = *ppRightsHeader; - while (NULL != pTmp->next) - pTmp = pTmp->next; - - pTmp->next = pRightsNode; - } - return TRUE; -} - -static int32_t drm_startConsumeRights(int32_t * bIsXXable, - T_DRM_Rights_Constraint * XXConstraint, - int32_t * writeFlag) -{ - T_DB_TIME_SysTime curDateTime; - T_DRM_DATETIME CurrentTime; - uint8_t countFlag = 0; - - memset(&CurrentTime, 0, sizeof(T_DRM_DATETIME)); - - if (NULL == bIsXXable || 0 == *bIsXXable || NULL == XXConstraint || NULL == writeFlag) - return DRM_FAILURE; - - if (0 != (uint8_t)(XXConstraint->Indicator & DRM_NO_CONSTRAINT)) /* Have utter right? */ - return DRM_SUCCESS; - - *bIsXXable = 0; /* Assume have invalid rights at first */ - *writeFlag = 0; - - if (0 != (XXConstraint->Indicator & (DRM_START_TIME_CONSTRAINT | DRM_END_TIME_CONSTRAINT | DRM_INTERVAL_CONSTRAINT))) { - DRM_time_getSysTime(&curDateTime); - - if (-1 == drm_checkDate(curDateTime.year, curDateTime.month, curDateTime.day, - curDateTime.hour, curDateTime.min, curDateTime.sec)) - return DRM_FAILURE; - - YMD_HMS_2_INT(curDateTime.year, curDateTime.month, curDateTime.day, - CurrentTime.date, curDateTime.hour, curDateTime.min, - curDateTime.sec, CurrentTime.time); - } - - if (0 != (uint8_t)(XXConstraint->Indicator & DRM_COUNT_CONSTRAINT)) { /* Have count restrict? */ - *writeFlag = 1; - /* If it has only one time for use, after use this function, we will delete this rights */ - if (XXConstraint->Count <= 0) { - XXConstraint->Indicator &= ~DRM_COUNT_CONSTRAINT; - return DRM_RIGHTS_EXPIRED; - } - - if (XXConstraint->Count-- <= 1) { - XXConstraint->Indicator &= ~DRM_COUNT_CONSTRAINT; - countFlag = 1; - } - } - - if (0 != (uint8_t)(XXConstraint->Indicator & DRM_START_TIME_CONSTRAINT)) { - if (XXConstraint->StartTime.date > CurrentTime.date || - (XXConstraint->StartTime.date == CurrentTime.date && - XXConstraint->StartTime.time >= CurrentTime.time)) { - *bIsXXable = 1; - return DRM_RIGHTS_PENDING; - } - } - - if (0 != (uint8_t)(XXConstraint->Indicator & DRM_END_TIME_CONSTRAINT)) { /* Have end time restrict? */ - if (XXConstraint->EndTime.date < CurrentTime.date || - (XXConstraint->EndTime.date == CurrentTime.date && - XXConstraint->EndTime.time <= CurrentTime.time)) { - *writeFlag = 1; - XXConstraint->Indicator &= ~DRM_END_TIME_CONSTRAINT; - return DRM_RIGHTS_EXPIRED; - } - } - - if (0 != (uint8_t)(XXConstraint->Indicator & DRM_INTERVAL_CONSTRAINT)) { /* Have interval time restrict? */ - int32_t year, mon, day, hour, min, sec, date, time; - int32_t ret; - - XXConstraint->Indicator |= DRM_END_TIME_CONSTRAINT; - XXConstraint->Indicator &= ~DRM_INTERVAL_CONSTRAINT; /* Write off interval right */ - *writeFlag = 1; - - if (XXConstraint->Interval.date == 0 - && XXConstraint->Interval.time == 0) { - return DRM_RIGHTS_EXPIRED; - } - date = CurrentTime.date + XXConstraint->Interval.date; - time = CurrentTime.time + XXConstraint->Interval.time; - INT_2_YMD_HMS(year, mon, day, date, hour, min, sec, time); - - if (sec > 59) { - min += sec / 60; - sec %= 60; - } - if (min > 59) { - hour += min / 60; - min %= 60; - } - if (hour > 23) { - day += hour / 24; - hour %= 24; - } - if (day > 31) { - mon += day / 31; - day %= 31; - } - if (mon > 12) { - year += mon / 12; - mon %= 12; - } - if (day > (ret = drm_monthDays(year, mon))) { - day -= ret; - mon++; - if (mon > 12) { - mon -= 12; - year++; - } - } - YMD_HMS_2_INT(year, mon, day, XXConstraint->EndTime.date, hour, - min, sec, XXConstraint->EndTime.time); - } - - if (1 != countFlag) - *bIsXXable = 1; /* Can go here ,so right must be valid */ - return DRM_SUCCESS; -} - -static int32_t drm_startCheckRights(int32_t * bIsXXable, - T_DRM_Rights_Constraint * XXConstraint) -{ - T_DB_TIME_SysTime curDateTime; - T_DRM_DATETIME CurrentTime; - - memset(&CurrentTime, 0, sizeof(T_DRM_DATETIME)); - - if (NULL == bIsXXable || 0 == *bIsXXable || NULL == XXConstraint) - return DRM_FAILURE; - - if (0 != (uint8_t)(XXConstraint->Indicator & DRM_NO_CONSTRAINT)) /* Have utter right? */ - return DRM_SUCCESS; - - *bIsXXable = 0; /* Assume have invalid rights at first */ - - if (0 != (XXConstraint->Indicator & (DRM_START_TIME_CONSTRAINT | DRM_END_TIME_CONSTRAINT))) { - DRM_time_getSysTime(&curDateTime); - - if (-1 == drm_checkDate(curDateTime.year, curDateTime.month, curDateTime.day, - curDateTime.hour, curDateTime.min, curDateTime.sec)) - return DRM_FAILURE; - - YMD_HMS_2_INT(curDateTime.year, curDateTime.month, curDateTime.day, - CurrentTime.date, curDateTime.hour, curDateTime.min, - curDateTime.sec, CurrentTime.time); - } - - if (0 != (uint8_t)(XXConstraint->Indicator & DRM_COUNT_CONSTRAINT)) { /* Have count restrict? */ - if (XXConstraint->Count <= 0) { - XXConstraint->Indicator &= ~DRM_COUNT_CONSTRAINT; - return DRM_RIGHTS_EXPIRED; - } - } - - if (0 != (uint8_t)(XXConstraint->Indicator & DRM_START_TIME_CONSTRAINT)) { - if (XXConstraint->StartTime.date > CurrentTime.date || - (XXConstraint->StartTime.date == CurrentTime.date && - XXConstraint->StartTime.time >= CurrentTime.time)) { - *bIsXXable = 1; - return DRM_RIGHTS_PENDING; - } - } - - if (0 != (uint8_t)(XXConstraint->Indicator & DRM_END_TIME_CONSTRAINT)) { /* Have end time restrict? */ - if (XXConstraint->EndTime.date < CurrentTime.date || - (XXConstraint->EndTime.date == CurrentTime.date && - XXConstraint->EndTime.time <= CurrentTime.time)) { - XXConstraint->Indicator &= ~DRM_END_TIME_CONSTRAINT; - return DRM_RIGHTS_EXPIRED; - } - } - - if (0 != (uint8_t)(XXConstraint->Indicator & DRM_INTERVAL_CONSTRAINT)) { /* Have interval time restrict? */ - if (XXConstraint->Interval.date == 0 && XXConstraint->Interval.time == 0) { - XXConstraint->Indicator &= ~DRM_INTERVAL_CONSTRAINT; - return DRM_RIGHTS_EXPIRED; - } - } - - *bIsXXable = 1; - return DRM_SUCCESS; -} - -int32_t drm_checkRoAndUpdate(int32_t id, int32_t permission) -{ - int32_t writeFlag = 0; - int32_t roAmount; - int32_t validRoAmount = 0; - int32_t flag = DRM_FAILURE; - int32_t i, j; - T_DRM_Rights *pRo; - T_DRM_Rights *pCurRo; - int32_t * pNumOfPriority; - int32_t iNum; - T_DRM_Rights_Constraint * pCurConstraint; - T_DRM_Rights_Constraint * pCompareConstraint; - int priority[8] = {1, 2, 4, 3, 8, 6, 7, 5}; - - if (FALSE == drm_writeOrReadInfo(id, NULL, &roAmount, GET_ROAMOUNT)) - return DRM_FAILURE; - - validRoAmount = roAmount; - if (roAmount < 1) - return DRM_NO_RIGHTS; - - pRo = malloc(roAmount * sizeof(T_DRM_Rights)); - pCurRo = pRo; - if (NULL == pRo) - return DRM_FAILURE; - - if (FALSE == drm_writeOrReadInfo(id, pRo, &roAmount, GET_ALL_RO)) { - free(pRo); - return DRM_FAILURE; - } - - /** check the right priority */ - pNumOfPriority = malloc(sizeof(int32_t) * roAmount); - for(i = 0; i < roAmount; i++) { - iNum = roAmount - 1; - for(j = 0; j < roAmount; j++) { - if(i == j) - continue; - switch(permission) { - case DRM_PERMISSION_PLAY: - pCurConstraint = &pRo[i].PlayConstraint; - pCompareConstraint = &pRo[j].PlayConstraint; - break; - case DRM_PERMISSION_DISPLAY: - pCurConstraint = &pRo[i].DisplayConstraint; - pCompareConstraint = &pRo[j].DisplayConstraint; - break; - case DRM_PERMISSION_EXECUTE: - pCurConstraint = &pRo[i].ExecuteConstraint; - pCompareConstraint = &pRo[j].ExecuteConstraint; - break; - case DRM_PERMISSION_PRINT: - pCurConstraint = &pRo[i].PrintConstraint; - pCompareConstraint = &pRo[j].PrintConstraint; - break; - default: - free(pRo); - free(pNumOfPriority); - return DRM_FAILURE; - } - - /**get priority by Indicator*/ - if(0 == (pCurConstraint->Indicator & DRM_NO_CONSTRAINT) && - 0 == (pCompareConstraint->Indicator & DRM_NO_CONSTRAINT)) { - int num1, num2; - num1 = (pCurConstraint->Indicator & 0x0e) >> 1; - num2 = (pCompareConstraint->Indicator & 0x0e) >> 1; - if(priority[num1] > priority[num2]) { - iNum--; - continue; - } else if(priority[pCurConstraint->Indicator] < priority[pCompareConstraint->Indicator]) - continue; - } else if(pCurConstraint->Indicator > pCompareConstraint->Indicator) { - iNum--; - continue; - } else if(pCurConstraint->Indicator < pCompareConstraint->Indicator) - continue; - - if(0 != (pCurConstraint->Indicator & DRM_END_TIME_CONSTRAINT)) { - if(pCurConstraint->EndTime.date < pCompareConstraint->EndTime.date) { - iNum--; - continue; - } else if(pCurConstraint->EndTime.date > pCompareConstraint->EndTime.date) - continue; - - if(pCurConstraint->EndTime.time < pCompareConstraint->EndTime.time) { - iNum--; - continue; - } else if(pCurConstraint->EndTime.date > pCompareConstraint->EndTime.date) - continue; - } - - if(0 != (pCurConstraint->Indicator & DRM_INTERVAL_CONSTRAINT)) { - if(pCurConstraint->Interval.date < pCompareConstraint->Interval.date) { - iNum--; - continue; - } else if(pCurConstraint->Interval.date > pCompareConstraint->Interval.date) - continue; - - if(pCurConstraint->Interval.time < pCompareConstraint->Interval.time) { - iNum--; - continue; - } else if(pCurConstraint->Interval.time > pCompareConstraint->Interval.time) - continue; - } - - if(0 != (pCurConstraint->Indicator & DRM_COUNT_CONSTRAINT)) { - if(pCurConstraint->Count < pCompareConstraint->Count) { - iNum--; - continue; - } else if(pCurConstraint->Count > pCompareConstraint->Count) - continue; - } - - if(i < j) - iNum--; - } - pNumOfPriority[iNum] = i; - } - - for (i = 0; i < validRoAmount; i++) { - /** check the right priority */ - if (pNumOfPriority[i] >= validRoAmount) - break; - - pCurRo = pRo + pNumOfPriority[i]; - - switch (permission) { - case DRM_PERMISSION_PLAY: - flag = - drm_startConsumeRights(&pCurRo->bIsPlayable, - &pCurRo->PlayConstraint, &writeFlag); - break; - case DRM_PERMISSION_DISPLAY: - flag = - drm_startConsumeRights(&pCurRo->bIsDisplayable, - &pCurRo->DisplayConstraint, - &writeFlag); - break; - case DRM_PERMISSION_EXECUTE: - flag = - drm_startConsumeRights(&pCurRo->bIsExecuteable, - &pCurRo->ExecuteConstraint, - &writeFlag); - break; - case DRM_PERMISSION_PRINT: - flag = - drm_startConsumeRights(&pCurRo->bIsPrintable, - &pCurRo->PrintConstraint, &writeFlag); - break; - default: - free(pNumOfPriority); - free(pRo); - return DRM_FAILURE; - } - - /* Here confirm the valid RO amount and set the writeFlag */ - if (0 == pCurRo->bIsPlayable && 0 == pCurRo->bIsDisplayable && - 0 == pCurRo->bIsExecuteable && 0 == pCurRo->bIsPrintable) { - int32_t iCurPri; - - /** refresh the right priority */ - iCurPri = pNumOfPriority[i]; - for(j = i; j < validRoAmount - 1; j++) - pNumOfPriority[j] = pNumOfPriority[j + 1]; - - if(iCurPri != validRoAmount - 1) { - memcpy(pCurRo, pRo + validRoAmount - 1, - sizeof(T_DRM_Rights)); - for(j = 0; j < validRoAmount -1; j++) { - if(validRoAmount - 1 == pNumOfPriority[j]) - pNumOfPriority[j] = iCurPri; - } - } - - /* Here means it is not the last one RO, so the invalid RO should be deleted */ - writeFlag = 1; - validRoAmount--; /* If current right is invalid */ - i--; - } - - /* If the flag is TRUE, this means: we have found a valid RO, so break, no need to check other RO */ - if (DRM_SUCCESS == flag) - break; - } - - if (1 == writeFlag) { - /* Delete the *.info first */ - //drm_removeIdInfoFile(id); - - if (FALSE == drm_writeOrReadInfo(id, pRo, &validRoAmount, SAVE_ALL_RO)) - flag = DRM_FAILURE; - } - - free(pNumOfPriority); - free(pRo); - return flag; -} - - -/* see svc_drm.h */ -int32_t SVC_drm_installRights(T_DRM_Input_Data data, T_DRM_Rights_Info* pRightsInfo) -{ - uint8_t *buf; - int32_t dataLen, bufLen; - T_DRM_Rights rights; - - if (0 == data.inputHandle) - return DRM_RIGHTS_DATA_INVALID; - - /* Get input rights data length */ - dataLen = data.getInputDataLength(data.inputHandle); - if (dataLen <= 0) - return DRM_RIGHTS_DATA_INVALID; - - /* Check if the length is larger than DRM max malloc length */ - if (dataLen > DRM_MAX_MALLOC_LEN) - bufLen = DRM_MAX_MALLOC_LEN; - else - bufLen = dataLen; - - buf = (uint8_t *)malloc(bufLen); - if (NULL == buf) - return DRM_FAILURE; - - /* Read input data to buffer */ - if (0 >= data.readInputData(data.inputHandle, buf, bufLen)) { - free(buf); - return DRM_RIGHTS_DATA_INVALID; - } - - /* if the input mime type is unknown, DRM engine will try to recognize it. */ - if (TYPE_DRM_UNKNOWN == data.mimeType) - data.mimeType = getMimeType(buf, bufLen); - - switch(data.mimeType) { - case TYPE_DRM_MESSAGE: /* in case of Combined Delivery, extract the rights part to install */ - { - T_DRM_DM_Info dmInfo; - - memset(&dmInfo, 0, sizeof(T_DRM_DM_Info)); - if (FALSE == drm_parseDM(buf, bufLen, &dmInfo)) { - free(buf); - return DRM_RIGHTS_DATA_INVALID; - } - - /* if it is not Combined Delivery, it can not use to "SVC_drm_installRights" */ - if (COMBINED_DELIVERY != dmInfo.deliveryType || dmInfo.rightsOffset <= 0 || dmInfo.rightsLen <= 0) { - free(buf); - return DRM_RIGHTS_DATA_INVALID; - } - - memset(&rights, 0, sizeof(T_DRM_Rights)); - if (FALSE == drm_relParser(buf + dmInfo.rightsOffset, dmInfo.rightsLen, TYPE_DRM_RIGHTS_XML, &rights)) { - free(buf); - return DRM_RIGHTS_DATA_INVALID; - } - } - break; - case TYPE_DRM_RIGHTS_XML: - case TYPE_DRM_RIGHTS_WBXML: - memset(&rights, 0, sizeof(T_DRM_Rights)); - if (FALSE == drm_relParser(buf, bufLen, data.mimeType, &rights)) { - free(buf); - return DRM_RIGHTS_DATA_INVALID; - } - break; - case TYPE_DRM_CONTENT: /* DCF should not using "SVC_drm_installRights", it should be used to open a session. */ - case TYPE_DRM_UNKNOWN: - default: - free(buf); - return DRM_MEDIA_DATA_INVALID; - } - - free(buf); - - /* append the rights information to DRM engine storage */ - if (FALSE == drm_appendRightsInfo(&rights)) - return DRM_FAILURE; - - memset(pRightsInfo, 0, sizeof(T_DRM_Rights_Info)); - drm_getLicenseInfo(&rights, pRightsInfo); - - return DRM_SUCCESS; -} - -/* see svc_drm.h */ -int32_t SVC_drm_openSession(T_DRM_Input_Data data) -{ - int32_t session; - int32_t dataLen; - T_DRM_Session_Node* s; - - if (0 == data.inputHandle) - return DRM_MEDIA_DATA_INVALID; - - /* Get input data length */ - dataLen = data.getInputDataLength(data.inputHandle); - if (dataLen <= 0) - return DRM_MEDIA_DATA_INVALID; - - s = newSession(data); - if (NULL == s) - return DRM_FAILURE; - - /* Check if the length is larger than DRM max malloc length */ - if (dataLen > DRM_MAX_MALLOC_LEN) - s->rawContentLen = DRM_MAX_MALLOC_LEN; - else - s->rawContentLen = dataLen; - - s->rawContent = (uint8_t *)malloc(s->rawContentLen); - if (NULL == s->rawContent) - return DRM_FAILURE; - - /* Read input data to buffer */ - if (0 >= data.readInputData(data.inputHandle, s->rawContent, s->rawContentLen)) { - freeSession(s); - return DRM_MEDIA_DATA_INVALID; - } - - /* if the input mime type is unknown, DRM engine will try to recognize it. */ - if (TYPE_DRM_UNKNOWN == data.mimeType) - data.mimeType = getMimeType(s->rawContent, s->rawContentLen); - - switch(data.mimeType) { - case TYPE_DRM_MESSAGE: - { - T_DRM_DM_Info dmInfo; - - memset(&dmInfo, 0, sizeof(T_DRM_DM_Info)); - if (FALSE == drm_parseDM(s->rawContent, s->rawContentLen, &dmInfo)) { - freeSession(s); - return DRM_MEDIA_DATA_INVALID; - } - - s->deliveryMethod = dmInfo.deliveryType; - - if (SEPARATE_DELIVERY_FL == s->deliveryMethod) - s->contentLength = DRM_UNKNOWN_DATA_LEN; - else - s->contentLength = dmInfo.contentLen; - - s->transferEncoding = dmInfo.transferEncoding; - s->contentOffset = dmInfo.contentOffset; - s->bEndData = FALSE; - strcpy((char *)s->contentType, (char *)dmInfo.contentType); - strcpy((char *)s->contentID, (char *)dmInfo.contentID); - - if (SEPARATE_DELIVERY_FL == s->deliveryMethod) { - s->infoStruct = (T_DRM_Dcf_Node *)malloc(sizeof(T_DRM_Dcf_Node)); - if (NULL == s->infoStruct) - return DRM_FAILURE; - memset(s->infoStruct, 0, sizeof(T_DRM_Dcf_Node)); - - ((T_DRM_Dcf_Node *)(s->infoStruct))->encContentLength = dmInfo.contentLen; - strcpy((char *)((T_DRM_Dcf_Node *)(s->infoStruct))->rightsIssuer, (char *)dmInfo.rightsIssuer); - break; - } - - if (DRM_MESSAGE_CODING_BASE64 == s->transferEncoding) { - s->infoStruct = (T_DRM_DM_Base64_Node *)malloc(sizeof(T_DRM_DM_Base64_Node)); - if (NULL == s->infoStruct) - return DRM_FAILURE; - memset(s->infoStruct, 0, sizeof(T_DRM_DM_Base64_Node)); - - strcpy((char *)((T_DRM_DM_Base64_Node *)(s->infoStruct))->boundary, (char *)dmInfo.boundary); - } else { - s->infoStruct = (T_DRM_DM_Binary_Node *)malloc(sizeof(T_DRM_DM_Binary_Node)); - if (NULL == s->infoStruct) - return DRM_FAILURE; - memset(s->infoStruct, 0, sizeof(T_DRM_DM_Binary_Node)); - - strcpy((char *)((T_DRM_DM_Binary_Node *)(s->infoStruct))->boundary, (char *)dmInfo.boundary); - } - - - if (DRM_MESSAGE_CODING_BASE64 == s->transferEncoding) { - if (s->contentLength > 0) { - int32_t encLen, decLen; - - encLen = s->contentLength; - decLen = encLen / DRM_B64_ENC_BLOCK * DRM_B64_DEC_BLOCK; - - decLen = drm_decodeBase64(s->rawContent, decLen, s->rawContent + s->contentOffset, &encLen); - s->contentLength = decLen; - } else { - int32_t encLen = DRM_MAX_MALLOC_LEN - s->contentOffset, decLen; - int32_t skipLen, needBytes, i; - uint8_t *pStart; - int32_t res, bFoundBoundary = FALSE; - - pStart = s->rawContent + s->contentOffset; - if (-1 == (skipLen = drm_skipCRLFinB64(pStart, encLen))) { - freeSession(s); - return DRM_FAILURE; - } - - needBytes = DRM_B64_ENC_BLOCK - ((encLen - skipLen) % DRM_B64_ENC_BLOCK); - if (needBytes < DRM_B64_ENC_BLOCK) { - s->rawContent = (uint8_t *)realloc(s->rawContent, DRM_MAX_MALLOC_LEN + needBytes); - if (NULL == s->rawContent) { - freeSession(s); - return DRM_FAILURE; - } - - i = 0; - while (i < needBytes) { - if (-1 != data.readInputData(data.inputHandle, s->rawContent + DRM_MAX_MALLOC_LEN + i, 1)) { - if ('\r' == *(s->rawContent + DRM_MAX_MALLOC_LEN + i) || '\n' == *(s->rawContent + DRM_MAX_MALLOC_LEN + i)) - continue; - i++; - } else - break; - } - encLen += i; - } - - res = drm_scanEndBoundary(pStart, encLen, ((T_DRM_DM_Base64_Node *)(s->infoStruct))->boundary); - if (-1 == res) { - freeSession(s); - return DRM_FAILURE; - } - if (-2 == res) { /* may be there is a boundary */ - int32_t boundaryLen, leftLen, readBytes; - char* pTmp = memrchr(pStart, '\r', encLen); - - if (NULL == pTmp) { - freeSession(s); - return DRM_FAILURE; /* conflict */ - } - boundaryLen = strlen((char *)((T_DRM_DM_Base64_Node *)(s->infoStruct))->boundary) + 2; /* 2 means: '\r''\n' */ - s->readBuf = (uint8_t *)malloc(boundaryLen); - if (NULL == s->readBuf) { - freeSession(s); - return DRM_FAILURE; - } - s->readBufOff = encLen - ((uint8_t *)pTmp - pStart); - s->readBufLen = boundaryLen - s->readBufOff; - memcpy(s->readBuf, pTmp, s->readBufOff); - readBytes = data.readInputData(data.inputHandle, s->readBuf + s->readBufOff, s->readBufLen); - if (-1 == readBytes || readBytes < s->readBufLen) { - freeSession(s); - return DRM_MEDIA_DATA_INVALID; - } - - if (0 == drm_scanEndBoundary(s->readBuf, boundaryLen, ((T_DRM_DM_Base64_Node *)(s->infoStruct))->boundary)) { - encLen = (uint8_t *)pTmp - pStart; /* yes, it is the end boundary */ - bFoundBoundary = TRUE; - } - } else { - if (res >= 0 && res < encLen) { - encLen = res; - bFoundBoundary = TRUE; - } - } - - decLen = encLen / DRM_B64_ENC_BLOCK * DRM_B64_DEC_BLOCK; - decLen = drm_decodeBase64(s->rawContent, decLen, s->rawContent + s->contentOffset, &encLen); - ((T_DRM_DM_Base64_Node *)(s->infoStruct))->b64DecodeDataLen = decLen; - if (bFoundBoundary) - s->contentLength = decLen; - } - } else { - /* binary data */ - if (DRM_UNKNOWN_DATA_LEN == s->contentLength) { - /* try to check whether there is boundary may be split */ - int32_t res, binContentLen; - uint8_t* pStart; - int32_t bFoundBoundary = FALSE; - - pStart = s->rawContent + s->contentOffset; - binContentLen = s->rawContentLen - s->contentOffset; - res = drm_scanEndBoundary(pStart, binContentLen, ((T_DRM_DM_Binary_Node *)(s->infoStruct))->boundary); - - if (-1 == res) { - freeSession(s); - return DRM_FAILURE; - } - - if (-2 == res) { /* may be the boundary is split */ - int32_t boundaryLen, leftLen, readBytes; - char* pTmp = memrchr(pStart, '\r', binContentLen); - - if (NULL == pTmp) { - freeSession(s); - return DRM_FAILURE; /* conflict */ - } - - boundaryLen = strlen((char *)((T_DRM_DM_Binary_Node *)(s->infoStruct))->boundary) + 2; /* 2 means: '\r''\n' */ - s->readBuf = (uint8_t *)malloc(boundaryLen); - if (NULL == s->readBuf) { - freeSession(s); - return DRM_FAILURE; - } - s->readBufOff = binContentLen - ((uint8_t *)pTmp - pStart); - s->readBufLen = boundaryLen - s->readBufOff; - memcpy(s->readBuf, pTmp, s->readBufOff); - readBytes = data.readInputData(data.inputHandle, s->readBuf + s->readBufOff, s->readBufLen); - if (-1 == readBytes || readBytes < s->readBufLen) { - freeSession(s); - return DRM_MEDIA_DATA_INVALID; - } - - if (0 == drm_scanEndBoundary(s->readBuf, boundaryLen, ((T_DRM_DM_Binary_Node *)(s->infoStruct))->boundary)) { - binContentLen = (uint8_t *)pTmp - pStart; /* yes, it is the end boundary */ - bFoundBoundary = TRUE; - } - } else { - if (res >= 0 && res < binContentLen) { - binContentLen = res; - bFoundBoundary = TRUE; - } - } - - if (bFoundBoundary) - s->contentLength = binContentLen; - } - } - } - break; - case TYPE_DRM_CONTENT: - { - T_DRM_DCF_Info dcfInfo; - uint8_t* pEncData = NULL; - - memset(&dcfInfo, 0, sizeof(T_DRM_DCF_Info)); - if (FALSE == drm_dcfParser(s->rawContent, s->rawContentLen, &dcfInfo, &pEncData)) { - freeSession(s); - return DRM_MEDIA_DATA_INVALID; - } - - s->infoStruct = (T_DRM_Dcf_Node *)malloc(sizeof(T_DRM_Dcf_Node)); - if (NULL == s->infoStruct) - return DRM_FAILURE; - memset(s->infoStruct, 0, sizeof(T_DRM_Dcf_Node)); - - s->deliveryMethod = SEPARATE_DELIVERY; - s->contentLength = dcfInfo.DecryptedDataLen; - ((T_DRM_Dcf_Node *)(s->infoStruct))->encContentLength = dcfInfo.EncryptedDataLen; - s->contentOffset = pEncData - s->rawContent; - strcpy((char *)s->contentType, (char *)dcfInfo.ContentType); - strcpy((char *)s->contentID, (char *)dcfInfo.ContentURI); - strcpy((char *)((T_DRM_Dcf_Node *)(s->infoStruct))->rightsIssuer, (char *)dcfInfo.Rights_Issuer); - } - break; - case TYPE_DRM_RIGHTS_XML: /* rights object should using "SVC_drm_installRights", it can not open a session */ - case TYPE_DRM_RIGHTS_WBXML: /* rights object should using "SVC_drm_installRights", it can not open a session */ - case TYPE_DRM_UNKNOWN: - default: - freeSession(s); - return DRM_MEDIA_DATA_INVALID; - } - - if ((SEPARATE_DELIVERY_FL == s->deliveryMethod || SEPARATE_DELIVERY == s->deliveryMethod) && - s->contentOffset + ((T_DRM_Dcf_Node *)(s->infoStruct))->encContentLength <= DRM_MAX_MALLOC_LEN) { - uint8_t keyValue[DRM_KEY_LEN]; - uint8_t lastDcfBuf[DRM_TWO_AES_BLOCK_LEN]; - int32_t seekPos, moreBytes; - - if (TRUE == drm_getKey(s->contentID, keyValue)) { - seekPos = s->contentOffset + ((T_DRM_Dcf_Node *)(s->infoStruct))->encContentLength - DRM_TWO_AES_BLOCK_LEN; - memcpy(lastDcfBuf, s->rawContent + seekPos, DRM_TWO_AES_BLOCK_LEN); - - if (TRUE == drm_updateDcfDataLen(lastDcfBuf, keyValue, &moreBytes)) { - s->contentLength = ((T_DRM_Dcf_Node *)(s->infoStruct))->encContentLength; - s->contentLength -= moreBytes; - } - } - } - - session = addSession(s); - if (-1 == session) - return DRM_FAILURE; - - return session; -} - -/* see svc_drm.h */ -int32_t SVC_drm_getDeliveryMethod(int32_t session) -{ - T_DRM_Session_Node* s; - - if (session < 0) - return DRM_FAILURE; - - s = getSession(session); - if (NULL == s) - return DRM_SESSION_NOT_OPENED; - - return s->deliveryMethod; -} - -/* see svc_drm.h */ -int32_t SVC_drm_getContentType(int32_t session, uint8_t* mediaType) -{ - T_DRM_Session_Node* s; - - if (session < 0 || NULL == mediaType) - return DRM_FAILURE; - - s = getSession(session); - if (NULL == s) - return DRM_SESSION_NOT_OPENED; - - strcpy((char *)mediaType, (char *)s->contentType); - - return DRM_SUCCESS; -} - -/* see svc_drm.h */ -int32_t SVC_drm_checkRights(int32_t session, int32_t permission) -{ - T_DRM_Session_Node* s; - int32_t id; - T_DRM_Rights *pRo, *pCurRo; - int32_t roAmount; - int32_t i; - int32_t res = DRM_FAILURE; - - if (session < 0) - return DRM_FAILURE; - - s = getSession(session); - if (NULL == s) - return DRM_SESSION_NOT_OPENED; - - /* if it is Forward-Lock cases, check it and return directly */ - if (FORWARD_LOCK == s->deliveryMethod) { - if (DRM_PERMISSION_PLAY == permission || - DRM_PERMISSION_DISPLAY == permission || - DRM_PERMISSION_EXECUTE == permission || - DRM_PERMISSION_PRINT == permission) - return DRM_SUCCESS; - - return DRM_FAILURE; - } - - /* if try to forward, only DCF can be forwarded */ - if (DRM_PERMISSION_FORWARD == permission) { - if (SEPARATE_DELIVERY == s->deliveryMethod) - return DRM_SUCCESS; - - return DRM_FAILURE; - } - - /* The following will check CD or SD other permissions */ - if (FALSE == drm_readFromUidTxt(s->contentID, &id, GET_ID)) - return DRM_FAILURE; - - drm_writeOrReadInfo(id, NULL, &roAmount, GET_ROAMOUNT); - if (roAmount <= 0) - return DRM_FAILURE; - - pRo = malloc(roAmount * sizeof(T_DRM_Rights)); - if (NULL == pRo) - return DRM_FAILURE; - - drm_writeOrReadInfo(id, pRo, &roAmount, GET_ALL_RO); - - pCurRo = pRo; - for (i = 0; i < roAmount; i++) { - switch (permission) { - case DRM_PERMISSION_PLAY: - res = drm_startCheckRights(&(pCurRo->bIsPlayable), &(pCurRo->PlayConstraint)); - break; - case DRM_PERMISSION_DISPLAY: - res = drm_startCheckRights(&(pCurRo->bIsDisplayable), &(pCurRo->DisplayConstraint)); - break; - case DRM_PERMISSION_EXECUTE: - res = drm_startCheckRights(&(pCurRo->bIsExecuteable), &(pCurRo->ExecuteConstraint)); - break; - case DRM_PERMISSION_PRINT: - res = drm_startCheckRights(&(pCurRo->bIsPrintable), &(pCurRo->PrintConstraint)); - break; - default: - free(pRo); - return DRM_FAILURE; - } - - if (DRM_SUCCESS == res) { - free(pRo); - return DRM_SUCCESS; - } - pCurRo++; - } - - free(pRo); - return res; -} - -/* see svc_drm.h */ -int32_t SVC_drm_consumeRights(int32_t session, int32_t permission) -{ - T_DRM_Session_Node* s; - int32_t id; - - if (session < 0) - return DRM_FAILURE; - - s = getSession(session); - if (NULL == s) - return DRM_SESSION_NOT_OPENED; - - if (DRM_PERMISSION_FORWARD == permission) { - if (SEPARATE_DELIVERY == s->deliveryMethod) - return DRM_SUCCESS; - - return DRM_FAILURE; - } - - if (FORWARD_LOCK == s->deliveryMethod) /* Forwardlock type have utter rights */ - return DRM_SUCCESS; - - if (FALSE == drm_readFromUidTxt(s->contentID, &id, GET_ID)) - return DRM_FAILURE; - - return drm_checkRoAndUpdate(id, permission); -} - -/* see svc_drm.h */ -int32_t SVC_drm_getContentLength(int32_t session) -{ - T_DRM_Session_Node* s; - - if (session < 0) - return DRM_FAILURE; - - s = getSession(session); - if (NULL == s) - return DRM_SESSION_NOT_OPENED; - - if (DRM_UNKNOWN_DATA_LEN == s->contentLength && s->contentOffset + ((T_DRM_Dcf_Node *)(s->infoStruct))->encContentLength <= DRM_MAX_MALLOC_LEN && - (SEPARATE_DELIVERY == s->deliveryMethod || SEPARATE_DELIVERY_FL == s->deliveryMethod)) { - uint8_t keyValue[DRM_KEY_LEN]; - uint8_t lastDcfBuf[DRM_TWO_AES_BLOCK_LEN]; - int32_t seekPos, moreBytes; - - if (TRUE == drm_getKey(s->contentID, keyValue)) { - seekPos = s->contentOffset + ((T_DRM_Dcf_Node *)(s->infoStruct))->encContentLength - DRM_TWO_AES_BLOCK_LEN; - memcpy(lastDcfBuf, s->rawContent + seekPos, DRM_TWO_AES_BLOCK_LEN); - - if (TRUE == drm_updateDcfDataLen(lastDcfBuf, keyValue, &moreBytes)) { - s->contentLength = ((T_DRM_Dcf_Node *)(s->infoStruct))->encContentLength; - s->contentLength -= moreBytes; - } - } - } - - return s->contentLength; -} - -static int32_t drm_readAesData(uint8_t* buf, T_DRM_Session_Node* s, int32_t aesStart, int32_t bufLen) -{ - if (NULL == buf || NULL == s || aesStart < 0 || bufLen < 0) - return -1; - - if (aesStart - s->contentOffset + bufLen > ((T_DRM_Dcf_Node *)(s->infoStruct))->encContentLength) - return -2; - - if (aesStart < DRM_MAX_MALLOC_LEN) { - if (aesStart + bufLen <= DRM_MAX_MALLOC_LEN) { /* read from buffer */ - memcpy(buf, s->rawContent + aesStart, bufLen); - return bufLen; - } else { /* first read from buffer and then from InputStream */ - int32_t point = DRM_MAX_MALLOC_LEN - aesStart; - int32_t res; - - if (((T_DRM_Dcf_Node *)(s->infoStruct))->bAesBackupBuf) { - memcpy(buf, ((T_DRM_Dcf_Node *)(s->infoStruct))->aesBackupBuf, DRM_ONE_AES_BLOCK_LEN); - res = s->readInputDataFunc(s->inputHandle, buf + DRM_ONE_AES_BLOCK_LEN, DRM_ONE_AES_BLOCK_LEN); - if (0 == res || -1 == res) - return -1; - - res += DRM_ONE_AES_BLOCK_LEN; - } else { - memcpy(buf, s->rawContent + aesStart, point); - res = s->readInputDataFunc(s->inputHandle, buf + point, bufLen - point); - if (0 == res || -1 == res) - return -1; - - res += point; - } - - memcpy(((T_DRM_Dcf_Node *)(s->infoStruct))->aesBackupBuf, buf + DRM_ONE_AES_BLOCK_LEN, DRM_ONE_AES_BLOCK_LEN); - ((T_DRM_Dcf_Node *)(s->infoStruct))->bAesBackupBuf = TRUE; - - return res; - } - } else { /* read from InputStream */ - int32_t res; - - memcpy(buf, ((T_DRM_Dcf_Node *)(s->infoStruct))->aesBackupBuf, DRM_ONE_AES_BLOCK_LEN); - res = s->readInputDataFunc(s->inputHandle, buf + DRM_ONE_AES_BLOCK_LEN, DRM_ONE_AES_BLOCK_LEN); - - if (0 == res || -1 == res) - return -1; - - memcpy(((T_DRM_Dcf_Node *)(s->infoStruct))->aesBackupBuf, buf + DRM_ONE_AES_BLOCK_LEN, DRM_ONE_AES_BLOCK_LEN); - - return DRM_ONE_AES_BLOCK_LEN + res; - } -} - -static int32_t drm_readContentFromBuf(T_DRM_Session_Node* s, int32_t offset, uint8_t* mediaBuf, int32_t mediaBufLen) -{ - int32_t readBytes; - - if (offset > s->contentLength) - return DRM_FAILURE; - - if (offset == s->contentLength) - return DRM_MEDIA_EOF; - - if (offset + mediaBufLen > s->contentLength) - readBytes = s->contentLength - offset; - else - readBytes = mediaBufLen; - - if (DRM_MESSAGE_CODING_BASE64 == s->transferEncoding) - memcpy(mediaBuf, s->rawContent + offset, readBytes); - else - memcpy(mediaBuf, s->rawContent + s->contentOffset + offset, readBytes); - - return readBytes; -} - -static int32_t drm_readB64ContentFromInputStream(T_DRM_Session_Node* s, int32_t offset, uint8_t* mediaBuf, int32_t mediaBufLen) -{ - uint8_t encBuf[DRM_B64_ENC_BLOCK], decBuf[DRM_B64_DEC_BLOCK]; - int32_t encLen, decLen; - int32_t i, j, piece, leftLen, firstBytes; - int32_t readBytes = 0; - - if (offset < ((T_DRM_DM_Base64_Node *)(s->infoStruct))->b64DecodeDataLen) { - readBytes = ((T_DRM_DM_Base64_Node *)(s->infoStruct))->b64DecodeDataLen - offset; - memcpy(mediaBuf, s->rawContent + offset, readBytes); - } else { - if (s->bEndData) - return DRM_MEDIA_EOF; - - firstBytes = offset % DRM_B64_DEC_BLOCK; - if (firstBytes > 0) { - if (DRM_B64_DEC_BLOCK - firstBytes >= mediaBufLen) { - readBytes = mediaBufLen; - memcpy(mediaBuf, ((T_DRM_DM_Base64_Node *)(s->infoStruct))->b64DecodeData + firstBytes, readBytes); - return readBytes; - } - - readBytes = DRM_B64_DEC_BLOCK - firstBytes; - memcpy(mediaBuf, ((T_DRM_DM_Base64_Node *)(s->infoStruct))->b64DecodeData + firstBytes, readBytes); - } - } - - leftLen = mediaBufLen - readBytes; - encLen = (leftLen - 1) / DRM_B64_DEC_BLOCK * DRM_B64_ENC_BLOCK + DRM_B64_ENC_BLOCK; - piece = encLen / DRM_B64_ENC_BLOCK; - - for (i = 0; i < piece; i++) { - j = 0; - while (j < DRM_B64_ENC_BLOCK) { - if (NULL != s->readBuf && s->readBufLen > 0) { /* read from backup buffer */ - *(encBuf + j) = s->readBuf[s->readBufOff]; - s->readBufOff++; - s->readBufLen--; - } else { /* read from InputStream */ - if (0 == s->readInputDataFunc(s->inputHandle, encBuf + j, 1)) - return DRM_MEDIA_DATA_INVALID; - } - - if ('\r' == *(encBuf + j) || '\n' == *(encBuf + j)) - continue; /* skip CRLF */ - - if ('-' == *(encBuf + j)) { - int32_t k, len; - - /* invalid base64 data, it comes to end boundary */ - if (0 != j) - return DRM_MEDIA_DATA_INVALID; - - /* check whether it is really the boundary */ - len = strlen((char *)((T_DRM_DM_Base64_Node *)(s->infoStruct))->boundary); - if (NULL == s->readBuf) { - s->readBuf = (uint8_t *)malloc(len); - if (NULL == s->readBuf) - return DRM_FAILURE; - } - - s->readBuf[0] = '-'; - for (k = 0; k < len - 1; k++) { - if (NULL != s->readBuf && s->readBufLen > 0) { /* read from backup buffer */ - *(s->readBuf + k + 1) = s->readBuf[s->readBufOff]; - s->readBufOff++; - s->readBufLen--; - } else { /* read from InputStream */ - if (-1 == s->readInputDataFunc(s->inputHandle, s->readBuf + k + 1, 1)) - return DRM_MEDIA_DATA_INVALID; - } - } - if (0 == memcmp(s->readBuf, ((T_DRM_DM_Base64_Node *)(s->infoStruct))->boundary, len)) - s->bEndData = TRUE; - else - return DRM_MEDIA_DATA_INVALID; - - break; - } - j++; - } - - if (TRUE == s->bEndData) { /* it means come to the end of base64 data */ - if (0 == readBytes) - return DRM_MEDIA_EOF; - - break; - } - - encLen = DRM_B64_ENC_BLOCK; - decLen = DRM_B64_DEC_BLOCK; - if (-1 == (decLen = drm_decodeBase64(decBuf, decLen, encBuf, &encLen))) - return DRM_MEDIA_DATA_INVALID; - - if (leftLen >= decLen) { - memcpy(mediaBuf + readBytes, decBuf, decLen); - readBytes += decLen; - leftLen -= decLen; - } else { - if (leftLen > 0) { - memcpy(mediaBuf + readBytes, decBuf, leftLen); - readBytes += leftLen; - } - break; - } - } - memcpy(((T_DRM_DM_Base64_Node *)(s->infoStruct))->b64DecodeData, decBuf, DRM_B64_DEC_BLOCK); - - return readBytes; -} - -static int32_t drm_readBase64Content(T_DRM_Session_Node* s, int32_t offset, uint8_t* mediaBuf, int32_t mediaBufLen) -{ - int32_t readBytes; - - /* when the content length has been well-known */ - if (s->contentLength >= 0) - readBytes = drm_readContentFromBuf(s, offset, mediaBuf, mediaBufLen); - else /* else when the content length has not been well-known yet */ - if (offset < ((T_DRM_DM_Base64_Node *)(s->infoStruct))->b64DecodeDataLen) - if (offset + mediaBufLen <= ((T_DRM_DM_Base64_Node *)(s->infoStruct))->b64DecodeDataLen) { - readBytes = mediaBufLen; - memcpy(mediaBuf, s->rawContent + offset, readBytes); - } else - readBytes = drm_readB64ContentFromInputStream(s, offset, mediaBuf, mediaBufLen); - else - readBytes = drm_readB64ContentFromInputStream(s, offset, mediaBuf, mediaBufLen); - - return readBytes; -} - -static int32_t drm_readBinaryContentFromInputStream(T_DRM_Session_Node* s, int32_t offset, uint8_t* mediaBuf, int32_t mediaBufLen) -{ - int32_t res = 0, readBytes = 0; - int32_t leftLen; - - if (s->contentOffset + offset < DRM_MAX_MALLOC_LEN) { - readBytes = DRM_MAX_MALLOC_LEN - s->contentOffset - offset; - memcpy(mediaBuf, s->rawContent + s->contentOffset + offset, readBytes); - } else - if (s->bEndData) - return DRM_MEDIA_EOF; - - leftLen = mediaBufLen - readBytes; - - if (NULL != s->readBuf && s->readBufLen > 0) { /* read from backup buffer */ - if (leftLen <= s->readBufLen) { - memcpy(mediaBuf + readBytes, s->readBuf + s->readBufOff, leftLen); - s->readBufOff += leftLen; - s->readBufLen -= leftLen; - readBytes += leftLen; - leftLen = 0; - } else { - memcpy(mediaBuf + readBytes, s->readBuf + s->readBufOff, s->readBufLen); - s->readBufOff += s->readBufLen; - leftLen -= s->readBufLen; - readBytes += s->readBufLen; - s->readBufLen = 0; - } - } - - if (leftLen > 0) { - res = s->readInputDataFunc(s->inputHandle, mediaBuf + readBytes, mediaBufLen - readBytes); - if (-1 == res) - return DRM_MEDIA_DATA_INVALID; - } - - readBytes += res; - res = drm_scanEndBoundary(mediaBuf, readBytes, ((T_DRM_DM_Binary_Node *)(s->infoStruct))->boundary); - if (-1 == res) - return DRM_MEDIA_DATA_INVALID; - if (-2 == res) { /* may be the boundary is split */ - int32_t boundaryLen, len, off, k; - char* pTmp = memrchr(mediaBuf, '\r', readBytes); - - if (NULL == pTmp) - return DRM_FAILURE; /* conflict */ - - boundaryLen = strlen((char *)((T_DRM_DM_Binary_Node *)(s->infoStruct))->boundary) + 2; /* 2 means: '\r''\n' */ - if (NULL == s->readBuf) { - s->readBuf = (uint8_t *)malloc(boundaryLen); - if (NULL == s->readBuf) - return DRM_FAILURE; - } - - off = readBytes - ((uint8_t *)pTmp - mediaBuf); - len = boundaryLen - off; - memcpy(s->readBuf, pTmp, off); - for (k = 0; k < boundaryLen - off; k++) { - if (NULL != s->readBuf && s->readBufLen > 0) { /* read from backup buffer */ - *(s->readBuf + k + off) = s->readBuf[s->readBufOff]; - s->readBufOff++; - s->readBufLen--; - } else { /* read from InputStream */ - if (-1 == s->readInputDataFunc(s->inputHandle, s->readBuf + k + off, 1)) - return DRM_MEDIA_DATA_INVALID; - } - } - s->readBufOff = off; - s->readBufLen = len; - - if (0 == drm_scanEndBoundary(s->readBuf, boundaryLen, ((T_DRM_DM_Binary_Node *)(s->infoStruct))->boundary)) { - readBytes = (uint8_t *)pTmp - mediaBuf; /* yes, it is the end boundary */ - s->bEndData = TRUE; - } - } else { - if (res >= 0 && res < readBytes) { - readBytes = res; - s->bEndData = TRUE; - } - } - - if (s->bEndData) { - if (0 == readBytes) - return DRM_MEDIA_EOF; - } - - return readBytes; -} - -static int32_t drm_readBinaryContent(T_DRM_Session_Node* s, int32_t offset, uint8_t* mediaBuf, int32_t mediaBufLen) -{ - int32_t readBytes; - - if (s->contentLength >= 0) - readBytes = drm_readContentFromBuf(s, offset, mediaBuf, mediaBufLen); - else /* else when the content length has not been well-known yet */ - if (s->contentOffset + offset < DRM_MAX_MALLOC_LEN) - if (s->contentOffset + offset + mediaBufLen <= DRM_MAX_MALLOC_LEN) { - readBytes = mediaBufLen; - memcpy(mediaBuf, s->rawContent + s->contentOffset + offset, readBytes); - } else - readBytes = drm_readBinaryContentFromInputStream(s, offset, mediaBuf, mediaBufLen); - else - readBytes = drm_readBinaryContentFromInputStream(s, offset, mediaBuf, mediaBufLen); - - return readBytes; -} - -static int32_t drm_readAesContent(T_DRM_Session_Node* s, int32_t offset, uint8_t* mediaBuf, int32_t mediaBufLen) -{ - uint8_t keyValue[DRM_KEY_LEN]; - uint8_t buf[DRM_TWO_AES_BLOCK_LEN]; - int32_t readBytes = 0; - int32_t bufLen, piece, i, copyBytes, leftBytes; - int32_t aesStart, mediaStart, mediaBufOff; - AES_KEY key; - - if (FALSE == drm_getKey(s->contentID, keyValue)) - return DRM_NO_RIGHTS; - - /* when the content length has been well-known */ - if (s->contentLength > 0) { - if (offset > s->contentLength) - return DRM_FAILURE; - - if (offset == s->contentLength) - return DRM_MEDIA_EOF; - - if (offset + mediaBufLen > s->contentLength) - readBytes = s->contentLength - offset; - else - readBytes = mediaBufLen; - - aesStart = s->contentOffset + (offset / DRM_ONE_AES_BLOCK_LEN * DRM_ONE_AES_BLOCK_LEN); - piece = (offset + readBytes - 1) / DRM_ONE_AES_BLOCK_LEN - offset / DRM_ONE_AES_BLOCK_LEN + 2; - mediaStart = offset % DRM_ONE_AES_BLOCK_LEN; - - AES_set_decrypt_key(keyValue, DRM_KEY_LEN * 8, &key); - mediaBufOff = 0; - leftBytes = readBytes; - - for (i = 0; i < piece - 1; i++) { - memcpy(buf, s->rawContent + aesStart + i * DRM_ONE_AES_BLOCK_LEN, DRM_TWO_AES_BLOCK_LEN); - bufLen = DRM_TWO_AES_BLOCK_LEN; - - if (drm_aesDecBuffer(buf, &bufLen, &key) < 0) - return DRM_MEDIA_DATA_INVALID; - - if (0 != i) - mediaStart = 0; - - if (bufLen - mediaStart <= leftBytes) - copyBytes = bufLen - mediaStart; - else - copyBytes = leftBytes; - - memcpy(mediaBuf + mediaBufOff, buf + mediaStart, copyBytes); - leftBytes -= copyBytes; - mediaBufOff += copyBytes; - } - } else { - int32_t res; - - if (s->bEndData) - return DRM_MEDIA_EOF; - - if (((T_DRM_Dcf_Node *)(s->infoStruct))->aesDecDataLen > ((T_DRM_Dcf_Node *)(s->infoStruct))->aesDecDataOff) { - if (mediaBufLen < ((T_DRM_Dcf_Node *)(s->infoStruct))->aesDecDataLen - ((T_DRM_Dcf_Node *)(s->infoStruct))->aesDecDataOff) - copyBytes = mediaBufLen; - else - copyBytes = ((T_DRM_Dcf_Node *)(s->infoStruct))->aesDecDataLen - ((T_DRM_Dcf_Node *)(s->infoStruct))->aesDecDataOff; - - memcpy(mediaBuf, ((T_DRM_Dcf_Node *)(s->infoStruct))->aesDecData + ((T_DRM_Dcf_Node *)(s->infoStruct))->aesDecDataOff, copyBytes); - ((T_DRM_Dcf_Node *)(s->infoStruct))->aesDecDataOff += copyBytes; - readBytes += copyBytes; - } - - leftBytes = mediaBufLen - readBytes; - if (0 == leftBytes) - return readBytes; - if (leftBytes < 0) - return DRM_FAILURE; - - offset += readBytes; - aesStart = s->contentOffset + (offset / DRM_ONE_AES_BLOCK_LEN * DRM_ONE_AES_BLOCK_LEN); - piece = (offset + leftBytes - 1) / DRM_ONE_AES_BLOCK_LEN - offset / DRM_ONE_AES_BLOCK_LEN + 2; - mediaBufOff = readBytes; - - AES_set_decrypt_key(keyValue, DRM_KEY_LEN * 8, &key); - - for (i = 0; i < piece - 1; i++) { - if (-1 == (res = drm_readAesData(buf, s, aesStart, DRM_TWO_AES_BLOCK_LEN))) - return DRM_MEDIA_DATA_INVALID; - - if (-2 == res) - break; - - bufLen = DRM_TWO_AES_BLOCK_LEN; - aesStart += DRM_ONE_AES_BLOCK_LEN; - - if (drm_aesDecBuffer(buf, &bufLen, &key) < 0) - return DRM_MEDIA_DATA_INVALID; - - drm_discardPaddingByte(buf, &bufLen); - - if (bufLen <= leftBytes) - copyBytes = bufLen; - else - copyBytes = leftBytes; - - memcpy(mediaBuf + mediaBufOff, buf, copyBytes); - leftBytes -= copyBytes; - mediaBufOff += copyBytes; - readBytes += copyBytes; - } - - memcpy(((T_DRM_Dcf_Node *)(s->infoStruct))->aesDecData, buf, DRM_ONE_AES_BLOCK_LEN); - ((T_DRM_Dcf_Node *)(s->infoStruct))->aesDecDataLen = bufLen; - ((T_DRM_Dcf_Node *)(s->infoStruct))->aesDecDataOff = copyBytes; - - if (aesStart - s->contentOffset > ((T_DRM_Dcf_Node *)(s->infoStruct))->encContentLength - DRM_TWO_AES_BLOCK_LEN && ((T_DRM_Dcf_Node *)(s->infoStruct))->aesDecDataOff == ((T_DRM_Dcf_Node *)(s->infoStruct))->aesDecDataLen) { - s->bEndData = TRUE; - if (0 == readBytes) - return DRM_MEDIA_EOF; - } - } - - return readBytes; -} - -/* see svc_drm.h */ -int32_t SVC_drm_getContent(int32_t session, int32_t offset, uint8_t* mediaBuf, int32_t mediaBufLen) -{ - T_DRM_Session_Node* s; - int32_t readBytes; - - if (session < 0 || offset < 0 || NULL == mediaBuf || mediaBufLen <= 0) - return DRM_FAILURE; - - s = getSession(session); - if (NULL == s) - return DRM_SESSION_NOT_OPENED; - - if (0 >= s->getInputDataLengthFunc(s->inputHandle)) - return DRM_MEDIA_DATA_INVALID; - - switch(s->deliveryMethod) { - case FORWARD_LOCK: - case COMBINED_DELIVERY: - if (DRM_MESSAGE_CODING_BASE64 == s->transferEncoding) - readBytes = drm_readBase64Content(s, offset, mediaBuf, mediaBufLen); - else /* binary */ - readBytes = drm_readBinaryContent(s, offset, mediaBuf, mediaBufLen); - break; - case SEPARATE_DELIVERY: - case SEPARATE_DELIVERY_FL: - readBytes = drm_readAesContent(s, offset, mediaBuf, mediaBufLen); - break; - default: - return DRM_FAILURE; - } - - return readBytes; -} - -/* see svc_drm.h */ -int32_t SVC_drm_getRightsIssuer(int32_t session, uint8_t* rightsIssuer) -{ - T_DRM_Session_Node* s; - - if (session < 0 || NULL == rightsIssuer) - return DRM_FAILURE; - - s = getSession(session); - if (NULL == s) - return DRM_SESSION_NOT_OPENED; - - if (SEPARATE_DELIVERY == s->deliveryMethod || SEPARATE_DELIVERY_FL == s->deliveryMethod) { - strcpy((char *)rightsIssuer, (char *)((T_DRM_Dcf_Node *)(s->infoStruct))->rightsIssuer); - return DRM_SUCCESS; - } - - return DRM_NOT_SD_METHOD; -} - -/* see svc_drm.h */ -int32_t SVC_drm_getRightsInfo(int32_t session, T_DRM_Rights_Info* rights) -{ - T_DRM_Session_Node* s; - T_DRM_Rights rightsInfo; - int32_t roAmount, id; - - if (session < 0 || NULL == rights) - return DRM_FAILURE; - - s = getSession(session); - if (NULL == s) - return DRM_SESSION_NOT_OPENED; - - if (FORWARD_LOCK == s->deliveryMethod) { - strcpy((char *)rights->roId, "ForwardLock"); - rights->displayRights.indicator = DRM_NO_CONSTRAINT; - rights->playRights.indicator = DRM_NO_CONSTRAINT; - rights->executeRights.indicator = DRM_NO_CONSTRAINT; - rights->printRights.indicator = DRM_NO_CONSTRAINT; - return DRM_SUCCESS; - } - - if (FALSE == drm_readFromUidTxt(s->contentID, &id, GET_ID)) - return DRM_NO_RIGHTS; - - if (FALSE == drm_writeOrReadInfo(id, NULL, &roAmount, GET_ROAMOUNT)) - return DRM_FAILURE; - - if (roAmount < 0) - return DRM_NO_RIGHTS; - - /* some rights has been installed, but now there is no valid rights */ - if (0 == roAmount) { - strcpy((char *)rights->roId, s->contentID); - rights->displayRights.indicator = DRM_NO_PERMISSION; - rights->playRights.indicator = DRM_NO_PERMISSION; - rights->executeRights.indicator = DRM_NO_PERMISSION; - rights->printRights.indicator = DRM_NO_PERMISSION; - return DRM_SUCCESS; - } - - roAmount = 1; - memset(&rightsInfo, 0, sizeof(T_DRM_Rights)); - if (FALSE == drm_writeOrReadInfo(id, &rightsInfo, &roAmount, GET_A_RO)) - return DRM_FAILURE; - - memset(rights, 0, sizeof(T_DRM_Rights_Info)); - drm_getLicenseInfo(&rightsInfo, rights); - return DRM_SUCCESS; -} - -/* see svc_drm.h */ -int32_t SVC_drm_closeSession(int32_t session) -{ - if (session < 0) - return DRM_FAILURE; - - if (NULL == getSession(session)) - return DRM_SESSION_NOT_OPENED; - - removeSession(session); - - return DRM_SUCCESS; -} - -/* see svc_drm.h */ -int32_t SVC_drm_updateRights(uint8_t* contentID, int32_t permission) -{ - int32_t id; - - if (NULL == contentID) - return DRM_FAILURE; - - if (FALSE == drm_readFromUidTxt(contentID, &id, GET_ID)) - return DRM_FAILURE; - - return drm_checkRoAndUpdate(id, permission); -} - -/* see svc_drm.h */ -int32_t SVC_drm_viewAllRights(T_DRM_Rights_Info_Node **ppRightsInfo) -{ - T_DRM_Rights_Info_Node rightsNode; - int32_t maxId, id, roAmount, j; - T_DRM_Rights rights; - - memset(&rights, 0, sizeof(T_DRM_Rights)); - - if (NULL == ppRightsInfo) - return DRM_FAILURE; - - *ppRightsInfo = NULL; - - maxId = drm_getMaxIdFromUidTxt(); - if (-1 == maxId) - return DRM_FAILURE; - - for (id = 1; id <= maxId; id++) { - drm_writeOrReadInfo(id, NULL, &roAmount, GET_ROAMOUNT); - if (roAmount <= 0) /* this means there is not any rights */ - continue; - - for (j = 1; j <= roAmount; j++) { - if (FALSE == drm_writeOrReadInfo(id, &rights, &j, GET_A_RO)) - continue; - - memset(&rightsNode, 0, sizeof(T_DRM_Rights_Info_Node)); - - drm_getLicenseInfo(&rights, &(rightsNode.roInfo)); - - if (FALSE == drm_addRightsNodeToList(ppRightsInfo, &rightsNode)) - continue; - } - } - return DRM_SUCCESS; -} - -/* see svc_drm.h */ -int32_t SVC_drm_freeRightsInfoList(T_DRM_Rights_Info_Node *pRightsHeader) -{ - T_DRM_Rights_Info_Node *pNode, *pTmp; - - if (NULL == pRightsHeader) - return DRM_FAILURE; - - pNode = pRightsHeader; - - while (NULL != pNode) { - pTmp = pNode; - pNode = pNode->next; - free(pTmp); - } - return DRM_SUCCESS; -} - -/* see svc_drm.h */ -int32_t SVC_drm_deleteRights(uint8_t* roId) -{ - int32_t maxId, id, roAmount, j; - T_DRM_Rights rights; - - memset(&rights, 0, sizeof(T_DRM_Rights)); - - if (NULL == roId) - return DRM_FAILURE; - - maxId = drm_getMaxIdFromUidTxt(); - if (-1 == maxId) - return DRM_NO_RIGHTS; - - for (id = 1; id <= maxId; id++) { - drm_writeOrReadInfo(id, NULL, &roAmount, GET_ROAMOUNT); - if (roAmount <= 0) /* this means there is not any rights */ - continue; - - for (j = 1; j <= roAmount; j++) { - if (FALSE == drm_writeOrReadInfo(id, &rights, &j, GET_A_RO)) - continue; - - /* here find the RO which will be deleted */ - if (0 == strcmp((char *)rights.uid, (char *)roId)) { - T_DRM_Rights *pAllRights; - - pAllRights = (T_DRM_Rights *)malloc(roAmount * sizeof(T_DRM_Rights)); - if (NULL == pAllRights) - return DRM_FAILURE; - - drm_writeOrReadInfo(id, pAllRights, &roAmount, GET_ALL_RO); - roAmount--; - if (0 == roAmount) { /* this means it is the last one rights */ - drm_removeIdInfoFile(id); /* delete the id.info file first */ - drm_updateUidTxtWhenDelete(id); /* update uid.txt file */ - free(pAllRights); - return DRM_SUCCESS; - } else /* using the last one rights instead of the deleted one */ - memcpy(pAllRights + (j - 1), pAllRights + roAmount, sizeof(T_DRM_Rights)); - - /* delete the id.info file first */ -// drm_removeIdInfoFile(id); - - if (FALSE == drm_writeOrReadInfo(id, pAllRights, &roAmount, SAVE_ALL_RO)) { - free(pAllRights); - return DRM_FAILURE; - } - - free(pAllRights); - return DRM_SUCCESS; - } - } - } - - return DRM_FAILURE; -} diff --git a/media/libdrm/mobile1/src/objmng/drm_decoder.c b/media/libdrm/mobile1/src/objmng/drm_decoder.c deleted file mode 100644 index 82c7efb..0000000 --- a/media/libdrm/mobile1/src/objmng/drm_decoder.c +++ /dev/null @@ -1,96 +0,0 @@ -/* - * 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. - */ - -#include <objmng/drm_decoder.h> - -/* global variables */ -static const uint8_t * base64_alphabet = (const uint8_t *)"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - -#define SKIP_CRLF(p) while('\r' == *(p) || '\n' == *(p)) \ - p++ - -static int8_t get_alphabet_index(int8_t ch) -{ - uint8_t * tmp; - - if ('=' == ch) - return 64; - - tmp = (uint8_t *)strchr((const char *)base64_alphabet, ch); - if (NULL == tmp) - return -1; - - return (int8_t)(tmp - base64_alphabet); -} - -/* See drm_decoder.h */ -int32_t drm_decodeBase64(uint8_t * dest, int32_t destLen, uint8_t * src, int32_t * srcLen) -{ - int32_t maxDestSize, i, maxGroup; - uint8_t *pDest, *pSrc; - int8_t tpChar; - - if (NULL == src || NULL == srcLen || *srcLen <= 0 || destLen < 0) - return -1; - - maxDestSize = (*srcLen) * 3/4; - if (NULL == dest || 0 == destLen) - return maxDestSize; - - if (destLen < maxDestSize) - maxDestSize = destLen; - maxGroup = maxDestSize/3; - - pDest = dest; /* start to decode src to dest */ - pSrc = src; - for (i = 0; i < maxGroup && *srcLen - (pSrc - src) >= 4; i++) { - SKIP_CRLF(pSrc); - if (pSrc - src >= *srcLen) - break; - tpChar = get_alphabet_index(*pSrc); /* to first byte */ - if (-1 == tpChar || 64 == tpChar) - return -1; - pDest[0] = tpChar << 2; - pSrc++; - SKIP_CRLF(pSrc); - tpChar = get_alphabet_index(*pSrc); - if (-1 == tpChar || 64 == tpChar) - return -1; - pDest[0] |= (tpChar >> 4); - pDest[1] = tpChar << 4; /* to second byte */ - pSrc++; - SKIP_CRLF(pSrc); - tpChar = get_alphabet_index(*pSrc); - if (-1 == tpChar) - return -1; - if (64 == tpChar) /* end */ - return pDest - dest + 1; - pDest[1] |= (tpChar >> 2); - pDest[2] = tpChar << 6; /* to third byte */ - pSrc++; - SKIP_CRLF(pSrc); - tpChar = get_alphabet_index(*pSrc); - if (-1 == tpChar) - return -1; - if (64 == tpChar) /* end */ - return pDest - dest + 2; - pDest[2] |= tpChar; - pDest += 3; - pSrc++; - } - *srcLen = pSrc - src; - return pDest - dest; -} diff --git a/media/libdrm/mobile1/src/objmng/drm_file.c b/media/libdrm/mobile1/src/objmng/drm_file.c deleted file mode 100644 index e6c303e..0000000 --- a/media/libdrm/mobile1/src/objmng/drm_file.c +++ /dev/null @@ -1,694 +0,0 @@ -/* - * 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. - */ - -#include <objmng/drm_file.h> - -#include <unistd.h> -#include <sys/param.h> -#include <sys/stat.h> -#include <stdio.h> -#include <fcntl.h> -#include <dirent.h> -#include <errno.h> -#include <string.h> - -/** - * Fails on zaurus? - #define DEVICE_FILESYSTEM -*/ -#define DEFAULT_TOTAL_SPACE (4L * 1024L * 1024L) /* 4 Meg. */ - -#ifndef DEVICE_FILESYSTEM -/* Store the total space on FS VM can use. */ -static int32_t totalSpace; -/* how many remain space can VM use. */ -static int32_t availableSize; -#endif - -extern char* getStorageRoot(void); - -static char tmpPathBuf1[MAX_FILENAME_LEN]; -static char tmpPathBuf2[MAX_FILENAME_LEN]; - -static int32_t -convertFilename(const uint16_t *strData, int32_t strLength, char *buffer); - -static int calcDirSize(char *path, int len, uint8_t includeSubdirs); - -#ifndef DEVICE_FILESYSTEM -static void initFsVariables(void); -#endif - -/** - * Convert a Java string into a nul terminated ascii string to pass to posix - * @param strData first character of name - * @param strLength number of characters in name - * @param buffer Buffer to store terminated string in (at least MAXPATHLEN) - * @return Length of filename in characters (excl. nul), or -1 on failure. - */ -static int32_t -convertFilename(const uint16_t *strData, int32_t strLength, char *buffer) -{ - int idx; - - if (strLength >= (MAXPATHLEN-1)) - { - Trace("convertFilename '%.*S' too long", strLength, strData); - return -1; - } - - for (idx = 0; idx < strLength; ++idx) - *buffer++ = (char)*strData++; - - *buffer = 0; - return strLength; -} - - -/** - * Perform a stat() call on the given filename. - * Helper for getFileLength and exists - * @param name unicode name - * @param nameLen number of unicode characters in name - * @param sbuf stat buffer - * @return TRUE on success, FALSE on failure - */ -static int32_t -getFileStat(const uint16_t *name, int32_t nameLen, struct stat *sbuf) -{ - Trace("getFileStat: %.*S", nameLen, name); - - if (convertFilename(name, nameLen, tmpPathBuf1) <= 0) - { - Trace("getFileStat: bad filename"); - } - else if (stat(tmpPathBuf1, sbuf) != 0) - { - Trace("getFileStat %s: stat() errno=%d", tmpPathBuf1, errno); - } - else /* Successful */ - { - return TRUE; - } - - return FALSE; -} - -#ifndef DEVICE_FILESYSTEM -/** - * initial the variables like totalSpace, availableSize... - */ -static void initFsVariables(void) -{ - totalSpace = DEFAULT_TOTAL_SPACE; - - availableSize = totalSpace; -} -#endif /* DEVICE_FILESYSTEM */ - -/** - * calculate the size of everything inside path pointed directory - * this function will use path pointed buffer to store some extra info - * so param len is needed. - * @param path the directory path need to calculate - * @param len length of the path buffer, not the path string length - * @param includeSubdirs also calculate all the subdirs in path holds? - * @return the calculated size, DRM_FILE_FAILURE on failure. - */ -static int calcDirSize(char *path, int len, uint8_t includeSubdirs) -{ - struct dirent *ent; - struct stat stat_buf; - - DIR *dir = NULL; - int size = 0; - int exists = -1; - int dirPathLen = strlen(path); - - /* Ensure space for wildcard */ - if((dirPathLen + 2) >= MAXPATHLEN || (dirPathLen + 2) >= len) - { - return DRM_FILE_FAILURE; - } - - if(path[dirPathLen - 1] != '/') - { - path[dirPathLen++] = '/'; - path[dirPathLen] = '\0'; - } - - dir = opendir(path); - if (dir == NULL) - { - return DRM_FILE_FAILURE; - } - - while ((ent = readdir(dir)) != NULL ) - { - if (strcmp(ent->d_name, ".") == 0 || - strcmp(ent->d_name, "..") == 0) - { - continue; - } - - path[dirPathLen] = '\0'; - if ((int)(strlen(ent->d_name) + dirPathLen + 1) < len) - { - strcat(path, ent->d_name); - } - else - { - continue; - } - - exists = stat(path, &stat_buf); - if (exists != -1) - { - /* exclude the storage occupied by directory itself */ - if (stat_buf.st_mode & S_IFDIR) - { - if(includeSubdirs) - { - /* calculate the size recursively */ - int ret; - ret = calcDirSize(path, len, includeSubdirs); - /* ignore failure in subdirs */ - if( DRM_FILE_FAILURE != ret ) - { - size += ret; - } - } - } - else - { - size += stat_buf.st_size; - } - } - } - - closedir(dir); - return size; -} - -/* see drm_file.h */ -int32_t DRM_file_startup(void) -{ - Trace("DRM_file_startup"); - -#ifndef DEVICE_FILESYSTEM - availableSize = -1; - - initFsVariables(); -#endif - - return DRM_FILE_SUCCESS; /* Nothing to do */ -} - -/* see drm_file.h */ -int32_t -DRM_file_listOpen(const uint16_t *prefix, - int32_t prefixLen, - int32_t* session, - int32_t* iteration) -{ - Trace("DRM_file_listOpen: %.*S", prefixLen, prefix); - - if (convertFilename(prefix, prefixLen, tmpPathBuf1) <= 0) - { - Trace("DRM_file_listOpen: bad filename"); - } - else - { - DIR *dir; - - /* find the last /, and store the offset to the leaf prefix in - * *iteration - */ - - char *sep = strrchr(tmpPathBuf1, '/'); - /* Root "/" is a leaf */ - if (sep == NULL || ((sep != NULL) && (sep == tmpPathBuf1))) - { - *iteration = prefixLen; - -#ifdef TRACE_ON - sep = " <empty>"; /* trace will show sep+1 */ -#endif - } - else - { - *iteration = sep - tmpPathBuf1 + 1; - *sep = 0; - } - - dir = opendir(tmpPathBuf1); - - if (dir == NULL) - { - Trace("DRM_file_listOpen: opendir %s: errno=%d", tmpPathBuf1, errno); - } - else - { - Trace("DRM_file_listOpen: dir %s, filter %s", tmpPathBuf1, sep+1); - *session = (int32_t)dir; - return DRM_FILE_SUCCESS; - } - } - - return DRM_FILE_FAILURE; -} - -/* see drm_file.h */ -int32_t -DRM_file_listNextEntry(const uint16_t *prefix, int32_t prefixLen, - uint16_t* entry, int32_t entrySize, - int32_t *session, int32_t* iteration) -{ - struct dirent *ent; - - /* We stored the offset of the leaf part of the prefix (if any) - * in *iteration - */ - const uint16_t* strData = prefix + *iteration; - int32_t strLength = prefixLen - *iteration; - - /* entrySize is bytes for some reason. Convert to ucs chars */ - entrySize /= 2; - - /* Now we want to filter for files which start with the (possibly empty) - * sequence at strData. We have to return fully-qualified filenames, - * which means *iteration characters from prefix, plus the - * leaf name. - */ - - while ( (ent = readdir((DIR *)*session)) != NULL) - { - int len = strlen(ent->d_name); - - if ( (len + *iteration) > entrySize) - { - Trace("DRM_file_listNextEntry: %s too long", ent->d_name); - } - else if (strcmp(ent->d_name, ".") != 0 && - strcmp(ent->d_name, "..") != 0) - { - int idx; - struct stat sinfo; - - /* check against the filter */ - - for (idx = 0; idx < strLength; ++idx) - { - if (ent->d_name[idx] != strData[idx]) - goto next_name; - } - - Trace("DRM_file_listNextEntry: matched %s", ent->d_name); - - /* Now generate the fully-qualified name */ - - for (idx = 0; idx < *iteration; ++idx) - entry[idx] = prefix[idx]; - - for (idx = 0; idx < len; ++idx) - entry[*iteration + idx] = (unsigned char)ent->d_name[idx]; - - /*add "/" at the end of a DIR file entry*/ - if (getFileStat(entry, idx + *iteration, &sinfo)){ - if (S_ISDIR(sinfo.st_mode) && - (idx + 1 + *iteration) < entrySize) { - entry[*iteration + idx] = '/'; - ++idx; - } - } - else - { - Trace("DRM_file_listNextEntry: stat FAILURE on %.*S", - idx + *iteration, entry); - } - Trace("DRM_file_listNextEntry: got %.*S", idx + *iteration, entry); - - return idx + *iteration; - } - - next_name: - Trace("DRM_file_listNextEntry: rejected %s", ent->d_name); - } - - Trace("DRM_file_listNextEntry: end of list"); - return 0; -} - -/* see drm_file.h */ -int32_t -DRM_file_listClose(int32_t session, int32_t iteration) -{ - closedir( (DIR *)session); - return DRM_FILE_SUCCESS; -} - -/* see drm_file.h */ -int32_t -DRM_file_getFileLength(const uint16_t *name, int32_t nameLen) -{ - struct stat sbuf; - - if (getFileStat(name, nameLen, &sbuf)) - { - if (sbuf.st_size >= INT32_MAX) - { - Trace("DRM_file_getFileLength: file too big"); - } - else /* Successful */ - { - Trace("DRM_file_getFileLength: %.*S -> %d", - nameLen, name, (int32_t)sbuf.st_size); - return (int32_t)sbuf.st_size; - } - } - - return DRM_FILE_FAILURE; -} - -/* see drm_file.h */ -int32_t -DRM_file_delete(const uint16_t *name, int32_t nameLen) -{ - Trace("DRM_file_delete: %.*S", nameLen, name); - - if (convertFilename(name, nameLen, tmpPathBuf1) <= 0) - { - Trace("DRM_file_delete: bad filename"); - return DRM_FILE_FAILURE; - } - else - { - struct stat sinfo; - if (stat(tmpPathBuf1, &sinfo) != 0){ - Trace("DRM_file_delete: stat failed, errno=%d", errno); - return DRM_FILE_FAILURE; - } -#ifndef DEVICE_FILESYSTEM - if (S_ISDIR(sinfo.st_mode)){ - /* it's a dir */ - if (rmdir(tmpPathBuf1) != 0){ - Trace("DRM_file_delete: dir remove failed, errno=%d", errno); - return DRM_FILE_FAILURE; - } - else - { - return DRM_FILE_SUCCESS; - } - } -#endif - /* it's a file */ - if (unlink(tmpPathBuf1) != 0) - { - Trace("DRM_file_delete: file remove failed, errno=%d", errno); - return DRM_FILE_FAILURE; - } - else - { -#ifndef DEVICE_FILESYSTEM - availableSize += sinfo.st_size; -#endif - return DRM_FILE_SUCCESS; - } - } - return DRM_FILE_FAILURE; -} - -/* see drm_file.h */ -int32_t -DRM_file_rename(const uint16_t *oldName, int32_t oldNameLen, - const uint16_t *newName, int32_t newNameLen) -{ - Trace("DRM_file_rename %.*S -> %.*S", - oldNameLen, oldName, newNameLen, newName); - if (DRM_file_exists(newName, newNameLen) != DRM_FILE_FAILURE) - { - Trace("DRM_file_rename: filename:%s exist",newName); - return DRM_FILE_FAILURE; - } - - if (convertFilename(oldName, oldNameLen, tmpPathBuf1) <= 0 || - convertFilename(newName, newNameLen, tmpPathBuf2) <= 0) - { - Trace("DRM_file_rename: bad filename"); - } - else if (rename(tmpPathBuf1, tmpPathBuf2) != 0) - { - Trace("DRM_file_rename: failed errno=%d", errno); - } - else /* Success */ - { - return DRM_FILE_SUCCESS; - } - - return DRM_FILE_FAILURE; -} - -/* see drm_file.h */ -int32_t -DRM_file_exists(const uint16_t *name, int32_t nameLen) -{ - struct stat sbuf; - - Trace("DRM_file_exists: %.*S", nameLen, name); - - /*remove trailing "/" separators, except the first "/" standing for root*/ - while ((nameLen > 1) && (name[nameLen -1] == '/')) - --nameLen; - - if (getFileStat(name, nameLen, &sbuf)) - { - Trace("DRM_file_exists: stat returns mode 0x%x", sbuf.st_mode); - - if (S_ISDIR(sbuf.st_mode)) - return DRM_FILE_ISDIR; - if (S_ISREG(sbuf.st_mode)) - return DRM_FILE_ISREG; - } - - return DRM_FILE_FAILURE; -} - -/* see drm_file.h */ -int32_t -DRM_file_open(const uint16_t *name, int32_t nameLen, int32_t mode, - int32_t* handle) -{ - int res; - -#if DRM_FILE_MODE_READ != 1 || DRM_FILE_MODE_WRITE != 2 -#error constants changed -#endif - - /* Convert DRM file modes to posix modes */ - static const int modes[4] = - { 0, - O_RDONLY, - O_WRONLY | O_CREAT, - O_RDWR | O_CREAT - }; - - Trace("DRM_file_open %.*S mode 0x%x", nameLen, name, mode); - - assert((mode & ~(DRM_FILE_MODE_READ|DRM_FILE_MODE_WRITE)) == 0); - - if (convertFilename(name, nameLen, tmpPathBuf1) <= 0) - { - Trace("DRM_file_open: bad filename"); - return DRM_FILE_FAILURE; - } - - if ((res = open(tmpPathBuf1, modes[mode], 0777)) == -1) - { - Trace("DRM_file_open: open failed errno=%d", errno); - return DRM_FILE_FAILURE; - } - - Trace("DRM_file_open: open '%s; returned %d", tmpPathBuf1, res); - *handle = res; - - return DRM_FILE_SUCCESS; -} - -/* see drm_file.h */ -int32_t -DRM_file_read(int32_t handle, uint8_t* dst, int32_t length) -{ - int n; - - assert(length > 0); - - /* TODO: Make dst a void *? */ - - n = read((int)handle, dst, (size_t)length); - if (n > 0) - { - Trace("DRM_file_read handle=%d read %d bytes", handle, n); - return n; - } - else if (n == 0) - { - Trace("DRM_file_read read EOF: handle=%d", handle); - return DRM_FILE_EOF; - } - else - { - Trace("DRM_file_read failed handle=%d, errno=%d", handle, errno); - return DRM_FILE_FAILURE; - } -} - -/* see drm_file.h */ -int32_t -DRM_file_write(int32_t handle, const uint8_t* src, int32_t length) -{ - /* TODO: Make dst a void *? */ - int n; -#ifndef DEVICE_FILESYSTEM - int delta; - off_t prevPos; - struct stat sbuf; - int prevFileSize; -#endif - - assert(length >= 0); - -#ifndef DEVICE_FILESYSTEM - if ( -1 == fstat((int)handle, &sbuf) ) - { - Trace("DRM_file_write: fstat error %d", errno); - return DRM_FILE_FAILURE; - } - prevFileSize = (int)(sbuf.st_size); - prevPos = lseek( (int)handle, 0, SEEK_CUR); - if ( (off_t)-1 == prevPos ) - { - Trace("DRM_file_write: get current pos error %d", errno); - return DRM_FILE_FAILURE; - } - delta = (int)prevPos + length - prevFileSize; - if (delta > availableSize) - { - Trace("DRM_file_write: not enough size!"); - return DRM_FILE_FAILURE; - } -#endif - n = write((int)handle, src, (size_t)length); - if (n < 0) - { - Trace("DRM_file_write failed errno=%d", errno); - return DRM_FILE_FAILURE; - } -#ifndef DEVICE_FILESYSTEM - delta = prevPos + n - prevFileSize; - - if ( delta > 0 ) - { - availableSize -= delta; - } -#endif - Trace("DRM_file_write handle=%d wrote %d/%d bytes", handle, n, length); - - return n; -} - -/* see drm_file.h */ -int32_t DRM_file_close(int32_t handle) -{ - if (close((int)handle) == 0) - { - Trace("DRM_file_close handle=%d success", handle); - return DRM_FILE_SUCCESS; - } - - Trace("DRM_file_close handle=%d failed", handle); - return DRM_FILE_FAILURE; -} - -/* see drm_file.h */ -int32_t -DRM_file_setPosition(int32_t handle, int32_t value) -{ -#ifndef DEVICE_FILESYSTEM - struct stat sbuf; -#endif - off_t newPos; - - if (value < 0) - { - Trace("DRM_file_setPosition: handle=%d negative value (%d)", - handle, value); - return DRM_FILE_FAILURE; - } - -#ifndef DEVICE_FILESYSTEM - if ( fstat((int)handle, &sbuf) == -1 ) - { - Trace("DRM_file_setPosition: fstat fail errno=%d", errno); - return DRM_FILE_FAILURE; - } - - if ( ((off_t)value > sbuf.st_size) && - (availableSize < (value - (int)(sbuf.st_size))) ) - { - Trace("DRM_file_setPosition: not enough space"); - return DRM_FILE_FAILURE; - } -#endif - - newPos = lseek( (int)handle, (off_t)value, SEEK_SET); - if ( newPos == (off_t)-1 ) - { - Trace("DRM_file_setPosition: seek failed: errno=%d", errno); - } - else - { -#ifndef DEVICE_FILESYSTEM - if ( newPos > sbuf.st_size ) - { - availableSize -= (int)(newPos - sbuf.st_size); - } -#endif - return DRM_FILE_SUCCESS; - } - - return DRM_FILE_FAILURE; -} - -/* see drm_file.h */ -int32_t -DRM_file_mkdir(const uint16_t* name, int32_t nameChars) -{ - Trace("DRM_file_mkdir started!.."); - - if (convertFilename(name, nameChars, tmpPathBuf1) <= 0) - { - Trace("DRM_file_mkdir: bad filename"); - return DRM_FILE_FAILURE; - } - - if (mkdir(tmpPathBuf1,0777) != 0) - { - Trace("DRM_file_mkdir failed!errno=%d",errno); - return DRM_FILE_FAILURE; - } - - return DRM_FILE_SUCCESS; -} diff --git a/media/libdrm/mobile1/src/objmng/drm_i18n.c b/media/libdrm/mobile1/src/objmng/drm_i18n.c deleted file mode 100644 index b1118a9..0000000 --- a/media/libdrm/mobile1/src/objmng/drm_i18n.c +++ /dev/null @@ -1,444 +0,0 @@ -/* - * 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. - */ - -#include <objmng/drm_i18n.h> - -#define IS_GB2312_HIGH_BYTE(c) ((c) >= 0xA1 && (c) <= 0xF7) -#define IS_GB2312_LOW_BYTE(c) ((c) >= 0xA1 && (c) <= 0xFE) -#define IS_GBK_HIGH_BYTE(c) ((c) >= 0x81 && (c) <= 0xFE) -#define IS_GBK_LOW_BYTE(c) ((c) >= 0x40 && (c) <= 0xFE && (c) != 0x7F) -#define IS_BIG5_HIGH_BYTE(c) ((c) >= 0xA1 && (c) <= 0xF9) -#define IS_BIG5_LOW_BYTE(c) (((c) >= 0x40 && (c) <= 0x7E) \ - || ((c) >= 0xA1 && (c) <= 0xFE)) -#define IS_ASCII(c) ((c) <= 127) - -#define INVALID_UNICODE 0xFFFD - -#define I18N_LATIN1_SUPPORT -#define I18N_UTF8_UTF16_SUPPORT - - -/** - * Simply convert ISO 8859-1 (latin1) to unicode - */ -static int32_t latin1ToWcs(const uint8_t *mbs, int32_t mbsLen, - uint16_t *wcsBuf, int32_t bufSizeInWideChar, - int32_t *bytesConsumed); - -/** - * Convert one unicode char to ISO 8859-1 (latin1) byte - */ -static int32_t wcToLatin1(uint16_t wc, uint8_t * mbs, int32_t bufSize); - -/** - * Convert UTF-8 to unicode - */ -static int32_t utf8ToWcs(const uint8_t *mbs, int32_t mbsLen, - uint16_t *wcsBuf, int32_t bufSizeInWideChar, - int32_t *bytesConsumed); - -/** - * Convert one unicode char to UTF-8 bytes - */ -static int32_t wcToUtf8(uint16_t wc, uint8_t * mbs, int32_t bufSize); - -/** - * Convert UTF-16 BE to unicode - */ -static int32_t utf16beToWcs(const uint8_t *mbs, int32_t mbsLen, - uint16_t *wcsBuf, int32_t bufSizeInWideChar, - int32_t *bytesConsumed); - -/** - * Convert one unicode char to UTF-16 BE bytes - */ -static int32_t wcToUtf16be(uint16_t wc, uint8_t * mbs, int32_t bufSize); - -/** - * Convert UTF-16 LE to unicode - */ -static int32_t utf16leToWcs(const uint8_t *mbs, int32_t mbsLen, - uint16_t *wcsBuf, int32_t bufSizeInWideChar, - int32_t *bytesConsumed); - -/** - * Convert one unicode char to UTF-16 LE bytes - */ -static int32_t wcToUtf16le(uint16_t wc, uint8_t * mbs, int32_t bufSize); - -/* - * see drm_i18n.h - */ -int32_t DRM_i18n_mbsToWcs(DRM_Charset_t charset, - const uint8_t *mbs, int32_t mbsLen, - uint16_t *wcsBuf, int32_t bufSizeInWideChar, - int32_t *bytesConsumed) -{ - switch (charset) - { -#ifdef I18N_GB2312_SUPPORT - case DRM_CHARSET_GB2312: - return gb2312ToWcs(mbs, mbsLen, wcsBuf, bufSizeInWideChar, bytesConsumed); -#endif -#ifdef I18N_GBK_SUPPORT - case DRM_CHARSET_GBK: - return gbkToWcs(mbs, mbsLen, wcsBuf, bufSizeInWideChar, bytesConsumed); -#endif -#ifdef I18N_BIG5_SUPPORT - case DRM_CHARSET_BIG5: - return big5ToWcs(mbs, mbsLen, wcsBuf, bufSizeInWideChar, bytesConsumed); -#endif -#ifdef I18N_LATIN1_SUPPORT - case DRM_CHARSET_LATIN1: - return latin1ToWcs(mbs, mbsLen, wcsBuf, bufSizeInWideChar, bytesConsumed); -#endif -#ifdef I18N_ISO8859X_SUPPORT - case DRM_CHARSET_LATIN2: - case DRM_CHARSET_LATIN3: - case DRM_CHARSET_LATIN4: - case DRM_CHARSET_CYRILLIC: - case DRM_CHARSET_ARABIC: - case DRM_CHARSET_GREEK: - case DRM_CHARSET_HEBREW: - case DRM_CHARSET_LATIN5: - case DRM_CHARSET_LATIN6: - case DRM_CHARSET_THAI: - case DRM_CHARSET_LATIN7: - case DRM_CHARSET_LATIN8: - case DRM_CHARSET_LATIN9: - case DRM_CHARSET_LATIN10: - return iso8859xToWcs(charset, mbs, mbsLen, wcsBuf, bufSizeInWideChar, bytesConsumed); -#endif -#ifdef I18N_UTF8_UTF16_SUPPORT - case DRM_CHARSET_UTF8: - return utf8ToWcs(mbs, mbsLen, wcsBuf, bufSizeInWideChar, bytesConsumed); - case DRM_CHARSET_UTF16BE: - return utf16beToWcs(mbs, mbsLen, wcsBuf, bufSizeInWideChar, bytesConsumed); - case DRM_CHARSET_UTF16LE: - return utf16leToWcs(mbs, mbsLen, wcsBuf, bufSizeInWideChar, bytesConsumed); -#endif - default: - return -1; - } -} - -/* - * see drm_i18n.h - */ -int32_t DRM_i18n_wcsToMbs(DRM_Charset_t charset, - const uint16_t *wcs, int32_t wcsLen, - uint8_t *mbsBuf, int32_t bufSizeInByte) -{ - int32_t (* wcToMbFunc)(uint16_t, uint8_t *, int32_t); - int32_t charIndex = 0; - int32_t numMultiBytes = 0; - - switch (charset) - { -#ifdef I18N_LATIN1_SUPPORT - case DRM_CHARSET_LATIN1: - wcToMbFunc = wcToLatin1; - break; -#endif -#ifdef I18N_UTF8_UTF16_SUPPORT - case DRM_CHARSET_UTF8: - wcToMbFunc = wcToUtf8; - break; - case DRM_CHARSET_UTF16BE: - wcToMbFunc = wcToUtf16be; - break; - case DRM_CHARSET_UTF16LE: - wcToMbFunc = wcToUtf16le; - break; -#endif -#ifdef I18N_ISO8859X_SUPPORT - case DRM_CHARSET_LATIN2: - case DRM_CHARSET_LATIN3: - case DRM_CHARSET_LATIN4: - case DRM_CHARSET_CYRILLIC: - case DRM_CHARSET_ARABIC: - case DRM_CHARSET_GREEK: - case DRM_CHARSET_HEBREW: - case DRM_CHARSET_LATIN5: - case DRM_CHARSET_LATIN6: - case DRM_CHARSET_THAI: - case DRM_CHARSET_LATIN7: - case DRM_CHARSET_LATIN8: - case DRM_CHARSET_LATIN9: - case DRM_CHARSET_LATIN10: - return wcsToIso8859x(charset, wcs, wcsLen, mbsBuf, bufSizeInByte); -#endif - default: - return -1; - } - - if (mbsBuf) { - while (numMultiBytes < bufSizeInByte && charIndex < wcsLen) { - /* TODO: handle surrogate pair values here */ - int32_t mbLen = wcToMbFunc(wcs[charIndex], - &mbsBuf[numMultiBytes], bufSizeInByte - numMultiBytes); - - if (numMultiBytes + mbLen > bufSizeInByte) { - /* Insufficient buffer. Don't update numMultiBytes */ - break; - } - charIndex++; - numMultiBytes += mbLen; - } - } else { - while (charIndex < wcsLen) { - /* TODO: handle surrogate pair values here */ - numMultiBytes += wcToMbFunc(wcs[charIndex], NULL, 0); - charIndex++; - } - } - - return numMultiBytes; -} - - -#ifdef I18N_LATIN1_SUPPORT - -int32_t latin1ToWcs(const uint8_t *mbs, int32_t mbsLen, - uint16_t *wcsBuf, int32_t bufSizeInWideChar, - int32_t *bytesConsumed) -{ - int32_t charsToConvert; - int32_t len; - - if (wcsBuf == NULL) { - return mbsLen; - } - - len = charsToConvert = mbsLen > bufSizeInWideChar ? bufSizeInWideChar : mbsLen; - if (len < 0) - return 0; - while (len--) { - *wcsBuf++ = *mbs++; - } - - if (bytesConsumed) - *bytesConsumed = charsToConvert; - - return charsToConvert; -} - -int32_t wcToLatin1(uint16_t wc, uint8_t * mbs, int32_t bufSize) -{ - uint8_t ch; - - if (wc < 0x100) { - ch = (uint8_t)(wc & 0xff); - } else { - ch = '?'; - } - if (mbs && bufSize > 0) - *mbs = ch; - return 1; -} - -#endif /* I18N_LATIN1_SUPPORT */ - -#ifdef I18N_UTF8_UTF16_SUPPORT - -int32_t utf8ToWcs(const uint8_t *mbs, int32_t mbsLen, - uint16_t *wcsBuf, int32_t bufSizeInWideChar, - int32_t *bytesConsumed) -{ - int32_t charsConverted = 0; - int32_t i = 0; - int32_t wideChar; - - if (wcsBuf == NULL) { - /* No conversion but we're still going to calculate bytesConsumed */ - bufSizeInWideChar = mbsLen * 2; - } - - while((i < mbsLen) && (charsConverted < bufSizeInWideChar)) { - uint8_t ch = mbs[i]; - uint8_t ch2, ch3, ch4; - - wideChar = -1; - - if(IS_ASCII(ch)) { - wideChar = ch; - i++; - } else if ((ch & 0xc0) == 0xc0) { - int utfStart = i; - if ((ch & 0xe0) == 0xc0) { - /* 2 byte sequence */ - if (i + 1 < mbsLen && ((ch2 = mbs[i + 1]) & 0xc0) == 0x80) { - wideChar = (uint16_t)(((ch & 0x1F) << 6) | (ch2 & 0x3F)); - i += 2; - } else { - /* skip incomplete sequence */ - i++; - } - } else if ((ch & 0xf0) == 0xe0) { - /* 3 byte sequence */ - if (i + 2 < mbsLen - && ((ch2 = mbs[i + 1]) & 0xc0) == 0x80 - && ((ch3 = mbs[i + 2]) & 0xc0) == 0x80) { - wideChar = (uint16_t)(((ch & 0x0F) << 12) | ((ch2 & 0x3F) << 6) | (ch3 & 0x3F)); - i += 3; - } else { - /* skip incomplete sequence (up to 2 bytes) */ - i++; - if (i < mbsLen && (mbs[i] & 0xc0) == 0x80) - i++; - } - } else if ((ch & 0xf8) == 0xf0) { - /* 4 byte sequence */ - if (i + 3 < mbsLen - && ((ch2 = mbs[i + 1]) & 0xc0) == 0x80 - && ((ch3 = mbs[i + 2]) & 0xc0) == 0x80 - && ((ch4 = mbs[i + 3]) & 0xc0) == 0x80) { - /* FIXME: we do NOT support U+10000 - U+10FFFF for now. - * leave it as 0xFFFD. */ - wideChar = INVALID_UNICODE; - i += 4; - } else { - /* skip incomplete sequence (up to 3 bytes) */ - i++; - if (i < mbsLen && (mbs[i] & 0xc0) == 0x80) { - i++; - if (i < mbsLen && (mbs[i] & 0xc0) == 0x80) { - i++; - } - } - } - } else { - /* invalid */ - i++; - } - if (i >= mbsLen && wideChar == -1) { - /* Possible incomplete UTF-8 sequence at the end of mbs. - * Leave it to the caller. - */ - i = utfStart; - break; - } - } else { - /* invalid */ - i++; - } - if(wcsBuf) { - if (wideChar == -1) - wideChar = INVALID_UNICODE; - wcsBuf[charsConverted] = (uint16_t)wideChar; - } - charsConverted++; - } - - if (bytesConsumed) - *bytesConsumed = i; - - return charsConverted; -} - -int32_t wcToUtf8(uint16_t wc, uint8_t * mbs, int32_t bufSize) -{ - if (wc <= 0x7f) { - if (mbs && (bufSize >= 1)) { - *mbs = (uint8_t)wc; - } - return 1; - } else if (wc <= 0x7ff) { - if (mbs && (bufSize >= 2)) { - *mbs++ = (uint8_t)((wc >> 6) | 0xc0); - *mbs = (uint8_t)((wc & 0x3f) | 0x80); - } - return 2; - } else { - if (mbs && (bufSize >= 3)) { - *mbs++ = (uint8_t)((wc >> 12) | 0xe0); - *mbs++ = (uint8_t)(((wc >> 6) & 0x3f)| 0x80); - *mbs = (uint8_t)((wc & 0x3f) | 0x80); - } - return 3; - } -} - -int32_t utf16beToWcs(const uint8_t *mbs, int32_t mbsLen, - uint16_t *wcsBuf, int32_t bufSizeInWideChar, - int32_t *bytesConsumed) -{ - int32_t charsToConvert; - int32_t len; - - if (wcsBuf == NULL) { - return mbsLen / 2; - } - - len = charsToConvert = (mbsLen / 2) > bufSizeInWideChar ? bufSizeInWideChar : (mbsLen / 2); - while (len--) { - /* TODO: handle surrogate pair values */ - *wcsBuf++ = (uint16_t)((*mbs << 8) | *(mbs + 1)); - mbs += 2; - } - - if (bytesConsumed) - *bytesConsumed = charsToConvert * 2; - - return charsToConvert; -} - -int32_t wcToUtf16be(uint16_t wc, uint8_t * mbs, int32_t bufSize) -{ - if (mbs && bufSize >= 2) { - /* TODO: handle surrogate pair values */ - *mbs = (uint8_t)(wc >> 8); - *(mbs + 1) = (uint8_t)(wc & 0xff); - } - return 2; -} - -int32_t utf16leToWcs(const uint8_t *mbs, int32_t mbsLen, - uint16_t *wcsBuf, int32_t bufSizeInWideChar, - int32_t *bytesConsumed) -{ - int32_t charsToConvert; - int32_t len; - - if (wcsBuf == NULL) { - return mbsLen / 2; - } - - len = charsToConvert = (mbsLen / 2) > bufSizeInWideChar ? bufSizeInWideChar : (mbsLen / 2); - while (len--) { - /* TODO: handle surrogate pair values */ - *wcsBuf++ = (uint16_t)(*mbs | (*(mbs + 1) << 8)); - mbs += 2; - } - - if (bytesConsumed) - *bytesConsumed = charsToConvert * 2; - - return charsToConvert; -} - -int32_t wcToUtf16le(uint16_t wc, uint8_t * mbs, int32_t bufSize) -{ - if (mbs && bufSize >= 2) { - /* TODO: handle surrogate pair values */ - *mbs = (uint8_t)(wc & 0xff); - *(mbs + 1) = (uint8_t)(wc >> 8); - } - return 2; -} - -#endif /* I18N_UTF8_UTF16_SUPPORT */ - diff --git a/media/libdrm/mobile1/src/objmng/drm_rights_manager.c b/media/libdrm/mobile1/src/objmng/drm_rights_manager.c deleted file mode 100644 index df22327..0000000 --- a/media/libdrm/mobile1/src/objmng/drm_rights_manager.c +++ /dev/null @@ -1,688 +0,0 @@ -/* - * 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. - */ - -#include <drm_rights_manager.h> -#include <drm_inner.h> -#include <drm_file.h> -#include <drm_i18n.h> - -static int32_t drm_getString(uint8_t* string, int32_t len, int32_t handle) -{ - int32_t i; - - for (i = 0; i < len; i++) { - if (DRM_FILE_FAILURE == DRM_file_read(handle, &string[i], 1)) - return FALSE; - if (string[i] == '\n') { - string[i + 1] = '\0'; - break; - } - } - return TRUE; -} - -static int32_t drm_putString(uint8_t* string, int32_t handle) -{ - int32_t i = 0; - - for (i = 0;; i++) { - if (string[i] == '\0') - break; - if (DRM_FILE_FAILURE == DRM_file_write(handle, &string[i], 1)) - return FALSE; - } - return TRUE; -} - -static int32_t drm_writeToUidTxt(uint8_t* Uid, int32_t* id) -{ - int32_t length; - int32_t i; - uint8_t idStr[8]; - int32_t idMax; - uint8_t(*uidStr)[256]; - uint16_t nameUcs2[MAX_FILENAME_LEN]; - int32_t nameLen; - int32_t bytesConsumed; - int32_t handle; - int32_t fileRes; - - if (*id < 1) - return FALSE; - - /* convert in ucs2 */ - nameLen = strlen(DRM_UID_FILE_PATH); - nameLen = DRM_i18n_mbsToWcs(DRM_CHARSET_UTF8, - (uint8_t *)DRM_UID_FILE_PATH, - nameLen, - nameUcs2, - MAX_FILENAME_LEN, - &bytesConsumed); - fileRes = DRM_file_open(nameUcs2, - nameLen, - DRM_FILE_MODE_READ, - &handle); - if (DRM_FILE_SUCCESS != fileRes) { - DRM_file_open(nameUcs2, - nameLen, - DRM_FILE_MODE_WRITE, - &handle); - DRM_file_write(handle, (uint8_t *)"0\n", 2); - DRM_file_close(handle); - DRM_file_open(nameUcs2, - nameLen, - DRM_FILE_MODE_READ, - &handle); - } - - if (!drm_getString(idStr, 8, handle)) { - DRM_file_close(handle); - return FALSE; - } - idMax = atoi((char *)idStr); - - if (idMax < *id) - uidStr = malloc((idMax + 1) * 256); - else - uidStr = malloc(idMax * 256); - - for (i = 0; i < idMax; i++) { - if (!drm_getString(uidStr[i], 256, handle)) { - DRM_file_close(handle); - free(uidStr); - return FALSE; - } - } - length = strlen((char *)Uid); - strcpy((char *)uidStr[*id - 1], (char *)Uid); - uidStr[*id - 1][length] = '\n'; - uidStr[*id - 1][length + 1] = '\0'; - if (idMax < (*id)) - idMax++; - DRM_file_close(handle); - - DRM_file_open(nameUcs2, - nameLen, - DRM_FILE_MODE_WRITE, - &handle); - sprintf((char *)idStr, "%d", idMax); - - if (!drm_putString(idStr, handle)) { - DRM_file_close(handle); - free(uidStr); - return FALSE; - } - if (DRM_FILE_FAILURE == DRM_file_write(handle, (uint8_t *)"\n", 1)) { - DRM_file_close(handle); - free(uidStr); - return FALSE; - } - for (i = 0; i < idMax; i++) { - if (!drm_putString(uidStr[i], handle)) { - DRM_file_close(handle); - free(uidStr); - return FALSE; - } - } - if (DRM_FILE_FAILURE == DRM_file_write(handle, (uint8_t *)"\n", 1)) { - DRM_file_close(handle); - free(uidStr); - return FALSE; - } - DRM_file_close(handle); - free(uidStr); - return TRUE; -} - -/* See objmng_files.h */ -int32_t drm_readFromUidTxt(uint8_t* Uid, int32_t* id, int32_t option) -{ - int32_t i; - uint8_t p[256] = { 0 }; - uint8_t idStr[8]; - int32_t idMax = 0; - uint16_t nameUcs2[MAX_FILENAME_LEN]; - int32_t nameLen = 0; - int32_t bytesConsumed; - int32_t handle; - int32_t fileRes; - - if (NULL == id || NULL == Uid) - return FALSE; - - DRM_file_startup(); - - /* convert in ucs2 */ - nameLen = strlen(DRM_UID_FILE_PATH); - nameLen = DRM_i18n_mbsToWcs(DRM_CHARSET_UTF8, - (uint8_t *)DRM_UID_FILE_PATH, - nameLen, - nameUcs2, - MAX_FILENAME_LEN, - &bytesConsumed); - fileRes = DRM_file_open(nameUcs2, - nameLen, - DRM_FILE_MODE_READ, - &handle); - if (DRM_FILE_SUCCESS != fileRes) { - DRM_file_open(nameUcs2, - nameLen, - DRM_FILE_MODE_WRITE, - &handle); - DRM_file_write(handle, (uint8_t *)"0\n", 2); - DRM_file_close(handle); - DRM_file_open(nameUcs2, - nameLen, - DRM_FILE_MODE_READ, - &handle); - } - - if (!drm_getString(idStr, 8, handle)) { - DRM_file_close(handle); - return FALSE; - } - idMax = atoi((char *)idStr); - - if (option == GET_UID) { - if (*id < 1 || *id > idMax) { - DRM_file_close(handle); - return FALSE; - } - for (i = 1; i <= *id; i++) { - if (!drm_getString(Uid, 256, handle)) { - DRM_file_close(handle); - return FALSE; - } - } - DRM_file_close(handle); - return TRUE; - } - if (option == GET_ID) { - *id = -1; - for (i = 1; i <= idMax; i++) { - if (!drm_getString(p, 256, handle)) { - DRM_file_close(handle); - return FALSE; - } - if (strstr((char *)p, (char *)Uid) != NULL - && strlen((char *)p) == strlen((char *)Uid) + 1) { - *id = i; - DRM_file_close(handle); - return TRUE; - } - if ((*id == -1) && (strlen((char *)p) < 3)) - *id = i; - } - if (*id != -1) { - DRM_file_close(handle); - return FALSE; - } - *id = idMax + 1; - DRM_file_close(handle); - return FALSE; - } - DRM_file_close(handle); - return FALSE; -} - -static int32_t drm_acquireId(uint8_t* uid, int32_t* id) -{ - if (TRUE == drm_readFromUidTxt(uid, id, GET_ID)) - return TRUE; - - drm_writeToUidTxt(uid, id); - - return FALSE; /* The Uid is not exit, then return FALSE indicate it */ -} - -int32_t drm_writeOrReadInfo(int32_t id, T_DRM_Rights* Ro, int32_t* RoAmount, int32_t option) -{ - uint8_t fullname[MAX_FILENAME_LEN] = {0}; - int32_t tmpRoAmount; - uint16_t nameUcs2[MAX_FILENAME_LEN]; - int32_t nameLen = 0; - int32_t bytesConsumed; - int32_t handle; - int32_t fileRes; - - sprintf((char *)fullname, ANDROID_DRM_CORE_PATH"%d"EXTENSION_NAME_INFO, id); - - /* convert in ucs2 */ - nameLen = strlen((char *)fullname); - nameLen = DRM_i18n_mbsToWcs(DRM_CHARSET_UTF8, - fullname, - nameLen, - nameUcs2, - MAX_FILENAME_LEN, - &bytesConsumed); - fileRes = DRM_file_open(nameUcs2, - nameLen, - DRM_FILE_MODE_READ, - &handle); - if (DRM_FILE_SUCCESS != fileRes) { - if (GET_ALL_RO == option || GET_A_RO == option) - return FALSE; - - if (GET_ROAMOUNT == option) { - *RoAmount = -1; - return TRUE; - } - } - - DRM_file_close(handle); - DRM_file_open(nameUcs2, - nameLen, - DRM_FILE_MODE_READ | DRM_FILE_MODE_WRITE, - &handle); - - switch(option) { - case GET_ROAMOUNT: - if (DRM_FILE_FAILURE == DRM_file_read(handle, (uint8_t*)RoAmount, sizeof(int32_t))) { - DRM_file_close(handle); - return FALSE; - } - break; - case GET_ALL_RO: - DRM_file_setPosition(handle, sizeof(int32_t)); - - if (DRM_FILE_FAILURE == DRM_file_read(handle, (uint8_t*)Ro, (*RoAmount) * sizeof(T_DRM_Rights))) { - DRM_file_close(handle); - return FALSE; - } - break; - case SAVE_ALL_RO: - if (DRM_FILE_FAILURE == DRM_file_write(handle, (uint8_t*)RoAmount, sizeof(int32_t))) { - DRM_file_close(handle); - return FALSE; - } - - if (NULL != Ro && *RoAmount >= 1) { - if (DRM_FILE_FAILURE == DRM_file_write(handle, (uint8_t*) Ro, (*RoAmount) * sizeof(T_DRM_Rights))) { - DRM_file_close(handle); - return FALSE; - } - } - break; - case GET_A_RO: - DRM_file_setPosition(handle, sizeof(int32_t) + (*RoAmount - 1) * sizeof(T_DRM_Rights)); - - if (DRM_FILE_FAILURE == DRM_file_read(handle, (uint8_t*)Ro, sizeof(T_DRM_Rights))) { - DRM_file_close(handle); - return FALSE; - } - break; - case SAVE_A_RO: - DRM_file_setPosition(handle, sizeof(int32_t) + (*RoAmount - 1) * sizeof(T_DRM_Rights)); - - if (DRM_FILE_FAILURE == DRM_file_write(handle, (uint8_t*)Ro, sizeof(T_DRM_Rights))) { - DRM_file_close(handle); - return FALSE; - } - - DRM_file_setPosition(handle, 0); - if (DRM_FILE_FAILURE == DRM_file_read(handle, (uint8_t*)&tmpRoAmount, sizeof(int32_t))) { - DRM_file_close(handle); - return FALSE; - } - if (tmpRoAmount < *RoAmount) { - DRM_file_setPosition(handle, 0); - DRM_file_write(handle, (uint8_t*)RoAmount, sizeof(int32_t)); - } - break; - default: - DRM_file_close(handle); - return FALSE; - } - - DRM_file_close(handle); - return TRUE; -} - -int32_t drm_appendRightsInfo(T_DRM_Rights* rights) -{ - int32_t id; - int32_t roAmount; - - if (NULL == rights) - return FALSE; - - drm_acquireId(rights->uid, &id); - - if (FALSE == drm_writeOrReadInfo(id, NULL, &roAmount, GET_ROAMOUNT)) - return FALSE; - - if (-1 == roAmount) - roAmount = 0; - - /* The RO amount increase */ - roAmount++; - - /* Save the rights information */ - if (FALSE == drm_writeOrReadInfo(id, rights, &roAmount, SAVE_A_RO)) - return FALSE; - - return TRUE; -} - -int32_t drm_getMaxIdFromUidTxt() -{ - uint8_t idStr[8]; - int32_t idMax = 0; - uint16_t nameUcs2[MAX_FILENAME_LEN] = {0}; - int32_t nameLen = 0; - int32_t bytesConsumed; - int32_t handle; - int32_t fileRes; - - /* convert in ucs2 */ - nameLen = strlen(DRM_UID_FILE_PATH); - nameLen = DRM_i18n_mbsToWcs(DRM_CHARSET_UTF8, - (uint8_t *)DRM_UID_FILE_PATH, - nameLen, - nameUcs2, - MAX_FILENAME_LEN, - &bytesConsumed); - fileRes = DRM_file_open(nameUcs2, - nameLen, - DRM_FILE_MODE_READ, - &handle); - - /* this means the uid.txt file is not exist, so there is not any DRM object */ - if (DRM_FILE_SUCCESS != fileRes) - return 0; - - if (!drm_getString(idStr, 8, handle)) { - DRM_file_close(handle); - return -1; - } - DRM_file_close(handle); - - idMax = atoi((char *)idStr); - return idMax; -} - -int32_t drm_removeIdInfoFile(int32_t id) -{ - uint8_t filename[MAX_FILENAME_LEN] = {0}; - uint16_t nameUcs2[MAX_FILENAME_LEN]; - int32_t nameLen = 0; - int32_t bytesConsumed; - - if (id <= 0) - return FALSE; - - sprintf((char *)filename, ANDROID_DRM_CORE_PATH"%d"EXTENSION_NAME_INFO, id); - - /* convert in ucs2 */ - nameLen = strlen((char *)filename); - nameLen = DRM_i18n_mbsToWcs(DRM_CHARSET_UTF8, - filename, - nameLen, - nameUcs2, - MAX_FILENAME_LEN, - &bytesConsumed); - if (DRM_FILE_SUCCESS != DRM_file_delete(nameUcs2, nameLen)) - return FALSE; - - return TRUE; -} - -int32_t drm_updateUidTxtWhenDelete(int32_t id) -{ - uint16_t nameUcs2[MAX_FILENAME_LEN]; - int32_t nameLen = 0; - int32_t bytesConsumed; - int32_t handle; - int32_t fileRes; - int32_t bufferLen; - uint8_t *buffer; - uint8_t idStr[8]; - int32_t idMax; - - if (id <= 0) - return FALSE; - - nameLen = strlen(DRM_UID_FILE_PATH); - nameLen = DRM_i18n_mbsToWcs(DRM_CHARSET_UTF8, - (uint8_t *)DRM_UID_FILE_PATH, - nameLen, - nameUcs2, - MAX_FILENAME_LEN, - &bytesConsumed); - bufferLen = DRM_file_getFileLength(nameUcs2, nameLen); - if (bufferLen <= 0) - return FALSE; - - buffer = (uint8_t *)malloc(bufferLen); - if (NULL == buffer) - return FALSE; - - fileRes = DRM_file_open(nameUcs2, - nameLen, - DRM_FILE_MODE_READ, - &handle); - if (DRM_FILE_SUCCESS != fileRes) { - free(buffer); - return FALSE; - } - - drm_getString(idStr, 8, handle); - idMax = atoi((char *)idStr); - - bufferLen -= strlen((char *)idStr); - fileRes = DRM_file_read(handle, buffer, bufferLen); - buffer[bufferLen] = '\0'; - DRM_file_close(handle); - - /* handle this buffer */ - { - uint8_t *pStart, *pEnd; - int32_t i, movLen; - - pStart = buffer; - pEnd = pStart; - for (i = 0; i < id; i++) { - if (pEnd != pStart) - pStart = ++pEnd; - while ('\n' != *pEnd) - pEnd++; - if (pStart == pEnd) - pStart--; - } - movLen = bufferLen - (pEnd - buffer); - memmove(pStart, pEnd, movLen); - bufferLen -= (pEnd - pStart); - } - - if (DRM_FILE_SUCCESS != DRM_file_delete(nameUcs2, nameLen)) { - free(buffer); - return FALSE; - } - - fileRes = DRM_file_open(nameUcs2, - nameLen, - DRM_FILE_MODE_WRITE, - &handle); - if (DRM_FILE_SUCCESS != fileRes) { - free(buffer); - return FALSE; - } - sprintf((char *)idStr, "%d", idMax); - drm_putString(idStr, handle); - DRM_file_write(handle, (uint8_t*)"\n", 1); - DRM_file_write(handle, buffer, bufferLen); - free(buffer); - DRM_file_close(handle); - return TRUE; -} - -int32_t drm_getKey(uint8_t* uid, uint8_t* KeyValue) -{ - T_DRM_Rights ro; - int32_t id, roAmount; - - if (NULL == uid || NULL == KeyValue) - return FALSE; - - if (FALSE == drm_readFromUidTxt(uid, &id, GET_ID)) - return FALSE; - - if (FALSE == drm_writeOrReadInfo(id, NULL, &roAmount, GET_ROAMOUNT)) - return FALSE; - - if (roAmount <= 0) - return FALSE; - - memset(&ro, 0, sizeof(T_DRM_Rights)); - roAmount = 1; - if (FALSE == drm_writeOrReadInfo(id, &ro, &roAmount, GET_A_RO)) - return FALSE; - - memcpy(KeyValue, ro.KeyValue, DRM_KEY_LEN); - return TRUE; -} - -void drm_discardPaddingByte(uint8_t *decryptedBuf, int32_t *decryptedBufLen) -{ - int32_t tmpLen = *decryptedBufLen; - int32_t i; - - if (NULL == decryptedBuf || *decryptedBufLen < 0) - return; - - /* Check whether the last several bytes are padding or not */ - for (i = 1; i < decryptedBuf[tmpLen - 1]; i++) { - if (decryptedBuf[tmpLen - 1 - i] != decryptedBuf[tmpLen - 1]) - break; /* Not the padding bytes */ - } - if (i == decryptedBuf[tmpLen - 1]) /* They are padding bytes */ - *decryptedBufLen = tmpLen - i; - return; -} - -int32_t drm_aesDecBuffer(uint8_t * Buffer, int32_t * BufferLen, AES_KEY *key) -{ - uint8_t dbuf[3 * DRM_ONE_AES_BLOCK_LEN], buf[DRM_ONE_AES_BLOCK_LEN]; - uint64_t i, len, wlen = DRM_ONE_AES_BLOCK_LEN, curLen, restLen; - uint8_t *pTarget, *pTargetHead; - - pTargetHead = Buffer; - pTarget = Buffer; - curLen = 0; - restLen = *BufferLen; - - if (restLen > 2 * DRM_ONE_AES_BLOCK_LEN) { - len = 2 * DRM_ONE_AES_BLOCK_LEN; - } else { - len = restLen; - } - memcpy(dbuf, Buffer, (size_t)len); - restLen -= len; - Buffer += len; - - if (len < 2 * DRM_ONE_AES_BLOCK_LEN) { /* The original file is less than one block in length */ - len -= DRM_ONE_AES_BLOCK_LEN; - /* Decrypt from position len to position len + DRM_ONE_AES_BLOCK_LEN */ - AES_decrypt((dbuf + len), (dbuf + len), key); - - /* Undo the CBC chaining */ - for (i = 0; i < len; ++i) - dbuf[i] ^= dbuf[i + DRM_ONE_AES_BLOCK_LEN]; - - /* Output the decrypted bytes */ - memcpy(pTarget, dbuf, (size_t)len); - pTarget += len; - } else { - uint8_t *b1 = dbuf, *b2 = b1 + DRM_ONE_AES_BLOCK_LEN, *b3 = b2 + DRM_ONE_AES_BLOCK_LEN, *bt; - - for (;;) { /* While some ciphertext remains, prepare to decrypt block b2 */ - /* Read in the next block to see if ciphertext stealing is needed */ - b3 = Buffer; - if (restLen > DRM_ONE_AES_BLOCK_LEN) { - len = DRM_ONE_AES_BLOCK_LEN; - } else { - len = restLen; - } - restLen -= len; - Buffer += len; - - /* Decrypt the b2 block */ - AES_decrypt((uint8_t *)b2, buf, key); - - if (len == 0 || len == DRM_ONE_AES_BLOCK_LEN) { /* No ciphertext stealing */ - /* Unchain CBC using the previous ciphertext block in b1 */ - for (i = 0; i < DRM_ONE_AES_BLOCK_LEN; ++i) - buf[i] ^= b1[i]; - } else { /* Partial last block - use ciphertext stealing */ - wlen = len; - /* Produce last 'len' bytes of plaintext by xoring with */ - /* The lowest 'len' bytes of next block b3 - C[N-1] */ - for (i = 0; i < len; ++i) - buf[i] ^= b3[i]; - - /* Reconstruct the C[N-1] block in b3 by adding in the */ - /* Last (DRM_ONE_AES_BLOCK_LEN - len) bytes of C[N-2] in b2 */ - for (i = len; i < DRM_ONE_AES_BLOCK_LEN; ++i) - b3[i] = buf[i]; - - /* Decrypt the C[N-1] block in b3 */ - AES_decrypt((uint8_t *)b3, (uint8_t *)b3, key); - - /* Produce the last but one plaintext block by xoring with */ - /* The last but two ciphertext block */ - for (i = 0; i < DRM_ONE_AES_BLOCK_LEN; ++i) - b3[i] ^= b1[i]; - - /* Write decrypted plaintext blocks */ - memcpy(pTarget, b3, DRM_ONE_AES_BLOCK_LEN); - pTarget += DRM_ONE_AES_BLOCK_LEN; - } - - /* Write the decrypted plaintext block */ - memcpy(pTarget, buf, (size_t)wlen); - pTarget += wlen; - - if (len != DRM_ONE_AES_BLOCK_LEN) { - *BufferLen = pTarget - pTargetHead; - return 0; - } - - /* Advance the buffer pointers */ - bt = b1, b1 = b2, b2 = b3, b3 = bt; - } - } - return 0; -} - -int32_t drm_updateDcfDataLen(uint8_t* pDcfLastData, uint8_t* keyValue, int32_t* moreBytes) -{ - AES_KEY key; - int32_t len = DRM_TWO_AES_BLOCK_LEN; - - if (NULL == pDcfLastData || NULL == keyValue) - return FALSE; - - AES_set_decrypt_key(keyValue, DRM_KEY_LEN * 8, &key); - - if (drm_aesDecBuffer(pDcfLastData, &len, &key) < 0) - return FALSE; - - drm_discardPaddingByte(pDcfLastData, &len); - - *moreBytes = DRM_TWO_AES_BLOCK_LEN - len; - - return TRUE; -} diff --git a/media/libdrm/mobile1/src/objmng/drm_time.c b/media/libdrm/mobile1/src/objmng/drm_time.c deleted file mode 100644 index fceb4952..0000000 --- a/media/libdrm/mobile1/src/objmng/drm_time.c +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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. - */ - -/** - * @file - * DRM 1.0 Reference Port: linux implementation of drm_time.c. - */ - -#include <objmng/drm_time.h> -#include <unistd.h> - -/* See drm_time.h */ -uint32_t DRM_time_getElapsedSecondsFrom1970(void) -{ - return time(NULL); -} - -/* See drm_time.h */ -void DRM_time_sleep(uint32_t ms) -{ - usleep(ms * 1000); -} - -/* See drm_time.h */ -void DRM_time_getSysTime(T_DB_TIME_SysTime *time_ptr) -{ - time_t t; - struct tm *tm_t; - - time(&t); - tm_t = gmtime(&t); - - time_ptr->year = tm_t->tm_year + 1900; - time_ptr->month = tm_t->tm_mon + 1; - time_ptr->day = tm_t->tm_mday; - time_ptr->hour = tm_t->tm_hour; - time_ptr->min = tm_t->tm_min; - time_ptr->sec = tm_t->tm_sec; -} diff --git a/media/libdrm/mobile1/src/parser/parser_dcf.c b/media/libdrm/mobile1/src/parser/parser_dcf.c deleted file mode 100644 index 3eac120..0000000 --- a/media/libdrm/mobile1/src/parser/parser_dcf.c +++ /dev/null @@ -1,153 +0,0 @@ -/* - * 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. - */ - -#include <parser_dcf.h> -#include <svc_drm.h> - -static int32_t drm_parseUintVar(uint8_t * buffer, uint8_t * len) -{ - int32_t i; - int32_t byteLen; - int32_t sum; - - if (NULL == buffer) - return DRM_UINT_VAR_ERR; - - byteLen = 0; - while ((buffer[byteLen] & UINT_VAR_FLAG) > 0 && byteLen < MAX_UINT_VAR_BYTE) /* UINT_VAR_FLAG == 0x80 */ - byteLen++; - - if (byteLen >= MAX_UINT_VAR_BYTE) /* MAX_UINT_VAR_BYTE == 5 */ - return DRM_UINT_VAR_ERR; /* The var int is too large, and that is impossible */ - - *len = (uint8_t)(byteLen + 1); - sum = buffer[byteLen]; - for (i = byteLen - 1; i >= 0; i--) - sum += ((buffer[i] & UINT_VAR_DATA) << 7 * (byteLen - i)); /* UINT_VAR_DATA == 0x7F */ - - return sum; -} - -/* See parser_dcf.h */ -int32_t drm_dcfParser(uint8_t *buffer, int32_t bufferLen, T_DRM_DCF_Info *pDcfInfo, - uint8_t **ppEncryptedData) -{ - uint8_t *tmpBuf; - uint8_t *pStart, *pEnd; - uint8_t *pHeader, *pData; - uint8_t varLen; - - if (NULL == buffer || bufferLen <= 0 || NULL == pDcfInfo) - return FALSE; - - tmpBuf = buffer; - /* 1. Parse the version, content-type and content-url */ - pDcfInfo->Version = *(tmpBuf++); - if (0x01 != pDcfInfo->Version) /* Because it is OMA DRM v1.0, the vension must be 1 */ - return FALSE; - - pDcfInfo->ContentTypeLen = *(tmpBuf++); - if (pDcfInfo->ContentTypeLen >= MAX_CONTENT_TYPE_LEN) - return FALSE; - - pDcfInfo->ContentURILen = *(tmpBuf++); - if (pDcfInfo->ContentURILen >= MAX_CONTENT_URI_LEN) - return FALSE; - - strncpy((char *)pDcfInfo->ContentType, (char *)tmpBuf, pDcfInfo->ContentTypeLen); - pDcfInfo->ContentType[MAX_CONTENT_TYPE_LEN - 1] = 0; - tmpBuf += pDcfInfo->ContentTypeLen; - strncpy((char *)pDcfInfo->ContentURI, (char *)tmpBuf, pDcfInfo->ContentURILen); - pDcfInfo->ContentURI[MAX_CONTENT_URI_LEN - 1] = 0; - tmpBuf += pDcfInfo->ContentURILen; - - /* 2. Get the headers length and data length */ - pDcfInfo->HeadersLen = drm_parseUintVar(tmpBuf, &varLen); - if (DRM_UINT_VAR_ERR == pDcfInfo->HeadersLen) - return FALSE; - tmpBuf += varLen; - pDcfInfo->DecryptedDataLen = DRM_UNKNOWN_DATA_LEN; - pDcfInfo->EncryptedDataLen = drm_parseUintVar(tmpBuf, &varLen); - if (DRM_UINT_VAR_ERR == pDcfInfo->EncryptedDataLen) - return FALSE; - tmpBuf += varLen; - pHeader = tmpBuf; - tmpBuf += pDcfInfo->HeadersLen; - pData = tmpBuf; - - /* 3. Parse the headers */ - pStart = pHeader; - while (pStart < pData) { - pEnd = pStart; - while ('\r' != *pEnd && pEnd < pData) - pEnd++; - - if (0 == strncmp((char *)pStart, HEADER_ENCRYPTION_METHOD, HEADER_ENCRYPTION_METHOD_LEN)) { - if ((pEnd - pStart - HEADER_ENCRYPTION_METHOD_LEN) >= MAX_ENCRYPTION_METHOD_LEN) - return FALSE; - strncpy((char *)pDcfInfo->Encryption_Method, - (char *)(pStart + HEADER_ENCRYPTION_METHOD_LEN), - pEnd - pStart - HEADER_ENCRYPTION_METHOD_LEN); - pDcfInfo->Encryption_Method[MAX_ENCRYPTION_METHOD_LEN - 1] = 0; - } else if (0 == strncmp((char *)pStart, HEADER_RIGHTS_ISSUER, HEADER_RIGHTS_ISSUER_LEN)) { - if ((pEnd - pStart - HEADER_RIGHTS_ISSUER_LEN) >= MAX_RIGHTS_ISSUER_LEN) - return FALSE; - strncpy((char *)pDcfInfo->Rights_Issuer, - (char *)(pStart + HEADER_RIGHTS_ISSUER_LEN), - pEnd - pStart - HEADER_RIGHTS_ISSUER_LEN); - pDcfInfo->Rights_Issuer[MAX_RIGHTS_ISSUER_LEN - 1] = 0; - } else if (0 == strncmp((char *)pStart, HEADER_CONTENT_NAME, HEADER_CONTENT_NAME_LEN)) { - if ((pEnd - pStart - HEADER_CONTENT_NAME_LEN) >= MAX_CONTENT_NAME_LEN) - return FALSE; - strncpy((char *)pDcfInfo->Content_Name, - (char *)(pStart + HEADER_CONTENT_NAME_LEN), - pEnd - pStart - HEADER_CONTENT_NAME_LEN); - pDcfInfo->Content_Name[MAX_CONTENT_NAME_LEN - 1] = 0; - } else if (0 == strncmp((char *)pStart, HEADER_CONTENT_DESCRIPTION, HEADER_CONTENT_DESCRIPTION_LEN)) { - if ((pEnd - pStart - HEADER_CONTENT_DESCRIPTION_LEN) >= MAX_CONTENT_DESCRIPTION_LEN) - return FALSE; - strncpy((char *)pDcfInfo->ContentDescription, - (char *)(pStart + HEADER_CONTENT_DESCRIPTION_LEN), - pEnd - pStart - HEADER_CONTENT_DESCRIPTION_LEN); - pDcfInfo->ContentDescription[MAX_CONTENT_DESCRIPTION_LEN - 1] = 0; - } else if (0 == strncmp((char *)pStart, HEADER_CONTENT_VENDOR, HEADER_CONTENT_VENDOR_LEN)) { - if ((pEnd - pStart - HEADER_CONTENT_VENDOR_LEN) >= MAX_CONTENT_VENDOR_LEN) - return FALSE; - strncpy((char *)pDcfInfo->ContentVendor, - (char *)(pStart + HEADER_CONTENT_VENDOR_LEN), - pEnd - pStart - HEADER_CONTENT_VENDOR_LEN); - pDcfInfo->ContentVendor[MAX_CONTENT_VENDOR_LEN - 1] = 0; - } else if (0 == strncmp((char *)pStart, HEADER_ICON_URI, HEADER_ICON_URI_LEN)) { - if ((pEnd - pStart - HEADER_ICON_URI_LEN) >= MAX_ICON_URI_LEN) - return FALSE; - strncpy((char *)pDcfInfo->Icon_URI, - (char *)(pStart + HEADER_ICON_URI_LEN), - pEnd - pStart - HEADER_ICON_URI_LEN); - pDcfInfo->Icon_URI[MAX_ICON_URI_LEN - 1] = 0; - } - - if ('\n' == *(pEnd + 1)) - pStart = pEnd + 2; /* Two bytes: a '\r' and a '\n' */ - else - pStart = pEnd + 1; - } - - /* 4. Give out the location of encrypted data */ - if (NULL != ppEncryptedData) - *ppEncryptedData = pData; - - return TRUE; -} diff --git a/media/libdrm/mobile1/src/parser/parser_dm.c b/media/libdrm/mobile1/src/parser/parser_dm.c deleted file mode 100644 index 4b4a2da..0000000 --- a/media/libdrm/mobile1/src/parser/parser_dm.c +++ /dev/null @@ -1,279 +0,0 @@ -/* - * 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. - */ - -#include <parser_dm.h> -#include <parser_dcf.h> -#include <svc_drm.h> -#include "log.h" - -#define DRM_SKIP_SPACE_TAB(p) while( (*(p) == ' ') || (*(p) == '\t') ) \ - p++ - -typedef enum _DM_PARSE_STATUS { - DM_PARSE_START, - DM_PARSING_RIGHTS, - DM_PARSING_CONTENT, - DM_PARSE_END -} DM_PARSE_STATUS; - -static int drm_strnicmp(const uint8_t* s1, const uint8_t* s2, int32_t n) -{ - if (n < 0 || NULL == s1 || NULL == s2) - return -1; - - if (n == 0) - return 0; - - while (n-- != 0 && tolower(*s1) == tolower(*s2)) - { - if (n == 0 || *s1 == '\0' || *s2 == '\0') - break; - s1++; - s2++; - } - - return tolower(*s1) - tolower(*s2); -} - -const uint8_t * drm_strnstr(const uint8_t * str, const uint8_t * strSearch, int32_t len) -{ - int32_t i, stringLen; - - if (NULL == str || NULL == strSearch || len <= 0) - return NULL; - - stringLen = strlen((char *)strSearch); - for (i = 0; i < len - stringLen + 1; i++) { - if (str[i] == *strSearch && 0 == memcmp(str + i, strSearch, stringLen)) - return str + i; - } - return NULL; -} - -/* See parser_dm.h */ -int32_t drm_parseDM(const uint8_t *buffer, int32_t bufferLen, T_DRM_DM_Info *pDmInfo) -{ - const uint8_t *pStart = NULL, *pEnd = NULL; - const uint8_t *pBufferEnd; - int32_t contentLen, leftLen; - DM_PARSE_STATUS status = DM_PARSE_START; - int32_t boundaryLen; - - if (NULL == buffer || bufferLen <= 0 || NULL == pDmInfo) - return FALSE; - - /* Find the end of the input buffer */ - pBufferEnd = buffer + bufferLen; - leftLen = bufferLen; - - /* Find out the boundary */ - pStart = drm_strnstr(buffer, (uint8_t *)"--", bufferLen); - if (NULL == pStart) - return FALSE; /* No boundary error */ - pEnd = pStart; - - /* Record the boundary */ - pEnd = drm_strnstr(pStart, (uint8_t *)DRM_NEW_LINE_CRLF, leftLen); - /* if can not find the CRLF, return FALSE */ - if (NULL == pEnd) - return FALSE; - if ((pEnd - pStart) >= MAX_CONTENT_BOUNDARY_LEN) - return FALSE; - strncpy((char *)pDmInfo->boundary, (char *)pStart, pEnd - pStart); - pDmInfo->boundary[MAX_CONTENT_BOUNDARY_LEN - 1] = 0; - boundaryLen = strlen((char *)pDmInfo->boundary) + 2; /* 2 means: '\r' and '\n' */ - - pEnd += 2; /* skip the '\r' and '\n' */ - pStart = pEnd; - leftLen = pBufferEnd - pStart; - do { - pDmInfo->transferEncoding = DRM_MESSAGE_CODING_7BIT; /* According RFC2045 chapter 6.1, the default value should be 7bit.*/ - strcpy((char *)pDmInfo->contentType, "text/plain"); /* According RFC2045 chapter 5.2, the default value should be "text/plain". */ - - /* Deal the header information */ - while ((('\r' != *pStart) || ('\n' != *(pStart + 1))) && pStart < pBufferEnd) { - pEnd = drm_strnstr(pStart, (uint8_t *)DRM_NEW_LINE_CRLF, leftLen); - if (NULL == pEnd) - return FALSE; - - if (0 != pDmInfo->deliveryType) { /* This means the delivery type has been confirmed */ - if (0 == strncmp((char *)pStart, HEADERS_TRANSFER_CODING, HEADERS_TRANSFER_CODING_LEN)) { - pStart += HEADERS_TRANSFER_CODING_LEN; - DRM_SKIP_SPACE_TAB(pStart); - - if (0 == strncmp((char *)pStart, TRANSFER_CODING_TYPE_7BIT, pEnd - pStart)) - pDmInfo->transferEncoding = DRM_MESSAGE_CODING_7BIT; - else if (0 == strncmp((char *)pStart, TRANSFER_CODING_TYPE_8BIT, pEnd - pStart)) - pDmInfo->transferEncoding = DRM_MESSAGE_CODING_8BIT; - else if (0 == strncmp((char *)pStart, TRANSFER_CODING_TYPE_BINARY, pEnd - pStart)) - pDmInfo->transferEncoding = DRM_MESSAGE_CODING_BINARY; - else if (0 == strncmp((char *)pStart, TRANSFER_CODING_TYPE_BASE64, pEnd - pStart)) - pDmInfo->transferEncoding = DRM_MESSAGE_CODING_BASE64; - else - return FALSE; /* Unknown transferCoding error */ - } else if (0 == drm_strnicmp(pStart, (uint8_t *)HEADERS_CONTENT_TYPE, HEADERS_CONTENT_TYPE_LEN)) { - pStart += HEADERS_CONTENT_TYPE_LEN; - DRM_SKIP_SPACE_TAB(pStart); - - if (pEnd - pStart > 0) { - if ((pEnd - pStart) >= MAX_CONTENT_TYPE_LEN) - return FALSE; - strncpy((char *)pDmInfo->contentType, (char *)pStart, pEnd - pStart); - pDmInfo->contentType[pEnd - pStart] = '\0'; - } - } else if (0 == drm_strnicmp(pStart, (uint8_t *)HEADERS_CONTENT_ID, HEADERS_CONTENT_ID_LEN)) { - uint8_t tmpBuf[MAX_CONTENT_ID] = {0}; - uint8_t *pTmp; - - pStart += HEADERS_CONTENT_ID_LEN; - DRM_SKIP_SPACE_TAB(pStart); - - /* error: more than one content id */ - if(drm_strnstr(pStart, (uint8_t*)HEADERS_CONTENT_ID, pBufferEnd - pStart)){ - ALOGD("drm_dmParser: error: more than one content id\r\n"); - return FALSE; - } - - status = DM_PARSING_CONTENT; /* can go here means that the rights object has been parsed. */ - - /* Change the format from <...> to cid:... */ - if (NULL != (pTmp = (uint8_t *)memchr((char *)pStart, '<', pEnd - pStart))) { - if ((pEnd - pTmp - 1) >= (int) sizeof(tmpBuf)) - return FALSE; - strncpy((char *)tmpBuf, (char *)(pTmp + 1), pEnd - pTmp - 1); - tmpBuf[MAX_CONTENT_ID - 1] = 0; - - if (NULL != (pTmp = (uint8_t *)memchr((char *)tmpBuf, '>', pEnd - pTmp - 1))) { - *pTmp = '\0'; - - memset(pDmInfo->contentID, 0, MAX_CONTENT_ID); - snprintf((char *)pDmInfo->contentID, MAX_CONTENT_ID, "%s%s", "cid:", (int8_t *)tmpBuf); - } - } - } - } else { /* First confirm delivery type, Forward_Lock, Combined Delivery or Separate Delivery */ - if (0 == drm_strnicmp(pStart, (uint8_t *)HEADERS_CONTENT_TYPE, HEADERS_CONTENT_TYPE_LEN)) { - pStart += HEADERS_CONTENT_TYPE_LEN; - DRM_SKIP_SPACE_TAB(pStart); - - if (pEnd - pStart > 0) { - strncpy((char *)pDmInfo->contentType, (char *)pStart, pEnd - pStart); - pDmInfo->contentType[pEnd - pStart] = '\0'; - } - - if (0 == strcmp((char *)pDmInfo->contentType, DRM_MIME_TYPE_RIGHTS_XML)) { - pDmInfo->deliveryType = COMBINED_DELIVERY; - status = DM_PARSING_RIGHTS; - } - else if (0 == strcmp((char *)pDmInfo->contentType, DRM_MIME_TYPE_CONTENT)) { - pDmInfo->deliveryType = SEPARATE_DELIVERY_FL; - status = DM_PARSING_CONTENT; - } - else if (0 == pDmInfo->deliveryType) { - pDmInfo->deliveryType = FORWARD_LOCK; - status = DM_PARSING_CONTENT; - } - } - } - pEnd += 2; /* skip the '\r' and '\n' */ - pStart = pEnd; - leftLen = pBufferEnd - pStart; - } - pStart += 2; /* skip the second CRLF: "\r\n" */ - pEnd = pStart; - - /* Deal the content part, including rel or real content */ - while (leftLen > 0) { - if (NULL == (pEnd = memchr(pEnd, '\r', leftLen))) { - pEnd = pBufferEnd; - break; /* no boundary found */ - } - - leftLen = pBufferEnd - pEnd; - if (leftLen < boundaryLen) { - pEnd = pBufferEnd; - break; /* here means may be the boundary has been split */ - } - - if (('\n' == *(pEnd + 1)) && (0 == memcmp(pEnd + 2, pDmInfo->boundary, strlen((char *)pDmInfo->boundary)))) - break; /* find the boundary here */ - - pEnd++; - leftLen--; - } - - if (pEnd >= pBufferEnd) - contentLen = DRM_UNKNOWN_DATA_LEN; - else - contentLen = pEnd - pStart; - - switch(pDmInfo->deliveryType) { - case FORWARD_LOCK: - pDmInfo->contentLen = contentLen; - pDmInfo->contentOffset = pStart - buffer; - status = DM_PARSE_END; - break; - case COMBINED_DELIVERY: - if (DM_PARSING_RIGHTS == status) { - pDmInfo->rightsLen = contentLen; - pDmInfo->rightsOffset = pStart - buffer; - } else { - pDmInfo->contentLen = contentLen; - pDmInfo->contentOffset = pStart - buffer; - status = DM_PARSE_END; - } - break; - case SEPARATE_DELIVERY_FL: - { - T_DRM_DCF_Info dcfInfo; - uint8_t* pEncData = NULL; - - memset(&dcfInfo, 0, sizeof(T_DRM_DCF_Info)); - if (DRM_UNKNOWN_DATA_LEN == contentLen) - contentLen = pEnd - pStart; - if (FALSE == drm_dcfParser(pStart, contentLen, &dcfInfo, &pEncData)) - return FALSE; - - pDmInfo->contentLen = dcfInfo.EncryptedDataLen; - pDmInfo->contentOffset = pEncData - buffer; - strcpy((char *)pDmInfo->contentType, (char *)dcfInfo.ContentType); - strcpy((char *)pDmInfo->contentID, (char *)dcfInfo.ContentURI); - strcpy((char *)pDmInfo->rightsIssuer, (char *)dcfInfo.Rights_Issuer); - status = DM_PARSE_END; - } - break; - default: - return FALSE; - } - - if (DM_PARSING_RIGHTS == status) { - /* Here means the rights object data has been completed, boundary must exist */ - leftLen = pBufferEnd - pEnd; - pStart = drm_strnstr(pEnd, pDmInfo->boundary, leftLen); - if (NULL == pStart) - return FALSE; - leftLen = pBufferEnd - pStart; - pEnd = drm_strnstr(pStart, (uint8_t *)DRM_NEW_LINE_CRLF, leftLen); - if (NULL == pEnd) - return FALSE; /* only rights object, no media object, error */ - - pEnd += 2; /* skip the "\r\n" */ - pStart = pEnd; - } - } while (DM_PARSE_END != status); - - return TRUE; -} diff --git a/media/libdrm/mobile1/src/parser/parser_rel.c b/media/libdrm/mobile1/src/parser/parser_rel.c deleted file mode 100644 index 537fa9c..0000000 --- a/media/libdrm/mobile1/src/parser/parser_rel.c +++ /dev/null @@ -1,663 +0,0 @@ -/* - * 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. - */ - -#include <parser_rel.h> -#include <parser_dm.h> -#include <xml_tinyParser.h> -#include <wbxml_tinyparser.h> -#include <drm_decoder.h> -#include <svc_drm.h> - -/* See parser_rel.h */ -int32_t drm_monthDays(int32_t year, int32_t month) -{ - switch (month) { - case 1: - case 3: - case 5: - case 7: - case 8: - case 10: - case 12: - return 31; - case 4: - case 6: - case 9: - case 11: - return 30; - case 2: - if (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0)) - return 29; - else - return 28; - default: - return -1; - } -} - -int32_t drm_checkDate(int32_t year, int32_t month, int32_t day, - int32_t hour, int32_t min, int32_t sec) -{ - if (month >= 1 && month <= 12 && - day >= 1 && day <= drm_monthDays(year, month) && - hour >= 0 && hour <= 23 && - min >= 0 && min <= 59 && sec >= 0 && sec <= 59) - return 0; - else - return -1; -} - -static int32_t drm_getStartEndTime(uint8_t * pValue, int32_t valueLen, - T_DRM_DATETIME * dateTime) -{ - int32_t year, mon, day, hour, min, sec; - uint8_t pTmp[64] = {0}; - - strncpy((char *)pTmp, (char *)pValue, valueLen); - { - uint8_t * pHead = pTmp; - uint8_t * pEnd = NULL; - uint8_t tmpByte; - - /** get year */ - pEnd = (uint8_t *)strstr((char *)pHead, "-"); - if(NULL == pEnd) - return FALSE; - tmpByte = *pEnd; - *pEnd = '\0'; - year = atoi((char *)pHead); - pHead = pEnd + 1; - *pEnd = tmpByte; - - /** get month */ - pEnd = (uint8_t *)strstr((char *)pHead, "-"); - if(NULL == pEnd) - return FALSE; - tmpByte = *pEnd; - *pEnd = '\0'; - mon = atoi((char *)pHead); - pHead = pEnd + 1; - *pEnd = tmpByte; - - /** get day */ - pEnd = (uint8_t *)strstr((char *)pHead, "T"); - if(NULL == pEnd) - return FALSE; - tmpByte = *pEnd; - *pEnd = '\0'; - day = atoi((char *)pHead); - pHead = pEnd + 1; - *pEnd = tmpByte; - - /** get hour */ - pEnd = (uint8_t *)strstr((char *)pHead, ":"); - if(NULL == pEnd) - return FALSE; - tmpByte = *pEnd; - *pEnd = '\0'; - hour = atoi((char *)pHead); - pHead = pEnd + 1; - *pEnd = tmpByte; - - /** get minute */ - pEnd = (uint8_t *)strstr((char *)pHead, ":"); - if(NULL == pEnd) - return FALSE; - tmpByte = *pEnd; - *pEnd = '\0'; - min = atoi((char *)pHead); - pHead = pEnd + 1; - *pEnd = tmpByte; - - /** get second */ - sec = atoi((char *)pHead); - } - if (0 != drm_checkDate(year, mon, day, hour, min, sec)) - return FALSE; - - YMD_HMS_2_INT(year, mon, day, dateTime->date, hour, min, sec, - dateTime->time); - return TRUE; -} - -static int32_t drm_checkWhetherHasUnknowConstraint(uint8_t* drm_constrain) -{ - char* begin_constrain = "<o-ex:constraint>"; - char* end_constrain = "</o-ex:constraint>"; - char* constrain_begin = strstr((char*)drm_constrain,begin_constrain); - char* constrain_end = strstr((char*)drm_constrain,end_constrain); - uint32_t constrain_len = 0; - - if(NULL == constrain_begin) - return FALSE; - - if(NULL == constrain_end) - return TRUE; - - /* compute valid characters length */ - { - uint32_t constrain_begin_len = strlen(begin_constrain); - char* cur_pos = constrain_begin + constrain_begin_len; - - constrain_len = (constrain_end - constrain_begin) - constrain_begin_len; - - while(cur_pos < constrain_end){ - if(isspace(*cur_pos)) - constrain_len--; - - cur_pos++; - } - } - - /* check all constraints */ - { - #define DRM_ALL_CONSTRAINT_COUNT 5 - - int32_t i = 0; - int32_t has_datetime = FALSE; - int32_t has_start_or_end = FALSE; - - char* all_vaild_constraints[DRM_ALL_CONSTRAINT_COUNT][2] = { - {"<o-dd:count>","</o-dd:count>"}, - {"<o-dd:interval>","</o-dd:interval>"}, - {"<o-dd:datetime>","</o-dd:datetime>"}, - {"<o-dd:start>","</o-dd:start>"}, - {"<o-dd:end>","</o-dd:end>"} - }; - - for(i = 0; i < DRM_ALL_CONSTRAINT_COUNT; i++){ - char*start = strstr((char*)drm_constrain,all_vaild_constraints[i][0]); - - if(start && (start < constrain_end)){ - char* end = strstr((char*)drm_constrain,all_vaild_constraints[i][1]); - - if(end && (end < constrain_end)){ - if(0 == strncmp(all_vaild_constraints[i][0],"<o-dd:datetime>",strlen("<o-dd:datetime>"))){ - constrain_len -= strlen(all_vaild_constraints[i][0]); - constrain_len -= strlen(all_vaild_constraints[i][1]); - - if(0 == constrain_len) - return TRUE; - - has_datetime = TRUE; - continue; - } - - if((0 == strncmp(all_vaild_constraints[i][0],"<o-dd:start>",strlen("<o-dd:start>"))) - || (0 == strncmp(all_vaild_constraints[i][0],"<o-dd:end>",strlen("<o-dd:end>")))){ - if(FALSE == has_datetime) - return TRUE; - else - has_start_or_end = TRUE; - } - - constrain_len -= (end - start); - constrain_len -= strlen(all_vaild_constraints[i][1]); - - if(0 == constrain_len) - if(has_datetime != has_start_or_end) - return TRUE; - else - return FALSE; - } - else - return TRUE; - } - } - - if(has_datetime != has_start_or_end) - return TRUE; - - if(constrain_len) - return TRUE; - else - return FALSE; - } -} - -static int32_t drm_getRightValue(uint8_t * buffer, int32_t bufferLen, - T_DRM_Rights * ro, uint8_t * operation, - uint8_t oper_char) -{ - uint8_t *pBuf, *pValue; - uint8_t sProperty[256]; - int32_t valueLen; - int32_t year, mon, day, hour, min, sec; - T_DRM_Rights_Constraint *pConstraint; - int32_t *bIsAble; - uint8_t *ret = NULL; - int32_t flag = 0; - - if (operation == NULL) { - switch (oper_char) { - case REL_TAG_PLAY: - pConstraint = &(ro->PlayConstraint); - bIsAble = &(ro->bIsPlayable); - break; - case REL_TAG_DISPLAY: - pConstraint = &(ro->DisplayConstraint); - bIsAble = &(ro->bIsDisplayable); - break; - case REL_TAG_EXECUTE: - pConstraint = &(ro->ExecuteConstraint); - bIsAble = &(ro->bIsExecuteable); - break; - case REL_TAG_PRINT: - pConstraint = &(ro->PrintConstraint); - bIsAble = &(ro->bIsPrintable); - break; - default: - return FALSE; /* The input parm is err */ - } - } else { - if (strcmp((char *)operation, "play") == 0) { - pConstraint = &(ro->PlayConstraint); - bIsAble = &(ro->bIsPlayable); - } else if (strcmp((char *)operation, "display") == 0) { - pConstraint = &(ro->DisplayConstraint); - bIsAble = &(ro->bIsDisplayable); - } else if (strcmp((char *)operation, "execute") == 0) { - pConstraint = &(ro->ExecuteConstraint); - bIsAble = &(ro->bIsExecuteable); - } else if (strcmp((char *)operation, "print") == 0) { - pConstraint = &(ro->PrintConstraint); - bIsAble = &(ro->bIsPrintable); - } else - return FALSE; /* The input parm is err */ - } - - if (operation == NULL) { - sprintf((char *)sProperty, "%c%c%c%c", REL_TAG_RIGHTS, - REL_TAG_AGREEMENT, REL_TAG_PERMISSION, oper_char); - ret = WBXML_DOM_getNode(buffer, bufferLen, sProperty); - } else { - sprintf((char *)sProperty, - "o-ex:rights\\o-ex:agreement\\o-ex:permission\\o-dd:%s", - operation); - ret = XML_DOM_getNode(buffer, sProperty); - } - CHECK_VALIDITY(ret); - if (NULL == ret) - return TRUE; - WRITE_RO_FLAG(*bIsAble, 1, pConstraint->Indicator, DRM_NO_CONSTRAINT); /* If exit first assume have utter rights */ - flag = 1; - - if (operation == NULL) { /* If father element node is not exit then return */ - sprintf((char *)sProperty, "%c%c%c%c%c", REL_TAG_RIGHTS, - REL_TAG_AGREEMENT, REL_TAG_PERMISSION, oper_char, - REL_TAG_CONSTRAINT); - ret = WBXML_DOM_getNode(buffer, bufferLen, sProperty); - } else { - sprintf((char *)sProperty, - "o-ex:rights\\o-ex:agreement\\o-ex:permission\\o-dd:%s\\o-ex:constraint", - operation); - ret = XML_DOM_getNode(buffer, sProperty); - } - - CHECK_VALIDITY(ret); - if (ret == NULL) - return TRUE; - - if(TRUE == drm_checkWhetherHasUnknowConstraint(ret)) - return FALSE; - - *bIsAble = 0; - pConstraint->Indicator = DRM_NO_PERMISSION; /* If exit constraint assume have no rights */ - flag = 2; - - if (operation == NULL) { - sprintf((char *)sProperty, "%c%c%c%c%c%c", REL_TAG_RIGHTS, - REL_TAG_AGREEMENT, REL_TAG_PERMISSION, oper_char, - REL_TAG_CONSTRAINT, REL_TAG_INTERVAL); - pBuf = - WBXML_DOM_getNodeValue(buffer, bufferLen, sProperty, (uint8_t **)&pValue, - &valueLen); - } else { - sprintf((char *)sProperty, - "o-ex:rights\\o-ex:agreement\\o-ex:permission\\o-dd:%s\\o-ex:constraint\\o-dd:interval", - operation); - pBuf = XML_DOM_getNodeValue(buffer, sProperty, &pValue, &valueLen); - } - CHECK_VALIDITY(pBuf); - if (pBuf) { /* If interval element exit then get the value */ - uint8_t pTmp[64] = {0}; - - strncpy((char *)pTmp, (char *)pValue, valueLen); - { - uint8_t * pHead = pTmp + 1; - uint8_t * pEnd = NULL; - uint8_t tmpChar; - - /** get year */ - pEnd = (uint8_t *)strstr((char *)pHead, "Y"); - if(NULL == pEnd) - return FALSE; - tmpChar = *pEnd; - *pEnd = '\0'; - year = atoi((char *)pHead); - pHead = pEnd + 1; - *pEnd = tmpChar; - - /** get month */ - pEnd = (uint8_t *)strstr((char *)pHead, "M"); - if(NULL == pEnd) - return FALSE; - tmpChar = *pEnd; - *pEnd = '\0'; - mon = atoi((char *)pHead); - pHead = pEnd + 1; - *pEnd = tmpChar; - - /** get day */ - pEnd = (uint8_t *)strstr((char *)pHead, "D"); - if(NULL == pEnd) - return FALSE; - tmpChar = *pEnd; - *pEnd = '\0'; - day = atoi((char *)pHead); - pHead = pEnd + 2; - *pEnd = tmpChar; - - /** get hour */ - pEnd = (uint8_t *)strstr((char *)pHead, "H"); - if(NULL == pEnd) - return FALSE; - tmpChar = *pEnd; - *pEnd = '\0'; - hour = atoi((char *)pHead); - pHead = pEnd + 1; - *pEnd = tmpChar; - - /** get minute */ - pEnd = (uint8_t *)strstr((char *)pHead, "M"); - if(NULL == pEnd) - return FALSE; - tmpChar = *pEnd; - *pEnd = '\0'; - min = atoi((char *)pHead); - pHead = pEnd + 1; - *pEnd = tmpChar; - - /** get second */ - pEnd = (uint8_t *)strstr((char *)pHead, "S"); - if(NULL == pEnd) - return FALSE; - tmpChar = *pEnd; - *pEnd = '\0'; - sec = atoi((char *)pHead); - pHead = pEnd + 1; - *pEnd = tmpChar; - } - - if (year < 0 || mon < 0 || day < 0 || hour < 0 - || min < 0 || sec < 0) - return FALSE; - YMD_HMS_2_INT(year, mon, day, pConstraint->Interval.date, hour, - min, sec, pConstraint->Interval.time); - WRITE_RO_FLAG(*bIsAble, 1, pConstraint->Indicator, - DRM_INTERVAL_CONSTRAINT); - flag = 3; - } - - if (operation == NULL) { - sprintf((char *)sProperty, "%c%c%c%c%c%c", REL_TAG_RIGHTS, - REL_TAG_AGREEMENT, REL_TAG_PERMISSION, oper_char, - REL_TAG_CONSTRAINT, REL_TAG_COUNT); - pBuf = - WBXML_DOM_getNodeValue(buffer, bufferLen, sProperty, (uint8_t **)&pValue, - &valueLen); - } else { - sprintf((char *)sProperty, - "o-ex:rights\\o-ex:agreement\\o-ex:permission\\o-dd:%s\\o-ex:constraint\\o-dd:count", - operation); - pBuf = XML_DOM_getNodeValue(buffer, sProperty, &pValue, &valueLen); - } - CHECK_VALIDITY(pBuf); - if (pBuf) { /* If count element exit the get the value */ - uint8_t pTmp[16] = {0}; - int32_t i; - - for (i = 0; i < valueLen; i++) { /* Check the count format */ - if (0 == isdigit(*(pValue + i))) - return FALSE; - } - - strncpy((char *)pTmp, (char *)pValue, valueLen); - pConstraint->Count = atoi((char *)pTmp); - - if(0 == pConstraint->Count) - { - WRITE_RO_FLAG(*bIsAble, 0, pConstraint->Indicator, DRM_NO_PERMISSION); - } - else if( pConstraint->Count > 0) - { - WRITE_RO_FLAG(*bIsAble, 1, pConstraint->Indicator, DRM_COUNT_CONSTRAINT); - } - else /* < 0 */ - { - return FALSE; - } - - flag = 3; - } - - if (operation == NULL) { - sprintf((char *)sProperty, "%c%c%c%c%c%c%c", REL_TAG_RIGHTS, - REL_TAG_AGREEMENT, REL_TAG_PERMISSION, oper_char, - REL_TAG_CONSTRAINT, REL_TAG_DATETIME, REL_TAG_START); - pBuf = - WBXML_DOM_getNodeValue(buffer, bufferLen, sProperty, (uint8_t **)&pValue, - &valueLen); - } else { - sprintf((char *)sProperty, - "o-ex:rights\\o-ex:agreement\\o-ex:permission\\o-dd:%s\\o-ex:constraint\\o-dd:datetime\\o-dd:start", - operation); - pBuf = XML_DOM_getNodeValue(buffer, sProperty, &pValue, &valueLen); - } - CHECK_VALIDITY(pBuf); - if (pBuf) { /* If start element exit then get the value */ - if (FALSE == - drm_getStartEndTime(pValue, valueLen, &pConstraint->StartTime)) - return FALSE; - WRITE_RO_FLAG(*bIsAble, 1, pConstraint->Indicator, DRM_START_TIME_CONSTRAINT); - flag = 3; - } - - if (operation == NULL) { - sprintf((char *)sProperty, "%c%c%c%c%c%c%c", REL_TAG_RIGHTS, - REL_TAG_AGREEMENT, REL_TAG_PERMISSION, oper_char, - REL_TAG_CONSTRAINT, REL_TAG_DATETIME, REL_TAG_END); - pBuf = - WBXML_DOM_getNodeValue(buffer, bufferLen, sProperty, (uint8_t **)&pValue, - &valueLen); - } else { - sprintf((char *)sProperty, - "o-ex:rights\\o-ex:agreement\\o-ex:permission\\o-dd:%s\\o-ex:constraint\\o-dd:datetime\\o-dd:end", - operation); - pBuf = XML_DOM_getNodeValue(buffer, sProperty, &pValue, &valueLen); - } - CHECK_VALIDITY(pBuf); - if (pBuf) { - if (FALSE == - drm_getStartEndTime(pValue, valueLen, &pConstraint->EndTime)) - return FALSE; - WRITE_RO_FLAG(*bIsAble, 1, pConstraint->Indicator, DRM_END_TIME_CONSTRAINT); - flag = 3; - } - - if (2 == flag) - WRITE_RO_FLAG(*bIsAble, 1, pConstraint->Indicator, DRM_NO_CONSTRAINT); /* If exit first assume have utter rights */ - return TRUE; -} - -/* See parser_rel.h */ -int32_t drm_relParser(uint8_t* buffer, int32_t bufferLen, int32_t Format, T_DRM_Rights* pRights) -{ - uint8_t *pBuf, *pValue; - uint8_t sProperty[256]; - int32_t valueLen; - - if (TYPE_DRM_RIGHTS_WBXML != Format && TYPE_DRM_RIGHTS_XML != Format) /* It is not the support parse format */ - return FALSE; - - if (TYPE_DRM_RIGHTS_XML == Format) { - /* Check whether it is a CD, and parse it using TYPE_DRM_RIGHTS_XML */ - if (NULL != drm_strnstr(buffer, (uint8_t *)HEADERS_CONTENT_ID, bufferLen)) - return FALSE; - - pBuf = - XML_DOM_getNodeValue(buffer, - (uint8_t *)"o-ex:rights\\o-ex:context\\o-dd:version", - &pValue, &valueLen); - CHECK_VALIDITY(pBuf); - - if (pBuf) { - if (valueLen > 8) /* Check version lenth */ - return FALSE; - - /* error version */ - if(strncmp(pValue,"1.0",valueLen)) - return FALSE; - - strncpy((char *)pRights->Version, (char *)pValue, valueLen); - } else - return FALSE; - - /* this means there is more than one version label in rights */ - if(strstr((char*)pBuf, "<o-dd:version>")) - return FALSE; - - pBuf = - XML_DOM_getNodeValue(buffer, - (uint8_t *)"o-ex:rights\\o-ex:agreement\\o-ex:asset\\ds:KeyInfo\\ds:KeyValue", - &pValue, &valueLen); - CHECK_VALIDITY(pBuf); - if (pBuf) { /* Get keyvalue */ - int32_t keyLen; - - if (24 != valueLen) - return FALSE; - - keyLen = drm_decodeBase64(NULL, 0, pValue, &valueLen); - if (keyLen < 0) - return FALSE; - - if (DRM_KEY_LEN != drm_decodeBase64(pRights->KeyValue, keyLen, pValue, &valueLen)) - return FALSE; - } - - pBuf = - XML_DOM_getNodeValue(buffer, - (uint8_t *)"o-ex:rights\\o-ex:agreement\\o-ex:asset\\o-ex:context\\o-dd:uid", - &pValue, &valueLen); - CHECK_VALIDITY(pBuf); - if (pBuf) { - if (valueLen > DRM_UID_LEN) - return FALSE; - strncpy((char *)pRights->uid, (char *)pValue, valueLen); - pRights->uid[valueLen] = '\0'; - } else - return FALSE; - - /* this means there is more than one uid label in rights */ - if(strstr((char*)pBuf, "<o-dd:uid>")) - return FALSE; - - if (FALSE == - drm_getRightValue(buffer, bufferLen, pRights, (uint8_t *)"play", 0)) - return FALSE; - - if (FALSE == - drm_getRightValue(buffer, bufferLen, pRights, (uint8_t *)"display", 0)) - return FALSE; - - if (FALSE == - drm_getRightValue(buffer, bufferLen, pRights, (uint8_t *)"execute", 0)) - return FALSE; - - if (FALSE == - drm_getRightValue(buffer, bufferLen, pRights, (uint8_t *)"print", 0)) - return FALSE; - } else if (TYPE_DRM_RIGHTS_WBXML == Format) { - if (!REL_CHECK_WBXML_HEADER(buffer)) - return FALSE; - - sprintf((char *)sProperty, "%c%c%c", REL_TAG_RIGHTS, REL_TAG_CONTEXT, - REL_TAG_VERSION); - pBuf = - WBXML_DOM_getNodeValue(buffer, bufferLen, sProperty, (uint8_t **)&pValue, - &valueLen); - CHECK_VALIDITY(pBuf); - - if (pBuf) { - if (valueLen > 8) /* Check version lenth */ - return FALSE; - strncpy((char *)pRights->Version, (char *)pValue, valueLen); - } else - return FALSE; - - sprintf((char *)sProperty, "%c%c%c%c%c", - REL_TAG_RIGHTS, REL_TAG_AGREEMENT, REL_TAG_ASSET, - REL_TAG_KEYINFO, REL_TAG_KEYVALUE); - pBuf = - WBXML_DOM_getNodeValue(buffer, bufferLen, sProperty, (uint8_t **)&pValue, - &valueLen); - CHECK_VALIDITY(pBuf); - if (pBuf) { - if (DRM_KEY_LEN != valueLen) - return FALSE; - memcpy(pRights->KeyValue, pValue, DRM_KEY_LEN); - memset(pValue, 0, DRM_KEY_LEN); /* Clean the KeyValue */ - } - - sprintf((char *)sProperty, "%c%c%c%c%c", - REL_TAG_RIGHTS, REL_TAG_AGREEMENT, REL_TAG_ASSET, - REL_TAG_CONTEXT, REL_TAG_UID); - pBuf = - WBXML_DOM_getNodeValue(buffer, bufferLen, sProperty, (uint8_t **)&pValue, - &valueLen); - CHECK_VALIDITY(pBuf); - if (pBuf) { - if (valueLen > DRM_UID_LEN) - return FALSE; - strncpy((char *)pRights->uid, (char *)pValue, valueLen); - pRights->uid[valueLen] = '\0'; - } else - return FALSE; - - if (FALSE == - drm_getRightValue(buffer, bufferLen, pRights, NULL, - REL_TAG_PLAY)) - return FALSE; - - if (FALSE == - drm_getRightValue(buffer, bufferLen, pRights, NULL, - REL_TAG_DISPLAY)) - return FALSE; - - if (FALSE == - drm_getRightValue(buffer, bufferLen, pRights, NULL, - REL_TAG_EXECUTE)) - return FALSE; - - if (FALSE == - drm_getRightValue(buffer, bufferLen, pRights, NULL, - REL_TAG_PRINT)) - return FALSE; - } - - return TRUE; -} diff --git a/media/libdrm/mobile1/src/xml/xml_tinyparser.c b/media/libdrm/mobile1/src/xml/xml_tinyparser.c deleted file mode 100644 index 7580312..0000000 --- a/media/libdrm/mobile1/src/xml/xml_tinyparser.c +++ /dev/null @@ -1,834 +0,0 @@ -/* - * 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. - */ - -#include <xml/xml_tinyParser.h> - -int32_t xml_errno; - -#ifdef XML_DOM_PARSER - -#define XML_IS_WHITESPACE(x) ((x) == '\t' || (x) == '\n' || (x) == ' ' || (x) == '\r') -#define XML_IS_NAMECHAR(ch) (isalpha(ch) || isdigit(ch) || ch ==':' || \ - ch == '_' || ch == '-' || ch =='.') - -static uint8_t *xml_ignore_blank(uint8_t *buffer) -{ - if (NULL == buffer) - return NULL; - - while (XML_IS_WHITESPACE(*buffer)) - buffer++; - - return buffer; -} - -static uint8_t *xml_goto_tagend(uint8_t *buffer) -{ - int32_t nameLen, valueLen; - uint8_t *name, *value; - - if (NULL == buffer) - return NULL; - - /* Ignore the start-tag */ - if (*buffer == '<') { - buffer++; - while (buffer != NULL && XML_IS_NAMECHAR(*buffer)) - buffer++; - if (NULL == buffer) - return NULL; - } - - do { - if (NULL == (buffer = xml_ignore_blank(buffer))) - return NULL; - - if (*buffer == '>' || (*buffer == '/' && *(buffer + 1) == '>')) - return buffer; - - if (NULL == - XML_DOM_getAttr(buffer, &name, &nameLen, &value, &valueLen)) - return NULL; - - buffer = value + valueLen + 1; - } while (*buffer != '\0'); - - return NULL; -} - -static uint8_t *xml_match_tag(uint8_t *buffer) -{ - int32_t tagLen, tagType, bal; - - if (NULL == buffer) - return NULL; - - bal = 0; - do { - if (NULL == (buffer = XML_DOM_getTag(buffer, &tagLen, &tagType))) - return NULL; - - switch (tagType) { - case XML_TAG_SELF: - case XML_TAG_START: - if (NULL == (buffer = xml_goto_tagend(buffer + tagLen + 1))) - return NULL; - if (strncmp((char *)buffer, "/>", 2) == 0) { - buffer += 2; - } else { - bal++; - } - break; - - case XML_TAG_END: - if (bal <= 0) - return NULL; - buffer = buffer + tagLen + 2; - bal--; - break; - } - } while (bal != 0); - - return buffer; -} - -uint8_t *XML_DOM_getAttr(uint8_t *buffer, uint8_t **pName, int32_t *nameLen, - uint8_t **pValue, int32_t *valueLen) -{ - uint8_t charQuoted; - - if (NULL == buffer) { - XML_ERROR(XML_ERROR_BUFFER_NULL); - return NULL; - } - - /* Ignore the tag */ - if (*buffer == '<') { - buffer++; - /* Ignore the STag */ - while (buffer != NULL && XML_IS_NAMECHAR(*buffer)) - buffer++; - if (NULL == buffer) - return NULL; - } - - if (NULL == (buffer = xml_ignore_blank(buffer))) { - XML_ERROR(XML_ERROR_BUFFER_NULL); - return NULL; - } - - /* Name */ - *pName = buffer; - while (buffer != NULL && XML_IS_NAMECHAR(*buffer)) - buffer++; - if (NULL == buffer) { - XML_ERROR(XML_ERROR_ATTR_NAME); - return NULL; - } - *nameLen = buffer - *pName; - if (*nameLen <= 0) { - XML_ERROR(XML_ERROR_ATTR_NAME); - return NULL; - } - - /* '=' */ - buffer = xml_ignore_blank(buffer); - if (NULL == buffer || *buffer != '=') { - XML_ERROR(XML_ERROR_ATTR_MISSED_EQUAL); - return NULL; - } - - /* Value */ - buffer++; - buffer = xml_ignore_blank(buffer); - if (NULL == buffer || (*buffer != '"' && *buffer != '\'')) { - XML_ERROR(XML_ERROR_ATTR_VALUE); - return NULL; - } - charQuoted = *buffer++; - *pValue = buffer; - while (*buffer != '\0' && *buffer != charQuoted) - buffer++; - if (*buffer != charQuoted) { - XML_ERROR(XML_ERROR_ATTR_VALUE); - return NULL; - } - *valueLen = buffer - *pValue; - - XML_ERROR(XML_ERROR_OK); - - return buffer + 1; -} - -uint8_t *XML_DOM_getValue(uint8_t *buffer, uint8_t **pValue, int32_t *valueLen) -{ - uint8_t *pEnd; - - if (NULL == buffer) { - XML_ERROR(XML_ERROR_BUFFER_NULL); - return NULL; - } - - /* Ignore the STag */ - if (*buffer == '<') { - buffer++; - /* If it's an end_tag, no value should be returned */ - if (*buffer == '/') { - *valueLen = 0; - XML_ERROR(XML_ERROR_NOVALUE); - return NULL; - } - - while (buffer != NULL && XML_IS_NAMECHAR(*buffer)) - buffer++; - if (NULL == buffer) { - XML_ERROR(XML_ERROR_BUFFER_NULL); - return NULL; - } - - if (NULL == (buffer = xml_goto_tagend(buffer))) { - XML_ERROR(XML_ERROR_PROPERTY_END); - return NULL; - } - } - - /* <test/> node found */ - if (*buffer == '/') { - if (*(buffer + 1) != '>') { - XML_ERROR(XML_ERROR_PROPERTY_END); - return NULL; - } - XML_ERROR(XML_ERROR_OK); - *valueLen = 0; - return buffer; - } - - if (*buffer == '>') - buffer++; - - if (NULL == (buffer = xml_ignore_blank(buffer))) { - XML_ERROR(XML_ERROR_BUFFER_NULL); - return NULL; - } - - /* the following is a tag instead of the value */ - if (*buffer == '<') { /* nono value, such as <test></test> */ - buffer++; - if (*buffer != '/') { - XML_ERROR(XML_ERROR_ENDTAG); - return NULL; - } - *valueLen = 0; - XML_ERROR(XML_ERROR_OK); - return NULL; - } - - *pValue = buffer; - pEnd = NULL; - while (*buffer != '\0' && *buffer != '<') { - if (!XML_IS_WHITESPACE(*buffer)) - pEnd = buffer; - buffer++; - } - if (*buffer != '<' || pEnd == NULL) { - XML_ERROR(XML_ERROR_VALUE); - return NULL; - } - - *valueLen = pEnd - *pValue + 1; - - buffer++; - if (*buffer != '/') { - XML_ERROR(XML_ERROR_ENDTAG); - return NULL; - } - - XML_ERROR(XML_ERROR_OK); - - return buffer - 1; -} - -uint8_t *XML_DOM_getTag(uint8_t *buffer, int32_t *tagLen, int32_t *tagType) -{ - uint8_t *pStart; - - /* WARNING: <!-- --> comment is not supported in this verison */ - if (NULL == buffer) { - XML_ERROR(XML_ERROR_BUFFER_NULL); - return NULL; - } - - do { - while (*buffer != '<') { - if (*buffer == '\0') { - XML_ERROR(XML_ERROR_BUFFER_NULL); - return NULL; - } - - if (*buffer == '\"' || *buffer == '\'') { - uint8_t charQuoted = *buffer; - buffer++; - while (*buffer != '\0' && *buffer != charQuoted) - buffer++; - if (*buffer == '\0') { - XML_ERROR(XML_ERROR_BUFFER_NULL); - return NULL; - } - } - buffer++; - } - buffer++; - } while (*buffer == '!' || *buffer == '?'); - - pStart = buffer - 1; - - if (*buffer == '/') { - buffer++; - *tagType = XML_TAG_END; - } else { - /* check here if it is self-end-tag */ - uint8_t *pCheck = xml_goto_tagend(pStart); - if (pCheck == NULL) { - XML_ERROR(XML_ERROR_PROPERTY_END); - return NULL; - } - - if (*pCheck == '>') - *tagType = XML_TAG_START; - else if (strncmp((char *)pCheck, "/>", 2) == 0) - *tagType = XML_TAG_SELF; - else { - XML_ERROR(XML_ERROR_PROPERTY_END); - return NULL; - } - } - - while (buffer != NULL && XML_IS_NAMECHAR(*buffer)) - buffer++; - if (NULL == buffer) { - XML_ERROR(XML_ERROR_BUFFER_NULL); - return NULL; - } - - if (*tagType == XML_TAG_END) - *tagLen = buffer - pStart - 2; - else - *tagLen = buffer - pStart - 1; - - XML_ERROR(XML_ERROR_OK); - - return pStart; -} - -uint8_t *XML_DOM_getNode(uint8_t *buffer, const uint8_t *const node) -{ - uint8_t *pStart; - uint8_t buf[XML_MAX_PROPERTY_LEN + 2]; - uint8_t *nodeStr = buf; - uint8_t *retPtr = NULL; - int32_t tagLen, tagType; - uint8_t *lastNode = (uint8_t *)""; - - if (NULL == buffer) { - XML_ERROR(XML_ERROR_BUFFER_NULL); - return NULL; - } - - strncpy((char *)nodeStr, (char *)node, XML_MAX_PROPERTY_LEN); - strcat((char *)nodeStr, "\\"); - pStart = (uint8_t *)strchr((char *)nodeStr, '\\'); - - while (pStart != NULL) { - *pStart = '\0'; - - /* get the first start_tag from buffer */ - if (NULL == (buffer = XML_DOM_getTag(buffer, &tagLen, &tagType))) { - XML_ERROR(XML_ERROR_NO_SUCH_NODE); - return NULL; - } - - if (tagType == XML_TAG_END) { - if (0 == - strncmp((char *)lastNode, (char *)(buffer + 2), strlen((char *)lastNode))) - XML_ERROR(XML_ERROR_NO_SUCH_NODE); - else - XML_ERROR(XML_ERROR_NO_START_TAG); - return NULL; - } - - /* wrong node, contiue to fetch the next node */ - if ((int32_t) strlen((char *)nodeStr) != tagLen - || strncmp((char *)nodeStr, (char *)(buffer + 1), tagLen) != 0) { - /* we should ignore all the middle code */ - buffer = xml_match_tag(buffer); - continue; - } - - retPtr = buffer; /* retPtr starts with '<xxx>' */ - buffer += (tagLen + 1); - - if (tagType == XML_TAG_SELF) { - nodeStr = pStart + 1; - break; - } - - lastNode = nodeStr; - nodeStr = pStart + 1; - pStart = (uint8_t *)strchr((char *)nodeStr, '\\'); - } - - /* Check 5: nodeStr should be empty here */ - if (*nodeStr != '\0') { - XML_ERROR(XML_ERROR_NO_SUCH_NODE); - return NULL; - } - - XML_ERROR(XML_ERROR_OK); - - return retPtr; -} - -uint8_t *XML_DOM_getNodeValue(uint8_t *buffer, uint8_t *node, - uint8_t **value, int32_t *valueLen) -{ - uint8_t *pStart; - uint8_t *lastTag; - - if (NULL == node || NULL == buffer) { - XML_ERROR(XML_ERROR_BUFFER_NULL); - return NULL; - } - - lastTag = node + strlen((char *)node) - 1; - while (lastTag >= node && *lastTag != '\\') - lastTag--; - lastTag++; - - if (NULL == (pStart = XML_DOM_getNode(buffer, node))) - return NULL; - - pStart += (strlen((char *)lastTag) + 1); - - if (NULL == (pStart = xml_goto_tagend(pStart))) { - XML_ERROR(XML_ERROR_PROPERTY_END); - return NULL; - } - - if (NULL == (pStart = XML_DOM_getValue(pStart, value, valueLen))) - return NULL; - - /* Check the end tag */ -#ifdef XML_DOM_CHECK_ENDTAG - if (strncmp((char *)pStart, "/>", 2) == 0) { - - } else if (strncmp((char *)lastTag, (char *)(pStart + 2), strlen((char *)lastTag)) != - 0) { - XML_ERROR(XML_ERROR_ENDTAG); - return NULL; - } -#endif - - XML_ERROR(XML_ERROR_OK); - - return *value; -} - -uint8_t *XML_DOM_getNextNode(uint8_t *buffer, uint8_t **pNodeName, int32_t *nodenameLen) -{ - int32_t tagType; - - if (NULL == buffer) - return NULL; - - do { - if (NULL == - (buffer = XML_DOM_getTag(buffer + 1, nodenameLen, &tagType))) { - XML_ERROR(XML_ERROR_NO_SUCH_NODE); - return NULL; - } - } while (tagType == XML_TAG_END); - - *pNodeName = buffer + 1; - - XML_ERROR(XML_ERROR_OK); - - return buffer; -} - -#endif /* XML_DOM_PARSER */ - -#ifdef WBXML_DOM_PARSER - -#ifdef WBXML_OLD_VERSION -uint8_t *WBXML_DOM_getNode(uint8_t *buffer, int32_t bufferLen, - uint8_t *node) -{ - int32_t i = 0, j = 0; - - if (NULL == buffer || node == NULL) { - XML_ERROR(XML_ERROR_BUFFER_NULL); - return NULL; - } - - while (i < bufferLen) { - if (WBXML_GET_TAG(buffer[i]) == WBXML_GET_TAG(node[j])) { - j++; - if (node[j] == '\0') - break; - - /* Check if there is the content(it should have content) */ - if (!WBXML_HAS_CONTENT(buffer[i])) { - /*XML_ERROR(WBXML_ERROR_MISSED_CONTENT); */ - XML_ERROR(XML_ERROR_NO_SUCH_NODE); - return NULL; - } - - /* Ignore the attrib filed */ - if (WBXML_HAS_ATTR(buffer[i])) { - while (i < bufferLen && buffer[i] != WBXML_ATTR_END) - i++; - if (i >= bufferLen) - break; - } - } - i++; - - /* Ignore the content filed */ - if (buffer[i] == WBXML_STR_I) { - while (i < bufferLen && buffer[i] != WBXML_END) - i++; - if (i >= bufferLen) - break; - i++; - } - } - - if (i >= bufferLen) { - XML_ERROR(XML_ERROR_NO_SUCH_NODE); - return NULL; - } - - XML_ERROR(XML_ERROR_OK); - - return buffer + i + 1; -} - -uint8_t *WBXML_DOM_getNodeValue(uint8_t *buffer, int32_t bufferLen, - uint8_t *node, - uint8_t **value, int32_t *valueLen) -{ - int32_t i; - uint8_t *pEnd; - - *value = NULL; - *valueLen = 0; - - pEnd = buffer + bufferLen; - buffer = WBXML_DOM_getNode(buffer, bufferLen, node); - if (NULL == buffer) { - XML_ERROR(XML_ERROR_NO_SUCH_NODE); - return NULL; - } - - if (*buffer == WBXML_OPAUE) { - buffer++; - *valueLen = WBXML_GetUintVar(buffer, &i); - if (*valueLen < 0) { - XML_ERROR(WBXML_ERROR_MBUINT32); - return NULL; - } - buffer += i; - *value = buffer; - return *value; - } - - if (*buffer != WBXML_STR_I) { - XML_ERROR(WBXML_ERROR_MISSED_STARTTAG); - return NULL; - } - - buffer++; - - i = 0; - while ((buffer + i) < pEnd && buffer[i] != WBXML_END) - i++; - - if (buffer[i] != WBXML_END) { - XML_ERROR(WBXML_ERROR_MISSED_ENDTAG); - return NULL; - } - - *value = buffer; - *valueLen = i; - XML_ERROR(XML_ERROR_OK); - - return *value; -} -#endif /* WBXML_OLD_VERSION */ - -#define MAX_UINT_VAR_BYTE 4 -#define UINTVAR_INVALID -1 -int32_t WBXML_GetUintVar(const uint8_t *const buffer, int32_t *len) -{ - int32_t i, byteLen; - int32_t sum; - - byteLen = 0; - while ((buffer[byteLen] & 0x80) > 0 && byteLen < MAX_UINT_VAR_BYTE) - byteLen++; - - if (byteLen > MAX_UINT_VAR_BYTE) - return UINTVAR_INVALID; - - *len = byteLen + 1; - sum = buffer[byteLen]; - for (i = byteLen - 1; i >= 0; i--) - sum += ((buffer[i] & 0x7F) << 7 * (byteLen - i)); - - return sum; -} - -XML_BOOL WBXML_DOM_Init(WBXML * pWbxml, uint8_t *buffer, - int32_t bufferLen) -{ - int32_t num, len; - - pWbxml->End = buffer + bufferLen; - pWbxml->version = *buffer++; - if (UINTVAR_INVALID == (num = WBXML_GetUintVar(buffer, &len))) - return XML_FALSE; - buffer += len; - pWbxml->publicid = num; - if (UINTVAR_INVALID == (num = WBXML_GetUintVar(buffer, &len))) - return XML_FALSE; - buffer += len; - pWbxml->charset = num; - if (UINTVAR_INVALID == (num = WBXML_GetUintVar(buffer, &len))) - return XML_FALSE; - buffer += len; - pWbxml->strTable = buffer; - pWbxml->strTableLen = num; - buffer += num; - pWbxml->curPtr = pWbxml->Content = buffer; - pWbxml->depth = 0; - - return XML_TRUE; -} - -void WBXML_DOM_Rewind(WBXML * pWbxml) -{ - pWbxml->curPtr = pWbxml->Content; -} - -XML_BOOL WBXML_DOM_Eof(WBXML * pWbxml) -{ - if (pWbxml->curPtr > pWbxml->End) - return XML_TRUE; - - return XML_FALSE; -} - -uint8_t WBXML_DOM_GetTag(WBXML * pWbxml) -{ - uint8_t tagChar; - - if (pWbxml->curPtr > pWbxml->End) - return XML_EOF; - - tagChar = *pWbxml->curPtr; - pWbxml->curPtr++; - - if (WBXML_GET_TAG(tagChar) == WBXML_CONTENT_END) - pWbxml->depth--; - else - pWbxml->depth++; - - return tagChar; -} - -uint8_t WBXML_DOM_GetChar(WBXML * pWbxml) -{ - return *pWbxml->curPtr++; -} - -void WBXML_DOM_Seek(WBXML * pWbxml, int32_t offset) -{ - pWbxml->curPtr += offset; -} - -uint8_t WBXML_DOM_GetUIntVar(WBXML * pWbxml) -{ - int32_t num, len; - - num = WBXML_GetUintVar(pWbxml->curPtr, &len); - pWbxml->curPtr += len; - - return (uint8_t)num; -} - -#ifdef XML_TREE_STRUCTURE - -#ifdef DEBUG_MODE -static int32_t malloc_times = 0; -static int32_t free_times = 0; -void XML_PrintMallocInfo() -{ - printf("====XML_PrintMallocInfo====\n"); - printf(" Total malloc times:%d\n", malloc_times); - printf(" Total free times:%d\n", free_times); - printf("===========================\n"); -} -#endif - -void *xml_malloc(int32_t size) -{ -#ifdef DEBUG_MODE - malloc_times++; -#endif - return malloc(size); -} - -void xml_free(void *buffer) -{ -#ifdef DEBUG_MODE - free_times++; -#endif - free(buffer); -} - -XML_TREE *xml_tree_fillnode(uint8_t **buf, int32_t tagLen) -{ - XML_TREE *Tree; - uint8_t *pAttr, *pName, *pValue; - int32_t nameLen, valueLen; - uint8_t *buffer = *buf; - - if (NULL == (Tree = (XML_TREE *) xml_malloc(sizeof(XML_TREE)))) - return NULL; - memset(Tree, 0, sizeof(XML_TREE)); - - strncpy((char *)Tree->tag, (char *)++buffer, tagLen); - buffer += tagLen; - pAttr = buffer; - - /* attribute */ - while (NULL != - (pAttr = - XML_DOM_getAttr(pAttr, &pName, &nameLen, &pValue, - &valueLen))) { - XML_TREE_ATTR *attr; - if (NULL == - (attr = (XML_TREE_ATTR *) xml_malloc(sizeof(XML_TREE_ATTR)))) - return NULL; - memset(attr, 0, sizeof(XML_TREE_ATTR)); - strncpy((char *)attr->name, (char *)pName, nameLen); - strncpy((char *)attr->value, (char *)pValue, valueLen); - buffer = pValue + valueLen + 1; - - if (NULL != Tree->attr) // no attribute now - Tree->last_attr->next = attr; - else - Tree->attr = attr; - Tree->last_attr = attr; - } - - /* value */ - pAttr = XML_DOM_getValue(buffer, &pValue, &valueLen); - if (pAttr != NULL && valueLen > 0) { - strncpy((char *)Tree->value, (char *)pValue, valueLen); - buffer = pValue + valueLen; - } - - *buf = buffer; - return Tree; -} - -XML_TREE *XML_makeTree(uint8_t **buf) -{ - uint8_t *pBuf; - int32_t valueLen, tagType; - uint8_t *buffer = *buf; - XML_TREE *TreeHead = NULL; - - if (NULL == (buffer = XML_DOM_getTag(buffer, &valueLen, &tagType))) - return NULL; - if (XML_TAG_END == tagType) - return NULL; - if (NULL == (TreeHead = xml_tree_fillnode(&buffer, valueLen))) - return NULL; - if (XML_TAG_SELF == tagType) { - *buf = buffer; - return TreeHead; - } - - do { - if (NULL == (pBuf = XML_DOM_getTag(buffer, &valueLen, &tagType))) - return NULL; - - switch (tagType) { - case XML_TAG_SELF: - case XML_TAG_START: - if (NULL == TreeHead->child) - TreeHead->child = XML_makeTree(&buffer); - else if (NULL == TreeHead->child->last_brother) { - TreeHead->child->brother = XML_makeTree(&buffer); - TreeHead->child->last_brother = TreeHead->child->brother; - } else { - TreeHead->child->last_brother->brother = - XML_makeTree(&buffer); - TreeHead->child->last_brother = - TreeHead->child->last_brother->brother; - } - break; - case XML_TAG_END: - *buf = pBuf; - return TreeHead; - } - buffer++; - } while (1); -} - -void XML_freeTree(XML_TREE * pTree) -{ - XML_TREE *p, *pNext; - XML_TREE_ATTR *pa, *lastpa; - - if (NULL == pTree) - return; - - p = pTree->brother; - while (NULL != p) { - pNext = p->brother; - p->brother = NULL; - XML_freeTree(p); - p = pNext; - } - - if (NULL != pTree->child) - XML_freeTree(pTree->child); - - pa = pTree->attr; - while (NULL != pa) { - lastpa = pa; - pa = pa->next; - xml_free(lastpa); - } - xml_free(pTree); -} - -#endif /* XML_TREE_STRUCTURE */ - -#endif /* WBXML_DOM_PARSER */ diff --git a/media/mca/filterfw/native/core/gl_env.cpp b/media/mca/filterfw/native/core/gl_env.cpp index 73768fe..84dad8c 100644 --- a/media/mca/filterfw/native/core/gl_env.cpp +++ b/media/mca/filterfw/native/core/gl_env.cpp @@ -26,6 +26,8 @@ #include <string> #include <EGL/eglext.h> +#include <gui/GLConsumer.h> + namespace android { namespace filterfw { @@ -160,9 +162,9 @@ bool GLEnv::InitWithNewContext() { } // Create dummy surface using a GLConsumer - surfaceTexture_ = new GLConsumer(0); - window_ = new Surface(static_cast<sp<IGraphicBufferProducer> >( - surfaceTexture_->getBufferQueue())); + sp<BufferQueue> bq = new BufferQueue(); + surfaceTexture_ = new GLConsumer(bq, 0); + window_ = new Surface(static_cast<sp<IGraphicBufferProducer> >(bq)); surfaces_[0] = SurfaceWindowPair(eglCreateWindowSurface(display(), config, window_.get(), NULL), NULL); if (CheckEGLError("eglCreateWindowSurface")) return false; diff --git a/media/mca/filterfw/native/core/gl_env.h b/media/mca/filterfw/native/core/gl_env.h index 81e1e9d..a709638 100644 --- a/media/mca/filterfw/native/core/gl_env.h +++ b/media/mca/filterfw/native/core/gl_env.h @@ -31,6 +31,9 @@ #include <gui/Surface.h> namespace android { + +class GLConsumer; + namespace filterfw { class ShaderProgram; diff --git a/media/tests/MediaFrameworkTest/Android.mk b/media/tests/MediaFrameworkTest/Android.mk index c9afa19..1e6b2e7 100644 --- a/media/tests/MediaFrameworkTest/Android.mk +++ b/media/tests/MediaFrameworkTest/Android.mk @@ -7,7 +7,7 @@ LOCAL_SRC_FILES := $(call all-subdir-java-files) LOCAL_JAVA_LIBRARIES := android.test.runner -LOCAL_STATIC_JAVA_LIBRARIES := easymocklib +LOCAL_STATIC_JAVA_LIBRARIES := easymocklib mockito-target LOCAL_PACKAGE_NAME := mediaframeworktest diff --git a/media/tests/MediaFrameworkTest/AndroidManifest.xml b/media/tests/MediaFrameworkTest/AndroidManifest.xml index b698705..91ee2c6 100644 --- a/media/tests/MediaFrameworkTest/AndroidManifest.xml +++ b/media/tests/MediaFrameworkTest/AndroidManifest.xml @@ -71,4 +71,9 @@ android:label="Media Power tests InstrumentationRunner"> </instrumentation> + <instrumentation android:name=".MediaFrameworkIntegrationTestRunner" + android:targetPackage="com.android.mediaframeworktest" + android:label="MediaFramework integration tests InstrumentationRunner"> + </instrumentation> + </manifest> diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkIntegrationTestRunner.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkIntegrationTestRunner.java new file mode 100644 index 0000000..7751fcc --- /dev/null +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkIntegrationTestRunner.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2013 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 com.android.mediaframeworktest; + +import android.os.Bundle; +import android.test.InstrumentationTestRunner; +import android.test.InstrumentationTestSuite; +import android.util.Log; + +import com.android.mediaframeworktest.integration.CameraBinderTest; +import com.android.mediaframeworktest.integration.CameraDeviceBinderTest; + +import junit.framework.TestSuite; + +/** + * Instrumentation Test Runner for all media framework integration tests. + * + * Running all tests: + * + * adb shell am instrument -w com.android.mediaframeworktest/.MediaFrameworkIntegrationTestRunner + */ + +public class MediaFrameworkIntegrationTestRunner extends InstrumentationTestRunner { + + private static final String TAG = "MediaFrameworkIntegrationTestRunner"; + + public static int mCameraId = 0; + + @Override + public TestSuite getAllTests() { + TestSuite suite = new InstrumentationTestSuite(this); + suite.addTestSuite(CameraBinderTest.class); + suite.addTestSuite(CameraDeviceBinderTest.class); + return suite; + } + + @Override + public ClassLoader getLoader() { + return MediaFrameworkIntegrationTestRunner.class.getClassLoader(); + } + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + String cameraId = (String) icicle.get("camera_id"); + if (cameraId != null) { + try { + Log.v(TAG, + String.format("Reading camera_id from icicle: '%s'", cameraId)); + mCameraId = Integer.parseInt(cameraId); + } + catch (NumberFormatException e) { + Log.e(TAG, String.format("Failed to convert camera_id to integer")); + } + } + } +} diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkTestRunner.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkTestRunner.java index 92ac9eb..cbb6642 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkTestRunner.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkTestRunner.java @@ -50,7 +50,7 @@ import android.test.InstrumentationTestSuite; * Running all tests: * * adb shell am instrument \ - * -w com.android.smstests.MediaPlayerInstrumentationTestRunner + * -w com.android.mediaframeworktest/.MediaFrameworkTestRunner */ public class MediaFrameworkTestRunner extends InstrumentationTestRunner { diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java index 62af3f3..64b12b7 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java @@ -48,6 +48,8 @@ public class MediaFrameworkUnitTestRunner extends InstrumentationTestRunner { addMediaRecorderStateUnitTests(suite); addMediaPlayerStateUnitTests(suite); addMediaScannerUnitTests(suite); + addCameraUnitTests(suite); + addImageReaderTests(suite); return suite; } @@ -56,6 +58,18 @@ public class MediaFrameworkUnitTestRunner extends InstrumentationTestRunner { return MediaFrameworkUnitTestRunner.class.getClassLoader(); } + private void addCameraUnitTests(TestSuite suite) { + suite.addTestSuite(CameraUtilsDecoratorTest.class); + suite.addTestSuite(CameraUtilsRuntimeExceptionTest.class); + suite.addTestSuite(CameraUtilsUncheckedThrowTest.class); + suite.addTestSuite(CameraUtilsBinderDecoratorTest.class); + suite.addTestSuite(CameraMetadataTest.class); + } + + private void addImageReaderTests(TestSuite suite) { + suite.addTestSuite(ImageReaderTest.class); + } + // Running all unit tests checking the state machine may be time-consuming. private void addMediaMetadataRetrieverStateUnitTests(TestSuite suite) { suite.addTestSuite(MediaMetadataRetrieverTest.class); diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CameraTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CameraTest.java index 2f864d7..7f23ba5 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CameraTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CameraTest.java @@ -36,7 +36,12 @@ import java.io.*; /** * Junit / Instrumentation test case for the camera api - + * + * To run only tests in this class: + * + * adb shell am instrument \ + * -e class com.android.mediaframeworktest.functional.CameraTest \ + * -w com.android.mediaframeworktest/.MediaFrameworkTestRunner */ public class CameraTest extends ActivityInstrumentationTestCase<MediaFrameworkTest> { private String TAG = "CameraTest"; diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java new file mode 100644 index 0000000..d157478 --- /dev/null +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java @@ -0,0 +1,237 @@ +/* + * Copyright (C) 2013 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 com.android.mediaframeworktest.integration; + +import android.hardware.CameraInfo; +import android.hardware.ICamera; +import android.hardware.ICameraClient; +import android.hardware.ICameraServiceListener; +import android.hardware.IProCameraCallbacks; +import android.hardware.IProCameraUser; +import android.hardware.camera2.CameraMetadata; +import android.hardware.camera2.ICameraDeviceCallbacks; +import android.hardware.camera2.ICameraDeviceUser; +import android.hardware.camera2.impl.CameraMetadataNative; +import android.hardware.camera2.utils.BinderHolder; +import android.hardware.camera2.utils.CameraBinderDecorator; +import android.os.Binder; +import android.os.IBinder; +import android.os.RemoteException; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; +import android.util.Log; + +/** + * <p> + * Junit / Instrumentation test case for the camera2 api + * </p> + * <p> + * To run only tests in this class: + * </p> + * + * <pre> + * adb shell am instrument \ + * -e class com.android.mediaframeworktest.integration.CameraBinderTest \ + * -w com.android.mediaframeworktest/.MediaFrameworkIntegrationTestRunner + * </pre> + */ +public class CameraBinderTest extends AndroidTestCase { + static String TAG = "CameraBinderTest"; + + protected CameraBinderTestUtils mUtils; + + public CameraBinderTest() { + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + + mUtils = new CameraBinderTestUtils(getContext()); + } + + @SmallTest + public void testNumberOfCameras() throws Exception { + + int numCameras = mUtils.getCameraService().getNumberOfCameras(); + assertTrue("At least this many cameras: " + mUtils.getGuessedNumCameras(), + numCameras >= mUtils.getGuessedNumCameras()); + Log.v(TAG, "Number of cameras " + numCameras); + } + + @SmallTest + public void testCameraInfo() throws Exception { + for (int cameraId = 0; cameraId < mUtils.getGuessedNumCameras(); ++cameraId) { + + CameraInfo info = new CameraInfo(); + info.info.facing = -1; + info.info.orientation = -1; + + assertTrue( + "Camera service returned info for camera " + cameraId, + mUtils.getCameraService().getCameraInfo(cameraId, info) == + CameraBinderTestUtils.NO_ERROR); + assertTrue("Facing was not set for camera " + cameraId, info.info.facing != -1); + assertTrue("Orientation was not set for camera " + cameraId, + info.info.orientation != -1); + + Log.v(TAG, "Camera " + cameraId + " info: facing " + info.info.facing + + ", orientation " + info.info.orientation); + } + } + + static abstract class DummyBase extends Binder implements android.os.IInterface { + @Override + public IBinder asBinder() { + return this; + } + } + + static class DummyCameraClient extends DummyBase implements ICameraClient { + } + + @SmallTest + public void testConnect() throws Exception { + for (int cameraId = 0; cameraId < mUtils.getGuessedNumCameras(); ++cameraId) { + + ICameraClient dummyCallbacks = new DummyCameraClient(); + + String clientPackageName = getContext().getPackageName(); + + BinderHolder holder = new BinderHolder(); + CameraBinderDecorator.newInstance(mUtils.getCameraService()) + .connect(dummyCallbacks, cameraId, clientPackageName, + CameraBinderTestUtils.USE_CALLING_UID, holder); + ICamera cameraUser = ICamera.Stub.asInterface(holder.getBinder()); + assertNotNull(String.format("Camera %s was null", cameraId), cameraUser); + + Log.v(TAG, String.format("Camera %s connected", cameraId)); + + cameraUser.disconnect(); + } + } + + static class DummyProCameraCallbacks extends DummyBase implements IProCameraCallbacks { + } + + @SmallTest + public void testConnectPro() throws Exception { + for (int cameraId = 0; cameraId < mUtils.getGuessedNumCameras(); ++cameraId) { + + IProCameraCallbacks dummyCallbacks = new DummyProCameraCallbacks(); + + String clientPackageName = getContext().getPackageName(); + + BinderHolder holder = new BinderHolder(); + CameraBinderDecorator.newInstance(mUtils.getCameraService()) + .connectPro(dummyCallbacks, cameraId, + clientPackageName, CameraBinderTestUtils.USE_CALLING_UID, holder); + IProCameraUser cameraUser = IProCameraUser.Stub.asInterface(holder.getBinder()); + assertNotNull(String.format("Camera %s was null", cameraId), cameraUser); + + Log.v(TAG, String.format("Camera %s connected", cameraId)); + + cameraUser.disconnect(); + } + } + + static class DummyCameraDeviceCallbacks extends ICameraDeviceCallbacks.Stub { + + @Override + public void onCameraError(int errorCode) { + } + + @Override + public void onCameraIdle() { + } + + @Override + public void onCaptureStarted(int requestId, long timestamp) { + } + + @Override + public void onResultReceived(int frameId, CameraMetadataNative result) + throws RemoteException { + } + } + + @SmallTest + public void testConnectDevice() throws Exception { + for (int cameraId = 0; cameraId < mUtils.getGuessedNumCameras(); ++cameraId) { + + ICameraDeviceCallbacks dummyCallbacks = new DummyCameraDeviceCallbacks(); + + String clientPackageName = getContext().getPackageName(); + + BinderHolder holder = new BinderHolder(); + CameraBinderDecorator.newInstance(mUtils.getCameraService()) + .connectDevice(dummyCallbacks, cameraId, + clientPackageName, CameraBinderTestUtils.USE_CALLING_UID, holder); + ICameraDeviceUser cameraUser = ICameraDeviceUser.Stub.asInterface(holder.getBinder()); + assertNotNull(String.format("Camera %s was null", cameraId), cameraUser); + + Log.v(TAG, String.format("Camera %s connected", cameraId)); + + cameraUser.disconnect(); + } + } + + static class DummyCameraServiceListener extends ICameraServiceListener.Stub { + @Override + public void onStatusChanged(int status, int cameraId) + throws RemoteException { + Log.v(TAG, String.format("Camera %d has status changed to 0x%x", cameraId, status)); + } + } + + /** + * <pre> + * adb shell am instrument \ + * -e class 'com.android.mediaframeworktest.integration.CameraBinderTest#testAddRemoveListeners' \ + * -w com.android.mediaframeworktest/.MediaFrameworkIntegrationTestRunner + * </pre> + */ + @SmallTest + public void testAddRemoveListeners() throws Exception { + for (int cameraId = 0; cameraId < mUtils.getGuessedNumCameras(); ++cameraId) { + + ICameraServiceListener listener = new DummyCameraServiceListener(); + + assertTrue( + "Listener was removed before added", + mUtils.getCameraService().removeListener(listener) == + CameraBinderTestUtils.BAD_VALUE); + + assertTrue("Listener was not added", + mUtils.getCameraService().addListener(listener) == + CameraBinderTestUtils.NO_ERROR); + assertTrue( + "Listener was wrongly added again", + mUtils.getCameraService().addListener(listener) == + CameraBinderTestUtils.ALREADY_EXISTS); + + assertTrue( + "Listener was not removed", + mUtils.getCameraService().removeListener(listener) == + CameraBinderTestUtils.NO_ERROR); + assertTrue( + "Listener was wrongly removed again", + mUtils.getCameraService().removeListener(listener) == + CameraBinderTestUtils.BAD_VALUE); + } + } +} diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTestUtils.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTestUtils.java new file mode 100644 index 0000000..1be2a62 --- /dev/null +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTestUtils.java @@ -0,0 +1,93 @@ + +package com.android.mediaframeworktest.integration; + +import static org.junit.Assert.assertNotNull; + +import android.content.Context; +import android.content.pm.FeatureInfo; +import android.content.pm.PackageManager; +import android.hardware.ICameraService; +import android.os.IBinder; +import android.os.ServiceManager; +import android.util.Log; + +public class CameraBinderTestUtils { + private final ICameraService mCameraService; + private int mGuessedNumCameras; + + static final String CAMERA_SERVICE_BINDER_NAME = "media.camera"; + + protected static final int USE_CALLING_UID = -1; + protected static final int BAD_VALUE = -22; + protected static final int INVALID_OPERATION = -38; + protected static final int ALREADY_EXISTS = -17; + public static final int NO_ERROR = 0; + private final Context mContext; + + public CameraBinderTestUtils(Context context) { + + mContext = context; + + guessNumCameras(); + + IBinder cameraServiceBinder = ServiceManager + .getService(CameraBinderTestUtils.CAMERA_SERVICE_BINDER_NAME); + assertNotNull("Camera service IBinder should not be null", cameraServiceBinder); + + this.mCameraService = ICameraService.Stub.asInterface(cameraServiceBinder); + assertNotNull("Camera service should not be null", getCameraService()); + } + + private void guessNumCameras() { + + /** + * Why do we need this? This way we have no dependency on getNumCameras + * actually working. On most systems there are only 0, 1, or 2 cameras, + * and this covers that 'usual case'. On other systems there might be 3+ + * cameras, but this will at least check the first 2. + */ + this.mGuessedNumCameras = 0; + + // Front facing camera + if (CameraBinderTestUtils.isFeatureAvailable(mContext, + PackageManager.FEATURE_CAMERA_FRONT)) { + this.mGuessedNumCameras = getGuessedNumCameras() + 1; + } + + // Back facing camera + if (CameraBinderTestUtils.isFeatureAvailable(mContext, + PackageManager.FEATURE_CAMERA)) { + this.mGuessedNumCameras = getGuessedNumCameras() + 1; + } + + // Any facing camera + if (getGuessedNumCameras() == 0 + && CameraBinderTestUtils.isFeatureAvailable(mContext, + PackageManager.FEATURE_CAMERA_ANY)) { + this.mGuessedNumCameras = getGuessedNumCameras() + 1; + } + + Log.v(CameraBinderTest.TAG, "Guessing there are at least " + getGuessedNumCameras() + + " cameras"); + } + + final static public boolean isFeatureAvailable(Context context, String feature) { + final PackageManager packageManager = context.getPackageManager(); + final FeatureInfo[] featuresList = packageManager.getSystemAvailableFeatures(); + for (FeatureInfo f : featuresList) { + if (f.name != null && f.name.equals(feature)) { + return true; + } + } + + return false; + } + + ICameraService getCameraService() { + return mCameraService; + } + + int getGuessedNumCameras() { + return mGuessedNumCameras; + } +} diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java new file mode 100644 index 0000000..43ebef4 --- /dev/null +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java @@ -0,0 +1,461 @@ +/* + * Copyright (C) 2013 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 com.android.mediaframeworktest.integration; + +import android.graphics.ImageFormat; +import android.graphics.SurfaceTexture; +import android.hardware.camera2.CameraMetadata; +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CaptureRequest; +import android.hardware.camera2.ICameraDeviceCallbacks; +import android.hardware.camera2.ICameraDeviceUser; +import android.hardware.camera2.impl.CameraMetadataNative; +import android.hardware.camera2.utils.BinderHolder; +import android.media.Image; +import android.media.ImageReader; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.RemoteException; +import android.os.SystemClock; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; +import android.util.Log; +import android.view.Surface; + +import static android.hardware.camera2.CameraDevice.TEMPLATE_PREVIEW; + +import com.android.mediaframeworktest.MediaFrameworkIntegrationTestRunner; + +import org.mockito.ArgumentMatcher; +import org.mockito.ArgumentCaptor; +import static org.mockito.Mockito.*; + +public class CameraDeviceBinderTest extends AndroidTestCase { + private static String TAG = "CameraDeviceBinderTest"; + // Number of streaming callbacks need to check. + private static int NUM_CALLBACKS_CHECKED = 10; + // Wait for capture result timeout value: 1500ms + private final static int WAIT_FOR_COMPLETE_TIMEOUT_MS = 1500; + // Wait for flush timeout value: 1000ms + private final static int WAIT_FOR_FLUSH_TIMEOUT_MS = 1000; + // Wait for idle timeout value: 2000ms + private final static int WAIT_FOR_IDLE_TIMEOUT_MS = 2000; + // Wait while camera device starts working on requests + private final static int WAIT_FOR_WORK_MS = 300; + // Default size is VGA, which is mandatory camera supported image size by CDD. + private static final int DEFAULT_IMAGE_WIDTH = 640; + private static final int DEFAULT_IMAGE_HEIGHT = 480; + private static final int MAX_NUM_IMAGES = 5; + + private int mCameraId; + private ICameraDeviceUser mCameraUser; + private CameraBinderTestUtils mUtils; + private ICameraDeviceCallbacks.Stub mMockCb; + private Surface mSurface; + private HandlerThread mHandlerThread; + private Handler mHandler; + ImageReader mImageReader; + + public CameraDeviceBinderTest() { + } + + private class ImageDropperListener implements ImageReader.OnImageAvailableListener { + + @Override + public void onImageAvailable(ImageReader reader) { + Image image = reader.acquireNextImage(); + if (image != null) image.close(); + } + } + + public class DummyCameraDeviceCallbacks extends ICameraDeviceCallbacks.Stub { + + @Override + public void onCameraError(int errorCode) { + } + + @Override + public void onCameraIdle() { + } + + @Override + public void onCaptureStarted(int requestId, long timestamp) { + } + + @Override + public void onResultReceived(int frameId, CameraMetadataNative result) { + } + } + + class IsMetadataNotEmpty extends ArgumentMatcher<CameraMetadataNative> { + @Override + public boolean matches(Object obj) { + return !((CameraMetadataNative) obj).isEmpty(); + } + } + + private void createDefaultSurface() { + mImageReader = + ImageReader.newInstance(DEFAULT_IMAGE_WIDTH, + DEFAULT_IMAGE_HEIGHT, + ImageFormat.YUV_420_888, + MAX_NUM_IMAGES); + mImageReader.setOnImageAvailableListener(new ImageDropperListener(), mHandler); + mSurface = mImageReader.getSurface(); + } + + private CaptureRequest.Builder createDefaultBuilder(boolean needStream) throws Exception { + CameraMetadataNative metadata = new CameraMetadataNative(); + assertTrue(metadata.isEmpty()); + + int status = mCameraUser.createDefaultRequest(TEMPLATE_PREVIEW, /* out */metadata); + assertEquals(CameraBinderTestUtils.NO_ERROR, status); + assertFalse(metadata.isEmpty()); + + CaptureRequest.Builder request = new CaptureRequest.Builder(metadata); + assertFalse(request.isEmpty()); + assertFalse(metadata.isEmpty()); + if (needStream) { + int streamId = mCameraUser.createStream(/* ignored */10, /* ignored */20, + /* ignored */30, mSurface); + assertEquals(0, streamId); + request.addTarget(mSurface); + } + return request; + } + + private int submitCameraRequest(CaptureRequest request, boolean streaming) throws Exception { + int requestId = mCameraUser.submitRequest(request, streaming); + assertTrue("Request IDs should be non-negative", requestId >= 0); + return requestId; + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + + /** + * Workaround for mockito and JB-MR2 incompatibility + * + * Avoid java.lang.IllegalArgumentException: dexcache == null + * https://code.google.com/p/dexmaker/issues/detail?id=2 + */ + System.setProperty("dexmaker.dexcache", getContext().getCacheDir().toString()); + mUtils = new CameraBinderTestUtils(getContext()); + + // This cannot be set in the constructor, since the onCreate isn't + // called yet + mCameraId = MediaFrameworkIntegrationTestRunner.mCameraId; + + ICameraDeviceCallbacks.Stub dummyCallbacks = new DummyCameraDeviceCallbacks(); + + String clientPackageName = getContext().getPackageName(); + + mMockCb = spy(dummyCallbacks); + + BinderHolder holder = new BinderHolder(); + mUtils.getCameraService().connectDevice(mMockCb, mCameraId, + clientPackageName, CameraBinderTestUtils.USE_CALLING_UID, holder); + mCameraUser = ICameraDeviceUser.Stub.asInterface(holder.getBinder()); + assertNotNull(String.format("Camera %s was null", mCameraId), mCameraUser); + mHandlerThread = new HandlerThread(TAG); + mHandlerThread.start(); + mHandler = new Handler(mHandlerThread.getLooper()); + createDefaultSurface(); + + Log.v(TAG, String.format("Camera %s connected", mCameraId)); + } + + @Override + protected void tearDown() throws Exception { + mCameraUser.disconnect(); + mCameraUser = null; + mSurface.release(); + mImageReader.close(); + mHandlerThread.quitSafely(); + } + + @SmallTest + public void testCreateDefaultRequest() throws Exception { + CameraMetadataNative metadata = new CameraMetadataNative(); + assertTrue(metadata.isEmpty()); + + int status = mCameraUser.createDefaultRequest(TEMPLATE_PREVIEW, /* out */metadata); + assertEquals(CameraBinderTestUtils.NO_ERROR, status); + assertFalse(metadata.isEmpty()); + + } + + @SmallTest + public void testCreateStream() throws Exception { + int streamId = mCameraUser.createStream(/* ignored */10, /* ignored */20, /* ignored */30, + mSurface); + assertEquals(0, streamId); + + assertEquals(CameraBinderTestUtils.ALREADY_EXISTS, + mCameraUser.createStream(/* ignored */0, /* ignored */0, /* ignored */0, mSurface)); + + assertEquals(CameraBinderTestUtils.NO_ERROR, mCameraUser.deleteStream(streamId)); + } + + @SmallTest + public void testDeleteInvalidStream() throws Exception { + assertEquals(CameraBinderTestUtils.BAD_VALUE, mCameraUser.deleteStream(-1)); + assertEquals(CameraBinderTestUtils.BAD_VALUE, mCameraUser.deleteStream(0)); + assertEquals(CameraBinderTestUtils.BAD_VALUE, mCameraUser.deleteStream(1)); + assertEquals(CameraBinderTestUtils.BAD_VALUE, mCameraUser.deleteStream(0xC0FFEE)); + } + + @SmallTest + public void testCreateStreamTwo() throws Exception { + + // Create first stream + int streamId = mCameraUser.createStream(/* ignored */0, /* ignored */0, /* ignored */0, + mSurface); + assertEquals(0, streamId); + + assertEquals(CameraBinderTestUtils.ALREADY_EXISTS, + mCameraUser.createStream(/* ignored */0, /* ignored */0, /* ignored */0, mSurface)); + + // Create second stream with a different surface. + SurfaceTexture surfaceTexture = new SurfaceTexture(/* ignored */0); + surfaceTexture.setDefaultBufferSize(640, 480); + Surface surface2 = new Surface(surfaceTexture); + + int streamId2 = mCameraUser.createStream(/* ignored */0, /* ignored */0, /* ignored */0, + surface2); + assertEquals(1, streamId2); + + // Clean up streams + assertEquals(CameraBinderTestUtils.NO_ERROR, mCameraUser.deleteStream(streamId)); + assertEquals(CameraBinderTestUtils.NO_ERROR, mCameraUser.deleteStream(streamId2)); + } + + @SmallTest + public void testSubmitBadRequest() throws Exception { + + CaptureRequest.Builder builder = createDefaultBuilder(/* needStream */false); + CaptureRequest request1 = builder.build(); + int status = mCameraUser.submitRequest(request1, /* streaming */false); + assertEquals("Expected submitRequest to return BAD_VALUE " + + "since we had 0 surface targets set.", CameraBinderTestUtils.BAD_VALUE, status); + + builder.addTarget(mSurface); + CaptureRequest request2 = builder.build(); + status = mCameraUser.submitRequest(request2, /* streaming */false); + assertEquals("Expected submitRequest to return BAD_VALUE since " + + "the target surface wasn't registered with createStream.", + CameraBinderTestUtils.BAD_VALUE, status); + } + + @SmallTest + public void testSubmitGoodRequest() throws Exception { + + CaptureRequest.Builder builder = createDefaultBuilder(/* needStream */true); + CaptureRequest request = builder.build(); + + // Submit valid request twice. + int requestId1 = submitCameraRequest(request, /* streaming */false); + int requestId2 = submitCameraRequest(request, /* streaming */false); + assertNotSame("Request IDs should be unique for multiple requests", requestId1, requestId2); + + } + + @SmallTest + public void testSubmitStreamingRequest() throws Exception { + + CaptureRequest.Builder builder = createDefaultBuilder(/* needStream */true); + + CaptureRequest request = builder.build(); + + // Submit valid request once (non-streaming), and another time + // (streaming) + int requestId1 = submitCameraRequest(request, /* streaming */false); + + int requestIdStreaming = submitCameraRequest(request, /* streaming */true); + assertNotSame("Request IDs should be unique for multiple requests", requestId1, + requestIdStreaming); + + int status = mCameraUser.cancelRequest(-1); + assertEquals("Invalid request IDs should not be cancellable", + CameraBinderTestUtils.BAD_VALUE, status); + + status = mCameraUser.cancelRequest(requestId1); + assertEquals("Non-streaming request IDs should not be cancellable", + CameraBinderTestUtils.BAD_VALUE, status); + + status = mCameraUser.cancelRequest(requestIdStreaming); + assertEquals("Streaming request IDs should be cancellable", CameraBinderTestUtils.NO_ERROR, + status); + + } + + @SmallTest + public void testCameraInfo() throws RemoteException { + CameraMetadataNative info = new CameraMetadataNative(); + + int status = mCameraUser.getCameraInfo(/*out*/info); + assertEquals(CameraBinderTestUtils.NO_ERROR, status); + + assertFalse(info.isEmpty()); + assertNotNull(info.get(CameraCharacteristics.SCALER_AVAILABLE_FORMATS)); + } + + @SmallTest + public void testCameraCharacteristics() throws RemoteException { + CameraMetadataNative info = new CameraMetadataNative(); + + int status = mUtils.getCameraService().getCameraCharacteristics(mCameraId, /*out*/info); + assertEquals(CameraBinderTestUtils.NO_ERROR, status); + + assertFalse(info.isEmpty()); + assertNotNull(info.get(CameraCharacteristics.SCALER_AVAILABLE_FORMATS)); + } + + @SmallTest + public void testWaitUntilIdle() throws Exception { + CaptureRequest.Builder builder = createDefaultBuilder(/* needStream */true); + int requestIdStreaming = submitCameraRequest(builder.build(), /* streaming */true); + + // Test Bad case first: waitUntilIdle when there is active repeating request + int status = mCameraUser.waitUntilIdle(); + assertEquals("waitUntilIdle is invalid operation when there is active repeating request", + CameraBinderTestUtils.INVALID_OPERATION, status); + + // Test good case, waitUntilIdle when there is no active repeating request + status = mCameraUser.cancelRequest(requestIdStreaming); + assertEquals(CameraBinderTestUtils.NO_ERROR, status); + status = mCameraUser.waitUntilIdle(); + assertEquals(CameraBinderTestUtils.NO_ERROR, status); + } + + @SmallTest + public void testCaptureResultCallbacks() throws Exception { + IsMetadataNotEmpty matcher = new IsMetadataNotEmpty(); + CaptureRequest request = createDefaultBuilder(/* needStream */true).build(); + + // Test both single request and streaming request. + int requestId1 = submitCameraRequest(request, /* streaming */false); + verify(mMockCb, timeout(WAIT_FOR_COMPLETE_TIMEOUT_MS).times(1)).onResultReceived( + eq(requestId1), + argThat(matcher)); + + int streamingId = submitCameraRequest(request, /* streaming */true); + verify(mMockCb, timeout(WAIT_FOR_COMPLETE_TIMEOUT_MS).atLeast(NUM_CALLBACKS_CHECKED)) + .onResultReceived( + eq(streamingId), + argThat(matcher)); + } + + @SmallTest + public void testCaptureStartedCallbacks() throws Exception { + CaptureRequest request = createDefaultBuilder(/* needStream */true).build(); + + ArgumentCaptor<Long> timestamps = ArgumentCaptor.forClass(Long.class); + + // Test both single request and streaming request. + int requestId1 = submitCameraRequest(request, /* streaming */false); + verify(mMockCb, timeout(WAIT_FOR_COMPLETE_TIMEOUT_MS).times(1)).onCaptureStarted( + eq(requestId1), + anyLong()); + + int streamingId = submitCameraRequest(request, /* streaming */true); + verify(mMockCb, timeout(WAIT_FOR_COMPLETE_TIMEOUT_MS).atLeast(NUM_CALLBACKS_CHECKED)) + .onCaptureStarted( + eq(streamingId), + timestamps.capture()); + + long timestamp = 0; // All timestamps should be larger than 0. + for (Long nextTimestamp : timestamps.getAllValues()) { + Log.v(TAG, "next t: " + nextTimestamp + " current t: " + timestamp); + assertTrue("Captures are out of order", timestamp < nextTimestamp); + timestamp = nextTimestamp; + } + } + + @SmallTest + public void testIdleCallback() throws Exception { + int status; + CaptureRequest request = createDefaultBuilder(/* needStream */true).build(); + + // Try streaming + int streamingId = submitCameraRequest(request, /* streaming */true); + + // Wait a bit to fill up the queue + SystemClock.sleep(WAIT_FOR_WORK_MS); + + // Cancel and make sure we eventually quiesce + status = mCameraUser.cancelRequest(streamingId); + + verify(mMockCb, timeout(WAIT_FOR_IDLE_TIMEOUT_MS).times(1)).onCameraIdle(); + + // Submit a few capture requests + int requestId1 = submitCameraRequest(request, /* streaming */false); + int requestId2 = submitCameraRequest(request, /* streaming */false); + int requestId3 = submitCameraRequest(request, /* streaming */false); + int requestId4 = submitCameraRequest(request, /* streaming */false); + int requestId5 = submitCameraRequest(request, /* streaming */false); + + // And wait for more idle + verify(mMockCb, timeout(WAIT_FOR_IDLE_TIMEOUT_MS).times(2)).onCameraIdle(); + + } + + @SmallTest + public void testFlush() throws Exception { + int status; + + // Initial flush should work + status = mCameraUser.flush(); + assertEquals(CameraBinderTestUtils.NO_ERROR, status); + + // Then set up a stream + CaptureRequest request = createDefaultBuilder(/* needStream */true).build(); + + // Flush should still be a no-op, really + status = mCameraUser.flush(); + assertEquals(CameraBinderTestUtils.NO_ERROR, status); + + // Submit a few capture requests + int requestId1 = submitCameraRequest(request, /* streaming */false); + int requestId2 = submitCameraRequest(request, /* streaming */false); + int requestId3 = submitCameraRequest(request, /* streaming */false); + int requestId4 = submitCameraRequest(request, /* streaming */false); + int requestId5 = submitCameraRequest(request, /* streaming */false); + + // Then flush and wait for idle + status = mCameraUser.flush(); + assertEquals(CameraBinderTestUtils.NO_ERROR, status); + + verify(mMockCb, timeout(WAIT_FOR_FLUSH_TIMEOUT_MS).times(1)).onCameraIdle(); + + // Now a streaming request + int streamingId = submitCameraRequest(request, /* streaming */true); + + // Wait a bit to fill up the queue + SystemClock.sleep(WAIT_FOR_WORK_MS); + + // Then flush and wait for the idle callback + status = mCameraUser.flush(); + assertEquals(CameraBinderTestUtils.NO_ERROR, status); + + verify(mMockCb, timeout(WAIT_FOR_FLUSH_TIMEOUT_MS).times(2)).onCameraIdle(); + + // TODO: When errors are hooked up, count that errors + successful + // requests equal to 5. + } +} diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java index 074bfe4..7b2a20e 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java @@ -61,6 +61,7 @@ public class MediaPlayerPerformance extends ActivityInstrumentationTestCase2<Med private SurfaceHolder mSurfaceHolder = null; private static final int NUM_STRESS_LOOP = 10; private static final int NUM_PLAYBACk_IN_EACH_LOOP = 20; + private static final int SHORT_WAIT = 2 * 1000; // 2 seconds private static final long MEDIA_STRESS_WAIT_TIME = 5000; //5 seconds private static final String MEDIA_MEMORY_OUTPUT = "/sdcard/mediaMemOutput.txt"; @@ -86,9 +87,9 @@ public class MediaPlayerPerformance extends ActivityInstrumentationTestCase2<Med private Writer mProcMemWriter; private Writer mMemWriter; - private CamcorderProfile mCamcorderProfile = CamcorderProfile.get(CAMERA_ID); - private int mVideoWidth = mCamcorderProfile.videoFrameWidth; - private int mVideoHeight = mCamcorderProfile.videoFrameHeight; + private CamcorderProfile mCamcorderProfile; + private int mVideoWidth; + private int mVideoHeight; Camera mCamera; @@ -101,8 +102,15 @@ public class MediaPlayerPerformance extends ActivityInstrumentationTestCase2<Med super.setUp(); //Insert a 2 second before launching the test activity. This is //the workaround for the race condition of requesting the updated surface. - Thread.sleep(2000); + Thread.sleep(SHORT_WAIT); getActivity(); + //Check if the device support the camcorder + mCamcorderProfile = CamcorderProfile.get(CAMERA_ID); + if (mCamcorderProfile != null) { + mVideoWidth = mCamcorderProfile.videoFrameWidth; + mVideoHeight = mCamcorderProfile.videoFrameHeight; + Log.v(TAG, "height = " + mVideoHeight + " width= " + mVideoWidth); + } if (MediaFrameworkPerfTestRunner.mGetNativeHeapDump) MediaTestUtil.getNativeHeapDump(this.getName() + "_before"); @@ -240,6 +248,8 @@ public class MediaPlayerPerformance extends ActivityInstrumentationTestCase2<Med Thread.sleep(MEDIA_STRESS_WAIT_TIME); mRecorder.stop(); mRecorder.release(); + //Insert 2 seconds to make sure the camera released. + Thread.sleep(SHORT_WAIT); } catch (Exception e) { Log.v("record video failed ", e.toString()); mRecorder.release(); @@ -332,7 +342,7 @@ public class MediaPlayerPerformance extends ActivityInstrumentationTestCase2<Med // USER PID PPID VSIZE RSS WCHAN PC NAME // media 131 1 13676 4796 ffffffff 400b1bd0 S media.log // media 219 131 37768 6892 ffffffff 400b236c S /system/bin/mediaserver - String memusage = poList[2].concat("\n"); + String memusage = poList[poList.length-1].concat("\n"); return memusage; } @@ -410,59 +420,65 @@ public class MediaPlayerPerformance extends ActivityInstrumentationTestCase2<Med // Test case 4: Capture the memory usage after every 20 video only recorded @LargeTest public void testH263RecordVideoOnlyMemoryUsage() throws Exception { - boolean memoryResult = false; - mStartPid = getMediaserverPid(); - int frameRate = MediaProfileReader.getMaxFrameRateForCodec(MediaRecorder.VideoEncoder.H263); - assertTrue("H263 video recording frame rate", frameRate != -1); - for (int i = 0; i < NUM_STRESS_LOOP; i++) { - assertTrue(stressVideoRecord(frameRate, mVideoWidth, mVideoHeight, - MediaRecorder.VideoEncoder.H263, MediaRecorder.OutputFormat.MPEG_4, - MediaNames.RECORDED_VIDEO_3GP, true)); - getMemoryWriteToLog(i); - writeProcmemInfo(); + if (mCamcorderProfile != null) { + boolean memoryResult = false; + mStartPid = getMediaserverPid(); + int frameRate = MediaProfileReader + .getMaxFrameRateForCodec(MediaRecorder.VideoEncoder.H263); + assertTrue("H263 video recording frame rate", frameRate != -1); + for (int i = 0; i < NUM_STRESS_LOOP; i++) { + assertTrue(stressVideoRecord(frameRate, mVideoWidth, mVideoHeight, + MediaRecorder.VideoEncoder.H263, MediaRecorder.OutputFormat.MPEG_4, + MediaNames.RECORDED_VIDEO_3GP, true)); + getMemoryWriteToLog(i); + writeProcmemInfo(); + } + memoryResult = validateMemoryResult(mStartPid, mStartMemory, ENCODER_LIMIT); + assertTrue("H263 record only memory test", memoryResult); } - memoryResult = validateMemoryResult(mStartPid, mStartMemory, ENCODER_LIMIT); - assertTrue("H263 record only memory test", memoryResult); } // Test case 5: Capture the memory usage after every 20 video only recorded @LargeTest public void testMpeg4RecordVideoOnlyMemoryUsage() throws Exception { - boolean memoryResult = false; - - mStartPid = getMediaserverPid(); - int frameRate = MediaProfileReader.getMaxFrameRateForCodec - (MediaRecorder.VideoEncoder.MPEG_4_SP); - assertTrue("MPEG4 video recording frame rate", frameRate != -1); - for (int i = 0; i < NUM_STRESS_LOOP; i++) { - assertTrue(stressVideoRecord(frameRate, mVideoWidth, mVideoHeight, - MediaRecorder.VideoEncoder.MPEG_4_SP, MediaRecorder.OutputFormat.MPEG_4, - MediaNames.RECORDED_VIDEO_3GP, true)); - getMemoryWriteToLog(i); - writeProcmemInfo(); + if (mCamcorderProfile != null) { + boolean memoryResult = false; + mStartPid = getMediaserverPid(); + int frameRate = MediaProfileReader.getMaxFrameRateForCodec + (MediaRecorder.VideoEncoder.MPEG_4_SP); + assertTrue("MPEG4 video recording frame rate", frameRate != -1); + for (int i = 0; i < NUM_STRESS_LOOP; i++) { + assertTrue(stressVideoRecord(frameRate, mVideoWidth, mVideoHeight, + MediaRecorder.VideoEncoder.MPEG_4_SP, MediaRecorder.OutputFormat.MPEG_4, + MediaNames.RECORDED_VIDEO_3GP, true)); + getMemoryWriteToLog(i); + writeProcmemInfo(); + } + memoryResult = validateMemoryResult(mStartPid, mStartMemory, ENCODER_LIMIT); + assertTrue("mpeg4 record only memory test", memoryResult); } - memoryResult = validateMemoryResult(mStartPid, mStartMemory, ENCODER_LIMIT); - assertTrue("mpeg4 record only memory test", memoryResult); } // Test case 6: Capture the memory usage after every 20 video and audio // recorded @LargeTest public void testRecordVideoAudioMemoryUsage() throws Exception { - boolean memoryResult = false; - - mStartPid = getMediaserverPid(); - int frameRate = MediaProfileReader.getMaxFrameRateForCodec(MediaRecorder.VideoEncoder.H263); - assertTrue("H263 video recording frame rate", frameRate != -1); - for (int i = 0; i < NUM_STRESS_LOOP; i++) { - assertTrue(stressVideoRecord(frameRate, mVideoWidth, mVideoHeight, - MediaRecorder.VideoEncoder.H263, MediaRecorder.OutputFormat.MPEG_4, - MediaNames.RECORDED_VIDEO_3GP, false)); - getMemoryWriteToLog(i); - writeProcmemInfo(); + if (mCamcorderProfile != null) { + boolean memoryResult = false; + mStartPid = getMediaserverPid(); + int frameRate = MediaProfileReader + .getMaxFrameRateForCodec(MediaRecorder.VideoEncoder.H263); + assertTrue("H263 video recording frame rate", frameRate != -1); + for (int i = 0; i < NUM_STRESS_LOOP; i++) { + assertTrue(stressVideoRecord(frameRate, mVideoWidth, mVideoHeight, + MediaRecorder.VideoEncoder.H263, MediaRecorder.OutputFormat.MPEG_4, + MediaNames.RECORDED_VIDEO_3GP, false)); + getMemoryWriteToLog(i); + writeProcmemInfo(); + } + memoryResult = validateMemoryResult(mStartPid, mStartMemory, ENCODER_LIMIT); + assertTrue("H263 audio video record memory test", memoryResult); } - memoryResult = validateMemoryResult(mStartPid, mStartMemory, ENCODER_LIMIT); - assertTrue("H263 audio video record memory test", memoryResult); } // Test case 7: Capture the memory usage after every 20 audio only recorded diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java new file mode 100644 index 0000000..3f17aa9 --- /dev/null +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java @@ -0,0 +1,642 @@ +/* + * Copyright (C) 2013 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 com.android.mediaframeworktest.unit; + +import android.os.Parcel; +import android.test.suitebuilder.annotation.SmallTest; +import android.graphics.Point; +import android.graphics.Rect; +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CaptureResult; +import android.hardware.camera2.Face; +import android.hardware.camera2.Rational; +import android.hardware.camera2.Size; +import android.hardware.camera2.impl.CameraMetadataNative; + +import static android.hardware.camera2.impl.CameraMetadataNative.*; + +import java.lang.reflect.Array; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * <pre> + * adb shell am instrument \ + * -e class 'com.android.mediaframeworktest.unit.CameraMetadataTest' \ + * -w com.android.mediaframeworktest/.MediaFrameworkUnitTestRunner + * </pre> + */ +public class CameraMetadataTest extends junit.framework.TestCase { + + CameraMetadataNative mMetadata; + Parcel mParcel; + + // Sections + static final int ANDROID_COLOR_CORRECTION = 0; + static final int ANDROID_CONTROL = 1; + + // Section starts + static final int ANDROID_COLOR_CORRECTION_START = ANDROID_COLOR_CORRECTION << 16; + static final int ANDROID_CONTROL_START = ANDROID_CONTROL << 16; + + // Tags + static final int ANDROID_COLOR_CORRECTION_MODE = ANDROID_COLOR_CORRECTION_START; + static final int ANDROID_COLOR_CORRECTION_TRANSFORM = ANDROID_COLOR_CORRECTION_START + 1; + static final int ANDROID_COLOR_CORRECTION_GAINS = ANDROID_COLOR_CORRECTION_START + 2; + + static final int ANDROID_CONTROL_AE_ANTIBANDING_MODE = ANDROID_CONTROL_START; + static final int ANDROID_CONTROL_AE_EXPOSURE_COMPENSATION = ANDROID_CONTROL_START + 1; + + @Override + public void setUp() { + mMetadata = new CameraMetadataNative(); + mParcel = Parcel.obtain(); + } + + @Override + public void tearDown() throws Exception { + mMetadata = null; + + mParcel.recycle(); + mParcel = null; + } + + @SmallTest + public void testNew() { + assertEquals(0, mMetadata.getEntryCount()); + assertTrue(mMetadata.isEmpty()); + } + + @SmallTest + public void testGetTagFromKey() { + + // Test success + + assertEquals(ANDROID_COLOR_CORRECTION_MODE, + CameraMetadataNative.getTag("android.colorCorrection.mode")); + assertEquals(ANDROID_COLOR_CORRECTION_TRANSFORM, + CameraMetadataNative.getTag("android.colorCorrection.transform")); + assertEquals(ANDROID_CONTROL_AE_ANTIBANDING_MODE, + CameraMetadataNative.getTag("android.control.aeAntibandingMode")); + assertEquals(ANDROID_CONTROL_AE_EXPOSURE_COMPENSATION, + CameraMetadataNative.getTag("android.control.aeExposureCompensation")); + + // Test failures + + try { + CameraMetadataNative.getTag(null); + fail("A null key should throw NPE"); + } catch(NullPointerException e) { + } + + try { + CameraMetadataNative.getTag("android.control"); + fail("A section name only should not be a valid key"); + } catch(IllegalArgumentException e) { + } + + try { + CameraMetadataNative.getTag("android.control.thisTagNameIsFakeAndDoesNotExist"); + fail("A valid section with an invalid tag name should not be a valid key"); + } catch(IllegalArgumentException e) { + } + + try { + CameraMetadataNative.getTag("android"); + fail("A namespace name only should not be a valid key"); + } catch(IllegalArgumentException e) { + } + + try { + CameraMetadataNative.getTag("this.key.is.definitely.invalid"); + fail("A completely fake key name should not be valid"); + } catch(IllegalArgumentException e) { + } + } + + @SmallTest + public void testGetTypeFromTag() { + assertEquals(TYPE_BYTE, CameraMetadataNative.getNativeType(ANDROID_COLOR_CORRECTION_MODE)); + assertEquals(TYPE_RATIONAL, CameraMetadataNative.getNativeType(ANDROID_COLOR_CORRECTION_TRANSFORM)); + assertEquals(TYPE_FLOAT, CameraMetadataNative.getNativeType(ANDROID_COLOR_CORRECTION_GAINS)); + assertEquals(TYPE_BYTE, CameraMetadataNative.getNativeType(ANDROID_CONTROL_AE_ANTIBANDING_MODE)); + assertEquals(TYPE_INT32, + CameraMetadataNative.getNativeType(ANDROID_CONTROL_AE_EXPOSURE_COMPENSATION)); + + try { + CameraMetadataNative.getNativeType(0xDEADF00D); + fail("No type should exist for invalid tag 0xDEADF00D"); + } catch(IllegalArgumentException e) { + } + } + + @SmallTest + public void testReadWriteValues() { + final byte ANDROID_COLOR_CORRECTION_MODE_HIGH_QUALITY = 2; + byte[] valueResult; + + assertEquals(0, mMetadata.getEntryCount()); + assertEquals(true, mMetadata.isEmpty()); + + // + // android.colorCorrection.mode (single enum byte) + // + + assertEquals(null, mMetadata.readValues(ANDROID_COLOR_CORRECTION_MODE)); + + // Write/read null values + mMetadata.writeValues(ANDROID_COLOR_CORRECTION_MODE, null); + assertEquals(null, mMetadata.readValues(ANDROID_COLOR_CORRECTION_MODE)); + + // Write 0 values + mMetadata.writeValues(ANDROID_COLOR_CORRECTION_MODE, new byte[] {}); + + // Read 0 values + valueResult = mMetadata.readValues(ANDROID_COLOR_CORRECTION_MODE); + assertNotNull(valueResult); + assertEquals(0, valueResult.length); + + assertEquals(1, mMetadata.getEntryCount()); + assertEquals(false, mMetadata.isEmpty()); + + // Write 1 value + mMetadata.writeValues(ANDROID_COLOR_CORRECTION_MODE, new byte[] { + ANDROID_COLOR_CORRECTION_MODE_HIGH_QUALITY + }); + + // Read 1 value + valueResult = mMetadata.readValues(ANDROID_COLOR_CORRECTION_MODE); + assertNotNull(valueResult); + assertEquals(1, valueResult.length); + assertEquals(ANDROID_COLOR_CORRECTION_MODE_HIGH_QUALITY, valueResult[0]); + + assertEquals(1, mMetadata.getEntryCount()); + assertEquals(false, mMetadata.isEmpty()); + + // + // android.colorCorrection.colorCorrectionGains (float x 4 array) + // + + final float[] colorCorrectionGains = new float[] { 1.0f, 2.0f, 3.0f, 4.0f}; + byte[] colorCorrectionGainsAsByteArray = new byte[colorCorrectionGains.length * 4]; + ByteBuffer colorCorrectionGainsByteBuffer = + ByteBuffer.wrap(colorCorrectionGainsAsByteArray).order(ByteOrder.nativeOrder()); + for (float f : colorCorrectionGains) + colorCorrectionGainsByteBuffer.putFloat(f); + + // Read + assertNull(mMetadata.readValues(ANDROID_COLOR_CORRECTION_GAINS)); + mMetadata.writeValues(ANDROID_COLOR_CORRECTION_GAINS, colorCorrectionGainsAsByteArray); + + // Write + assertArrayEquals(colorCorrectionGainsAsByteArray, + mMetadata.readValues(ANDROID_COLOR_CORRECTION_GAINS)); + + assertEquals(2, mMetadata.getEntryCount()); + assertEquals(false, mMetadata.isEmpty()); + + // Erase + mMetadata.writeValues(ANDROID_COLOR_CORRECTION_GAINS, null); + assertNull(mMetadata.readValues(ANDROID_COLOR_CORRECTION_GAINS)); + assertEquals(1, mMetadata.getEntryCount()); + } + + private static <T> void assertArrayEquals(T expected, T actual) { + assertEquals(Array.getLength(expected), Array.getLength(actual)); + + int len = Array.getLength(expected); + for (int i = 0; i < len; ++i) { + assertEquals(Array.get(expected, i), Array.get(actual, i)); + } + } + + private <T> void checkKeyGetAndSet(String keyStr, Class<T> type, T value) { + assertFalse("Use checkKeyGetAndSetArray to compare array Keys", type.isArray()); + + Key<T> key = new Key<T>(keyStr, type); + assertNull(mMetadata.get(key)); + mMetadata.set(key, null); + assertNull(mMetadata.get(key)); + mMetadata.set(key, value); + + T actual = mMetadata.get(key); + assertEquals(value, actual); + } + + private <T> void checkKeyGetAndSetArray(String keyStr, Class<T> type, T value) { + assertTrue(type.isArray()); + + Key<T> key = new Key<T>(keyStr, type); + assertNull(mMetadata.get(key)); + mMetadata.set(key, value); + assertArrayEquals(value, mMetadata.get(key)); + } + + @SmallTest + public void testReadWritePrimitive() { + // int32 (single) + checkKeyGetAndSet("android.control.aeExposureCompensation", Integer.TYPE, 0xC0FFEE); + + // byte (single) + checkKeyGetAndSet("android.flash.maxEnergy", Byte.TYPE, (byte)6); + + // int64 (single) + checkKeyGetAndSet("android.flash.firingTime", Long.TYPE, 0xABCD12345678FFFFL); + + // float (single) + checkKeyGetAndSet("android.lens.aperture", Float.TYPE, Float.MAX_VALUE); + + // double (single) -- technically double x 3, but we fake it + checkKeyGetAndSet("android.jpeg.gpsCoordinates", Double.TYPE, Double.MAX_VALUE); + + // rational (single) + checkKeyGetAndSet("android.sensor.baseGainFactor", Rational.class, new Rational(1, 2)); + + /** + * Weirder cases, that don't map 1:1 with the native types + */ + + // bool (single) -- with TYPE_BYTE + checkKeyGetAndSet("android.control.aeLock", Boolean.TYPE, true); + + // integer (single) -- with TYPE_BYTE + checkKeyGetAndSet("android.control.aePrecaptureTrigger", Integer.TYPE, 6); + } + + @SmallTest + public void testReadWritePrimitiveArray() { + // int32 (n) + checkKeyGetAndSetArray("android.sensor.info.sensitivityRange", int[].class, + new int[] { + 0xC0FFEE, 0xDEADF00D + }); + + // byte (n) + checkKeyGetAndSetArray("android.statistics.faceScores", byte[].class, new byte[] { + 1, 2, 3, 4 + }); + + // int64 (n) + checkKeyGetAndSetArray("android.scaler.availableProcessedMinDurations", long[].class, + new long[] { + 0xABCD12345678FFFFL, 0x1234ABCD5678FFFFL, 0xFFFF12345678ABCDL + }); + + // float (n) + checkKeyGetAndSetArray("android.lens.info.availableApertures", float[].class, + new float[] { + Float.MAX_VALUE, Float.MIN_NORMAL, Float.MIN_VALUE + }); + + // double (n) -- in particular double x 3 + checkKeyGetAndSetArray("android.jpeg.gpsCoordinates", double[].class, + new double[] { + Double.MAX_VALUE, Double.MIN_NORMAL, Double.MIN_VALUE + }); + + // rational (n) -- in particular rational x 9 + checkKeyGetAndSetArray("android.sensor.calibrationTransform1", Rational[].class, + new Rational[] { + new Rational(1, 2), new Rational(3, 4), new Rational(5, 6), + new Rational(7, 8), new Rational(9, 10), new Rational(10, 11), + new Rational(12, 13), new Rational(14, 15), new Rational(15, 16) + }); + + /** + * Weirder cases, that don't map 1:1 with the native types + */ + + // bool (n) -- with TYPE_BYTE + checkKeyGetAndSetArray("android.control.aeLock", boolean[].class, new boolean[] { + true, false, true + }); + + + // integer (n) -- with TYPE_BYTE + checkKeyGetAndSetArray("android.control.aeAvailableModes", int[].class, new int[] { + 1, 2, 3, 4 + }); + } + + private enum ColorCorrectionMode { + TRANSFORM_MATRIX, + FAST, + HIGH_QUALITY + } + + private enum AeAntibandingMode { + OFF, + _50HZ, + _60HZ, + AUTO + } + + // TODO: special values for the enum. + private enum AvailableFormat { + RAW_SENSOR, + YV12, + YCrCb_420_SP, + IMPLEMENTATION_DEFINED, + YCbCr_420_888, + BLOB + } + + @SmallTest + public void testReadWriteEnum() { + // byte (single) + checkKeyGetAndSet("android.colorCorrection.mode", ColorCorrectionMode.class, + ColorCorrectionMode.HIGH_QUALITY); + + // byte (single) + checkKeyGetAndSet("android.control.aeAntibandingMode", AeAntibandingMode.class, + AeAntibandingMode.AUTO); + + // byte (n) + checkKeyGetAndSetArray("android.control.aeAvailableAntibandingModes", + AeAntibandingMode[].class, new AeAntibandingMode[] { + AeAntibandingMode.OFF, AeAntibandingMode._50HZ, AeAntibandingMode._60HZ, + AeAntibandingMode.AUTO + }); + + /** + * Stranger cases that don't use byte enums + */ + // int (n) + checkKeyGetAndSetArray("android.scaler.availableFormats", AvailableFormat[].class, + new AvailableFormat[] { + AvailableFormat.RAW_SENSOR, + AvailableFormat.YV12, + AvailableFormat.IMPLEMENTATION_DEFINED, + AvailableFormat.YCbCr_420_888, + AvailableFormat.BLOB + }); + + } + + @SmallTest + public void testReadWriteEnumWithCustomValues() { + CameraMetadataNative.registerEnumValues(AeAntibandingMode.class, new int[] { + 0, + 10, + 20, + 30 + }); + + // byte (single) + checkKeyGetAndSet("android.control.aeAntibandingMode", AeAntibandingMode.class, + AeAntibandingMode.AUTO); + + // byte (n) + checkKeyGetAndSetArray("android.control.aeAvailableAntibandingModes", + AeAntibandingMode[].class, new AeAntibandingMode[] { + AeAntibandingMode.OFF, AeAntibandingMode._50HZ, AeAntibandingMode._60HZ, + AeAntibandingMode.AUTO + }); + + Key<AeAntibandingMode[]> aeAntibandingModeKey = + new Key<AeAntibandingMode[]>("android.control.aeAvailableAntibandingModes", + AeAntibandingMode[].class); + byte[] aeAntibandingModeValues = mMetadata.readValues(CameraMetadataNative + .getTag("android.control.aeAvailableAntibandingModes")); + byte[] expectedValues = new byte[] { 0, 10, 20, 30 }; + assertArrayEquals(expectedValues, aeAntibandingModeValues); + + + /** + * Stranger cases that don't use byte enums + */ + // int (n) + CameraMetadataNative.registerEnumValues(AvailableFormat.class, new int[] { + 0x20, + 0x32315659, + 0x11, + 0x22, + 0x23, + 0x21, + }); + + checkKeyGetAndSetArray("android.scaler.availableFormats", AvailableFormat[].class, + new AvailableFormat[] { + AvailableFormat.RAW_SENSOR, + AvailableFormat.YV12, + AvailableFormat.IMPLEMENTATION_DEFINED, + AvailableFormat.YCbCr_420_888, + AvailableFormat.BLOB + }); + + Key<AvailableFormat[]> availableFormatsKey = + new Key<AvailableFormat[]>("android.scaler.availableFormats", + AvailableFormat[].class); + byte[] availableFormatValues = mMetadata.readValues(CameraMetadataNative + .getTag(availableFormatsKey.getName())); + + int[] expectedIntValues = new int[] { + 0x20, + 0x32315659, + 0x22, + 0x23, + 0x21 + }; + + ByteBuffer bf = ByteBuffer.wrap(availableFormatValues).order(ByteOrder.nativeOrder()); + + assertEquals(expectedIntValues.length * 4, availableFormatValues.length); + for (int i = 0; i < expectedIntValues.length; ++i) { + assertEquals(expectedIntValues[i], bf.getInt()); + } + } + + @SmallTest + public void testReadWriteSize() { + // int32 x n + checkKeyGetAndSet("android.jpeg.thumbnailSize", Size.class, new Size(123, 456)); + + // int32 x 2 x n + checkKeyGetAndSetArray("android.scaler.availableJpegSizes", Size[].class, new Size[] { + new Size(123, 456), + new Size(0xDEAD, 0xF00D), + new Size(0xF00, 0xB00) + }); + } + + @SmallTest + public void testReadWriteRectangle() { + // int32 x n + checkKeyGetAndSet("android.scaler.cropRegion", Rect.class, new Rect(10, 11, 1280, 1024)); + + // int32 x 2 x n + checkKeyGetAndSetArray("android.statistics.faceRectangles", Rect[].class, new Rect[] { + new Rect(110, 111, 11280, 11024), + new Rect(210, 111, 21280, 21024), + new Rect(310, 111, 31280, 31024) + }); + } + + @SmallTest + public void testReadWriteString() { + // (byte) string + Key<String> gpsProcessingMethodKey = + new Key<String>("android.jpeg.gpsProcessingMethod", String.class); + + String helloWorld = new String("HelloWorld"); + byte[] helloWorldBytes = new byte[] { + 'H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd', '\0' }; + + mMetadata.set(gpsProcessingMethodKey, helloWorld); + + String actual = mMetadata.get(gpsProcessingMethodKey); + assertEquals(helloWorld, actual); + + byte[] actualBytes = mMetadata.readValues(getTag(gpsProcessingMethodKey.getName())); + assertArrayEquals(helloWorldBytes, actualBytes); + + // Does not yet test as a string[] since we don't support that in native code. + + // (byte) string + Key<String[]> gpsProcessingMethodKeyArray = + new Key<String[]>("android.jpeg.gpsProcessingMethod", String[].class); + + String[] gpsStrings = new String[] { "HelloWorld", "FooBar", "Shazbot" }; + byte[] gpsBytes = new byte[] { + 'H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd', '\0', + 'F', 'o', 'o', 'B', 'a', 'r', '\0', + 'S', 'h', 'a', 'z', 'b', 'o', 't', '\0'}; + + mMetadata.set(gpsProcessingMethodKeyArray, gpsStrings); + + String[] actualArray = mMetadata.get(gpsProcessingMethodKeyArray); + assertArrayEquals(gpsStrings, actualArray); + + byte[] actualBytes2 = mMetadata.readValues(getTag(gpsProcessingMethodKeyArray.getName())); + assertArrayEquals(gpsBytes, actualBytes2); + } + + <T> void compareGeneric(T expected, T actual) { + assertEquals(expected, actual); + } + + @SmallTest + public void testReadWriteOverride() { + // + // android.scaler.availableFormats (int x n array) + // + int[] availableFormats = new int[] { + 0x20, // RAW_SENSOR + 0x32315659, // YV12 + 0x11, // YCrCb_420_SP + 0x100, // ImageFormat.JPEG + 0x22, // IMPLEMENTATION_DEFINED + 0x23, // YCbCr_420_888 + }; + int[] expectedIntValues = new int[] { + 0x20, // RAW_SENSOR + 0x32315659, // YV12 + 0x11, // YCrCb_420_SP + 0x21, // BLOB + 0x22, // IMPLEMENTATION_DEFINED + 0x23, // YCbCr_420_888 + }; + int availableFormatTag = CameraMetadataNative.getTag("android.scaler.availableFormats"); + + // Write + mMetadata.set(CameraCharacteristics.SCALER_AVAILABLE_FORMATS, availableFormats); + + byte[] availableFormatValues = mMetadata.readValues(availableFormatTag); + + ByteBuffer bf = ByteBuffer.wrap(availableFormatValues).order(ByteOrder.nativeOrder()); + + assertEquals(expectedIntValues.length * 4, availableFormatValues.length); + for (int i = 0; i < expectedIntValues.length; ++i) { + assertEquals(expectedIntValues[i], bf.getInt()); + } + // Read + byte[] availableFormatsAsByteArray = new byte[expectedIntValues.length * 4]; + ByteBuffer availableFormatsByteBuffer = + ByteBuffer.wrap(availableFormatsAsByteArray).order(ByteOrder.nativeOrder()); + for (int value : expectedIntValues) { + availableFormatsByteBuffer.putInt(value); + } + mMetadata.writeValues(availableFormatTag, availableFormatsAsByteArray); + + int[] resultFormats = mMetadata.get(CameraCharacteristics.SCALER_AVAILABLE_FORMATS); + assertNotNull("result available formats shouldn't be null", resultFormats); + assertArrayEquals(availableFormats, resultFormats); + + // + // android.statistics.faces (Face x n array) + // + int[] expectedFaceIds = new int[] {1, 2, 3, 4, 5}; + byte[] expectedFaceScores = new byte[] {10, 20, 30, 40, 50}; + int numFaces = expectedFaceIds.length; + Rect[] expectedRects = new Rect[numFaces]; + for (int i = 0; i < numFaces; i++) { + expectedRects[i] = new Rect(i*4 + 1, i * 4 + 2, i * 4 + 3, i * 4 + 4); + } + int[] expectedFaceLM = new int[] { + 1, 2, 3, 4, 5, 6, + 7, 8, 9, 10, 11, 12, + 13, 14, 15, 16, 17, 18, + 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, + }; + Point[] expectedFaceLMPoints = new Point[numFaces * 3]; + for (int i = 0; i < numFaces; i++) { + expectedFaceLMPoints[i*3] = new Point(expectedFaceLM[i*6], expectedFaceLM[i*6+1]); + expectedFaceLMPoints[i*3+1] = new Point(expectedFaceLM[i*6+2], expectedFaceLM[i*6+3]); + expectedFaceLMPoints[i*3+2] = new Point(expectedFaceLM[i*6+4], expectedFaceLM[i*6+5]); + } + + /** + * Read - FACE_DETECT_MODE == FULL + */ + mMetadata.set(CaptureResult.STATISTICS_FACE_DETECT_MODE, + CaptureResult.STATISTICS_FACE_DETECT_MODE_FULL); + mMetadata.set(CaptureResult.STATISTICS_FACE_IDS, expectedFaceIds); + mMetadata.set(CaptureResult.STATISTICS_FACE_SCORES, expectedFaceScores); + mMetadata.set(CaptureResult.STATISTICS_FACE_RECTANGLES, expectedRects); + mMetadata.set(CaptureResult.STATISTICS_FACE_LANDMARKS, expectedFaceLM); + Face[] resultFaces = mMetadata.get(CaptureResult.STATISTICS_FACES); + assertEquals(numFaces, resultFaces.length); + for (int i = 0; i < numFaces; i++) { + assertEquals(expectedFaceIds[i], resultFaces[i].getId()); + assertEquals(expectedFaceScores[i], resultFaces[i].getScore()); + assertEquals(expectedRects[i], resultFaces[i].getBounds()); + assertEquals(expectedFaceLMPoints[i*3], resultFaces[i].getLeftEyePosition()); + assertEquals(expectedFaceLMPoints[i*3+1], resultFaces[i].getRightEyePosition()); + assertEquals(expectedFaceLMPoints[i*3+2], resultFaces[i].getMouthPosition()); + } + + /** + * Read - FACE_DETECT_MODE == SIMPLE + */ + mMetadata.set(CaptureResult.STATISTICS_FACE_DETECT_MODE, + CaptureResult.STATISTICS_FACE_DETECT_MODE_SIMPLE); + mMetadata.set(CaptureResult.STATISTICS_FACE_SCORES, expectedFaceScores); + mMetadata.set(CaptureResult.STATISTICS_FACE_RECTANGLES, expectedRects); + Face[] resultSimpleFaces = mMetadata.get(CaptureResult.STATISTICS_FACES); + assertEquals(numFaces, resultSimpleFaces.length); + for (int i = 0; i < numFaces; i++) { + assertEquals(Face.ID_UNSUPPORTED, resultSimpleFaces[i].getId()); + assertEquals(expectedFaceScores[i], resultSimpleFaces[i].getScore()); + assertEquals(expectedRects[i], resultSimpleFaces[i].getBounds()); + assertNull(resultSimpleFaces[i].getLeftEyePosition()); + assertNull(resultSimpleFaces[i].getRightEyePosition()); + assertNull(resultSimpleFaces[i].getMouthPosition()); + } + + } +} diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraUtilsBinderDecoratorTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraUtilsBinderDecoratorTest.java new file mode 100644 index 0000000..727af78 --- /dev/null +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraUtilsBinderDecoratorTest.java @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2013 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 com.android.mediaframeworktest.unit; + +import android.hardware.camera2.CameraAccessException; +import android.hardware.camera2.utils.CameraBinderDecorator; +import android.hardware.camera2.utils.CameraRuntimeException; +import android.os.DeadObjectException; +import android.os.RemoteException; +import android.os.TransactionTooLargeException; +import android.test.suitebuilder.annotation.SmallTest; + +import static org.mockito.Mockito.*; +import static android.hardware.camera2.utils.CameraBinderDecorator.*; +import static android.hardware.camera2.CameraAccessException.*; + +import junit.framework.Assert; + +public class CameraUtilsBinderDecoratorTest extends junit.framework.TestCase { + + private interface ICameraBinderStereotype { + + double doNothing(); + + // int is a 'status_t' + int doSomethingPositive(); + + int doSomethingNoError(); + + int doSomethingPermissionDenied(); + + int doSomethingAlreadyExists(); + + int doSomethingBadValue(); + + int doSomethingDeadObject() throws CameraRuntimeException; + + int doSomethingBadPolicy() throws CameraRuntimeException; + + int doSomethingDeviceBusy() throws CameraRuntimeException; + + int doSomethingNoSuchDevice() throws CameraRuntimeException; + + int doSomethingUnknownErrorCode(); + + int doSomethingThrowDeadObjectException() throws RemoteException; + + int doSomethingThrowTransactionTooLargeException() throws RemoteException; + } + + private static final double SOME_ARBITRARY_DOUBLE = 1.0; + private static final int SOME_ARBITRARY_POSITIVE_INT = 5; + private static final int SOME_ARBITRARY_NEGATIVE_INT = -0xC0FFEE; + + @SmallTest + public void testStereotypes() { + + ICameraBinderStereotype mock = mock(ICameraBinderStereotype.class); + try { + when(mock.doNothing()).thenReturn(SOME_ARBITRARY_DOUBLE); + when(mock.doSomethingPositive()).thenReturn(SOME_ARBITRARY_POSITIVE_INT); + when(mock.doSomethingNoError()).thenReturn(NO_ERROR); + when(mock.doSomethingPermissionDenied()).thenReturn(PERMISSION_DENIED); + when(mock.doSomethingAlreadyExists()).thenReturn(ALREADY_EXISTS); + when(mock.doSomethingBadValue()).thenReturn(BAD_VALUE); + when(mock.doSomethingDeadObject()).thenReturn(DEAD_OBJECT); + when(mock.doSomethingBadPolicy()).thenReturn(EACCES); + when(mock.doSomethingDeviceBusy()).thenReturn(EBUSY); + when(mock.doSomethingNoSuchDevice()).thenReturn(ENODEV); + when(mock.doSomethingUnknownErrorCode()).thenReturn(SOME_ARBITRARY_NEGATIVE_INT); + when(mock.doSomethingThrowDeadObjectException()).thenThrow(new DeadObjectException()); + when(mock.doSomethingThrowTransactionTooLargeException()).thenThrow( + new TransactionTooLargeException()); + } catch (RemoteException e) { + Assert.fail("Unreachable"); + } + + ICameraBinderStereotype decoratedMock = CameraBinderDecorator.newInstance(mock); + + // ignored by decorator because return type is double, not int + assertEquals(SOME_ARBITRARY_DOUBLE, decoratedMock.doNothing()); + + // pass through for positive values + assertEquals(SOME_ARBITRARY_POSITIVE_INT, decoratedMock.doSomethingPositive()); + + // pass through NO_ERROR + assertEquals(NO_ERROR, decoratedMock.doSomethingNoError()); + + try { + decoratedMock.doSomethingPermissionDenied(); + Assert.fail("Should've thrown SecurityException"); + } catch (SecurityException e) { + } + + assertEquals(ALREADY_EXISTS, decoratedMock.doSomethingAlreadyExists()); + + try { + decoratedMock.doSomethingBadValue(); + Assert.fail("Should've thrown IllegalArgumentException"); + } catch (IllegalArgumentException e) { + } + + try { + decoratedMock.doSomethingDeadObject(); + Assert.fail("Should've thrown CameraRuntimeException"); + } catch (CameraRuntimeException e) { + assertEquals(CAMERA_DISCONNECTED, e.getReason()); + } + + try { + decoratedMock.doSomethingBadPolicy(); + Assert.fail("Should've thrown CameraRuntimeException"); + } catch (CameraRuntimeException e) { + assertEquals(CAMERA_DISABLED, e.getReason()); + } + + try { + decoratedMock.doSomethingDeviceBusy(); + Assert.fail("Should've thrown CameraRuntimeException"); + } catch (CameraRuntimeException e) { + assertEquals(CAMERA_IN_USE, e.getReason()); + } + + try { + decoratedMock.doSomethingNoSuchDevice(); + Assert.fail("Should've thrown CameraRuntimeException"); + } catch (CameraRuntimeException e) { + assertEquals(CAMERA_DISCONNECTED, e.getReason()); + } + + try { + decoratedMock.doSomethingUnknownErrorCode(); + Assert.fail("Should've thrown UnsupportedOperationException"); + } catch (UnsupportedOperationException e) { + assertEquals(String.format("Unknown error %d", + SOME_ARBITRARY_NEGATIVE_INT), e.getMessage()); + } + + try { + decoratedMock.doSomethingThrowDeadObjectException(); + Assert.fail("Should've thrown CameraRuntimeException"); + } catch (CameraRuntimeException e) { + assertEquals(CAMERA_DISCONNECTED, e.getReason()); + } catch (RemoteException e) { + Assert.fail("Should not throw a DeadObjectException directly, but rethrow"); + } + + try { + decoratedMock.doSomethingThrowTransactionTooLargeException(); + Assert.fail("Should've thrown UnsupportedOperationException"); + } catch (UnsupportedOperationException e) { + assertTrue(e.getCause() instanceof TransactionTooLargeException); + } catch (RemoteException e) { + Assert.fail("Should not throw a TransactionTooLargeException directly, but rethrow"); + } + } + +} diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraUtilsDecoratorTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraUtilsDecoratorTest.java new file mode 100644 index 0000000..c3b6006 --- /dev/null +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraUtilsDecoratorTest.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2013 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 com.android.mediaframeworktest.unit; + +import android.test.suitebuilder.annotation.SmallTest; +import android.hardware.camera2.utils.*; +import android.hardware.camera2.utils.Decorator.DecoratorListener; + +import junit.framework.Assert; + +import java.lang.reflect.Method; + +/** + * adb shell am instrument -e class 'com.android.mediaframeworktest.unit.CameraUtilsDecoratorTest' \ + * -w com.android.mediaframeworktest/.MediaFrameworkUnitTestRunner + */ +public class CameraUtilsDecoratorTest extends junit.framework.TestCase { + private DummyListener mDummyListener; + private DummyInterface mIface; + + @Override + public void setUp() { + mDummyListener = new DummyListener(); + mIface = Decorator.newInstance(new DummyImpl(), mDummyListener); + } + + interface DummyInterface { + int addValues(int x, int y, int z); + + void raiseException() throws Exception; + + void raiseUnsupportedOperationException() throws UnsupportedOperationException; + } + + class DummyImpl implements DummyInterface { + @Override + public int addValues(int x, int y, int z) { + return x + y + z; + } + + @Override + public void raiseException() throws Exception { + throw new Exception("Test exception"); + } + + @Override + public void raiseUnsupportedOperationException() throws UnsupportedOperationException { + throw new UnsupportedOperationException("Test exception"); + } + } + + class DummyListener implements DecoratorListener { + + public boolean beforeCalled = false; + public boolean afterCalled = false; + public boolean catchCalled = false; + public boolean finallyCalled = false; + public Object resultValue = null; + + public boolean raiseException = false; + + @Override + public void onBeforeInvocation(Method m, Object[] args) { + beforeCalled = true; + } + + @Override + public void onAfterInvocation(Method m, Object[] args, Object result) { + afterCalled = true; + resultValue = result; + + if (raiseException) { + throw new UnsupportedOperationException("Test exception"); + } + } + + @Override + public boolean onCatchException(Method m, Object[] args, Throwable t) { + catchCalled = true; + return false; + } + + @Override + public void onFinally(Method m, Object[] args) { + finallyCalled = true; + } + + }; + + @SmallTest + public void testDecorator() { + + // TODO rewrite this using mocks + + assertTrue(mIface.addValues(1, 2, 3) == 6); + assertTrue(mDummyListener.beforeCalled); + assertTrue(mDummyListener.afterCalled); + + int resultValue = (Integer)mDummyListener.resultValue; + assertTrue(resultValue == 6); + assertTrue(mDummyListener.finallyCalled); + assertFalse(mDummyListener.catchCalled); + } + + @SmallTest + public void testDecoratorExceptions() { + + boolean gotExceptions = false; + try { + mIface.raiseException(); + } catch (Exception e) { + gotExceptions = true; + assertTrue(e.getMessage() == "Test exception"); + } + assertTrue(gotExceptions); + assertTrue(mDummyListener.beforeCalled); + assertFalse(mDummyListener.afterCalled); + assertTrue(mDummyListener.catchCalled); + assertTrue(mDummyListener.finallyCalled); + } + + @SmallTest + public void testDecoratorUnsupportedOperationException() { + + boolean gotExceptions = false; + try { + mIface.raiseUnsupportedOperationException(); + } catch (UnsupportedOperationException e) { + gotExceptions = true; + assertTrue(e.getMessage() == "Test exception"); + } + assertTrue(gotExceptions); + assertTrue(mDummyListener.beforeCalled); + assertFalse(mDummyListener.afterCalled); + assertTrue(mDummyListener.catchCalled); + assertTrue(mDummyListener.finallyCalled); + } + + @SmallTest + public void testDecoratorRaisesException() { + + boolean gotExceptions = false; + try { + mDummyListener.raiseException = true; + mIface.addValues(1, 2, 3); + Assert.fail("unreachable"); + } catch (UnsupportedOperationException e) { + gotExceptions = true; + assertTrue(e.getMessage() == "Test exception"); + } + assertTrue(gotExceptions); + assertTrue(mDummyListener.beforeCalled); + assertTrue(mDummyListener.afterCalled); + assertFalse(mDummyListener.catchCalled); + assertTrue(mDummyListener.finallyCalled); + } +} diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraUtilsRuntimeExceptionTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraUtilsRuntimeExceptionTest.java new file mode 100644 index 0000000..02c9f2a --- /dev/null +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraUtilsRuntimeExceptionTest.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2013 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 com.android.mediaframeworktest.unit; + +import android.hardware.camera2.CameraAccessException; +import android.hardware.camera2.utils.CameraRuntimeException; +import android.hardware.camera2.utils.UncheckedThrow; +import android.test.suitebuilder.annotation.SmallTest; + +import junit.framework.Assert; + +public class CameraUtilsRuntimeExceptionTest extends junit.framework.TestCase { + + @SmallTest + public void testCameraRuntimeException1() { + try { + CameraRuntimeException runtimeExc = new CameraRuntimeException(12345); + throw runtimeExc.asChecked(); + } catch (CameraAccessException e) { + assertEquals(12345, e.getReason()); + assertNull(e.getMessage()); + assertNull(e.getCause()); + } + } + + @SmallTest + public void testCameraRuntimeException2() { + try { + CameraRuntimeException runtimeExc = new CameraRuntimeException(12345, "Hello"); + throw runtimeExc.asChecked(); + } catch (CameraAccessException e) { + assertEquals(12345, e.getReason()); + assertEquals("Hello", e.getMessage()); + assertNull(e.getCause()); + } + } + + @SmallTest + public void testCameraRuntimeException3() { + Throwable cause = new IllegalStateException("For great justice"); + try { + CameraRuntimeException runtimeExc = new CameraRuntimeException(12345, cause); + throw runtimeExc.asChecked(); + } catch (CameraAccessException e) { + assertEquals(12345, e.getReason()); + assertNull(e.getMessage()); + assertEquals(cause, e.getCause()); + } + } + + @SmallTest + public void testCameraRuntimeException4() { + Throwable cause = new IllegalStateException("For great justice"); + try { + CameraRuntimeException runtimeExc = new CameraRuntimeException(12345, "Hello", cause); + throw runtimeExc.asChecked(); + } catch (CameraAccessException e) { + assertEquals(12345, e.getReason()); + assertEquals("Hello", e.getMessage()); + assertEquals(cause, e.getCause()); + } + } +} diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraUtilsUncheckedThrowTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraUtilsUncheckedThrowTest.java new file mode 100644 index 0000000..b648763 --- /dev/null +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraUtilsUncheckedThrowTest.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2013 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 com.android.mediaframeworktest.unit; + +import android.hardware.camera2.CameraAccessException; +import android.hardware.camera2.utils.UncheckedThrow; +import android.test.suitebuilder.annotation.SmallTest; + +import junit.framework.Assert; + +public class CameraUtilsUncheckedThrowTest extends junit.framework.TestCase { + + private void fakeNeverThrowsCameraAccess() throws CameraAccessException { + } + + @SmallTest + public void testUncheckedThrow() { + try { + UncheckedThrow.throwAnyException(new CameraAccessException( + CameraAccessException.CAMERA_DISCONNECTED)); + Assert.fail("unreachable"); + fakeNeverThrowsCameraAccess(); + } catch (CameraAccessException e) { + } + } +} diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ImageReaderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ImageReaderTest.java new file mode 100644 index 0000000..f6cd990 --- /dev/null +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ImageReaderTest.java @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2013 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 com.android.mediaframeworktest.unit; + +import static org.mockito.Mockito.*; + +import android.graphics.ImageFormat; +import android.media.Image; +import android.media.Image.Plane; +import android.media.ImageReader; +import android.media.ImageReader.OnImageAvailableListener; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +public class ImageReaderTest extends AndroidTestCase { + + private static final String TAG = "ImageReaderTest-unit"; + + private static final int DEFAULT_WIDTH = 640; + private static final int DEFAULT_HEIGHT = 480; + private static final int DEFAULT_FORMAT = ImageFormat.YUV_420_888; + private static final int DEFAULT_MAX_IMAGES = 3; + + private ImageReader mReader; + private Image mImage1; + private Image mImage2; + private Image mImage3; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + /** + * Workaround for mockito and JB-MR2 incompatibility + * + * Avoid java.lang.IllegalArgumentException: dexcache == null + * https://code.google.com/p/dexmaker/issues/detail?id=2 + */ + System.setProperty("dexmaker.dexcache", getContext().getCacheDir().toString()); + + // TODO: refactor above into one of the test runners + + mReader = spy(ImageReader.newInstance(DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_FORMAT, + DEFAULT_MAX_IMAGES)); + mImage1 = mock(Image.class); + mImage2 = mock(Image.class); + mImage3 = mock(Image.class); + + /** + * Ensure rest of classes are mockable + */ + { + mock(Plane.class); + mock(OnImageAvailableListener.class); + } + + } + + @Override + protected void tearDown() throws Exception { + mReader.close(); + + super.tearDown(); + } + + /** + * Return null when there is nothing in the image queue. + */ + @SmallTest + public void testGetLatestImageEmpty() { + when(mReader.acquireNextImage()).thenReturn(null); + when(mReader.acquireNextImageNoThrowISE()).thenReturn(null); + assertEquals(null, mReader.acquireLatestImage()); + } + + /** + * Return the last image from the image queue, close up the rest. + */ + @SmallTest + public void testGetLatestImage1() { + when(mReader.acquireNextImage()).thenReturn(mImage1); + when(mReader.acquireNextImageNoThrowISE()).thenReturn(null); + assertEquals(mImage1, mReader.acquireLatestImage()); + verify(mImage1, never()).close(); + } + + /** + * Return the last image from the image queue, close up the rest. + */ + @SmallTest + public void testGetLatestImage2() { + when(mReader.acquireNextImage()).thenReturn(mImage1); + when(mReader.acquireNextImageNoThrowISE()).thenReturn(mImage2).thenReturn(null); + assertEquals(mImage2, mReader.acquireLatestImage()); + verify(mImage1, atLeastOnce()).close(); + verify(mImage2, never()).close(); + } + + /** + * Return the last image from the image queue, close up the rest. + */ + @SmallTest + public void testGetLatestImage3() { + when(mReader.acquireNextImage()).thenReturn(mImage1); + when(mReader.acquireNextImageNoThrowISE()).thenReturn(mImage2). + thenReturn(mImage3). + thenReturn(null); + assertEquals(mImage3, mReader.acquireLatestImage()); + verify(mImage1, atLeastOnce()).close(); + verify(mImage2, atLeastOnce()).close(); + verify(mImage3, never()).close(); + } + + /** + * Return null if get a IllegalStateException with no images in the queue. + */ + @SmallTest + public void testGetLatestImageTooManyBuffersAcquiredEmpty() { + when(mReader.acquireNextImage()).thenThrow(new IllegalStateException()); + try { + mReader.acquireLatestImage(); + fail("Expected IllegalStateException to be thrown"); + } catch(IllegalStateException e) { + } + } + + /** + * All images are cleaned up when we get an unexpected Error. + */ + @SmallTest + public void testGetLatestImageExceptionalError() { + when(mReader.acquireNextImage()).thenReturn(mImage1); + when(mReader.acquireNextImageNoThrowISE()).thenReturn(mImage2). + thenReturn(mImage3). + thenThrow(new OutOfMemoryError()); + try { + mReader.acquireLatestImage(); + fail("Impossible"); + } catch(OutOfMemoryError e) { + } + + verify(mImage1, atLeastOnce()).close(); + verify(mImage2, atLeastOnce()).close(); + verify(mImage3, atLeastOnce()).close(); + } + + /** + * All images are cleaned up when we get an unexpected RuntimeException. + */ + @SmallTest + public void testGetLatestImageExceptionalRuntime() { + + when(mReader.acquireNextImage()).thenReturn(mImage1); + when(mReader.acquireNextImageNoThrowISE()).thenReturn(mImage2). + thenReturn(mImage3). + thenThrow(new RuntimeException()); + try { + mReader.acquireLatestImage(); + fail("Impossible"); + } catch(RuntimeException e) { + } + + verify(mImage1, atLeastOnce()).close(); + verify(mImage2, atLeastOnce()).close(); + verify(mImage3, atLeastOnce()).close(); + } +} diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RationalTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RationalTest.java new file mode 100644 index 0000000..9621f92 --- /dev/null +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RationalTest.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2013 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 com.android.mediaframeworktest.unit; + +import android.test.suitebuilder.annotation.SmallTest; +import android.hardware.camera2.Rational; + +/** + * <pre> + * adb shell am instrument \ + * -e class 'com.android.mediaframeworktest.unit.RationalTest' \ + * -w com.android.mediaframeworktest/.MediaFrameworkUnitTestRunner + * </pre> + */ +public class RationalTest extends junit.framework.TestCase { + @SmallTest + public void testConstructor() { + + // Simple case + Rational r = new Rational(1, 2); + assertEquals(1, r.getNumerator()); + assertEquals(2, r.getDenominator()); + + // Denominator negative + r = new Rational(-1, 2); + assertEquals(-1, r.getNumerator()); + assertEquals(2, r.getDenominator()); + + // Numerator negative + r = new Rational(1, -2); + assertEquals(-1, r.getNumerator()); + assertEquals(2, r.getDenominator()); + + // Both negative + r = new Rational(-1, -2); + assertEquals(1, r.getNumerator()); + assertEquals(2, r.getDenominator()); + + // Infinity. + r = new Rational(1, 0); + assertEquals(0, r.getNumerator()); + assertEquals(0, r.getDenominator()); + + // Negative infinity. + r = new Rational(-1, 0); + assertEquals(0, r.getNumerator()); + assertEquals(0, r.getDenominator()); + + // NaN. + r = new Rational(0, 0); + assertEquals(0, r.getNumerator()); + assertEquals(0, r.getDenominator()); + } + + @SmallTest + public void testGcd() { + Rational r = new Rational(1, 2); + assertEquals(1, r.gcd()); + + Rational twoThirds = new Rational(2, 3); + assertEquals(1, twoThirds.gcd()); + + Rational moreComplicated2 = new Rational(5*78, 7*78); + assertEquals(78, moreComplicated2.gcd()); + + Rational oneHalf = new Rational(-1, 2); + assertEquals(1, oneHalf.gcd()); + + twoThirds = new Rational(-2, 3); + assertEquals(1, twoThirds.gcd()); + } + + @SmallTest + public void testEquals() { + Rational r = new Rational(1, 2); + assertEquals(1, r.getNumerator()); + assertEquals(2, r.getDenominator()); + + assertEquals(r, r); + assertFalse(r.equals(null)); + assertFalse(r.equals(new Object())); + + Rational twoThirds = new Rational(2, 3); + assertFalse(r.equals(twoThirds)); + assertFalse(twoThirds.equals(r)); + + Rational fourSixths = new Rational(4, 6); + assertEquals(twoThirds, fourSixths); + assertEquals(fourSixths, twoThirds); + + Rational moreComplicated = new Rational(5*6*7*8*9, 1*2*3*4*5); + Rational moreComplicated2 = new Rational(5*6*7*8*9*78, 1*2*3*4*5*78); + assertEquals(moreComplicated, moreComplicated2); + assertEquals(moreComplicated2, moreComplicated); + + // Ensure negatives are fine + twoThirds = new Rational(-2, 3); + fourSixths = new Rational(-4, 6); + assertEquals(twoThirds, fourSixths); + assertEquals(fourSixths, twoThirds); + + moreComplicated = new Rational(-5*6*7*8*9, 1*2*3*4*5); + moreComplicated2 = new Rational(-5*6*7*8*9*78, 1*2*3*4*5*78); + assertEquals(moreComplicated, moreComplicated2); + assertEquals(moreComplicated2, moreComplicated); + + Rational nan = new Rational(0, 0); + Rational nan2 = new Rational(0, 0); + assertTrue(nan.equals(nan)); + assertTrue(nan.equals(nan2)); + assertTrue(nan2.equals(nan)); + assertFalse(nan.equals(r)); + assertFalse(r.equals(nan)); + + // Infinities of the same sign are equal. + Rational posInf = new Rational(1, 0); + Rational posInf2 = new Rational(2, 0); + Rational negInf = new Rational(-1, 0); + Rational negInf2 = new Rational(-2, 0); + assertEquals(posInf, posInf); + assertEquals(negInf, negInf); + assertEquals(posInf, posInf2); + assertEquals(negInf, negInf2); + + // Infinities aren't equal to anything else. + assertFalse(posInf.equals(negInf)); + assertFalse(negInf.equals(posInf)); + assertFalse(negInf.equals(r)); + assertFalse(posInf.equals(r)); + assertFalse(r.equals(negInf)); + assertFalse(r.equals(posInf)); + assertFalse(posInf.equals(nan)); + assertFalse(negInf.equals(nan)); + assertFalse(nan.equals(posInf)); + assertFalse(nan.equals(negInf)); + } +} diff --git a/media/tests/ScoAudioTest/src/com/android/scoaudiotest/ScoAudioTest.java b/media/tests/ScoAudioTest/src/com/android/scoaudiotest/ScoAudioTest.java index fe3929d..0304640 100644 --- a/media/tests/ScoAudioTest/src/com/android/scoaudiotest/ScoAudioTest.java +++ b/media/tests/ScoAudioTest/src/com/android/scoaudiotest/ScoAudioTest.java @@ -429,7 +429,7 @@ public class ScoAudioTest extends Activity { mMediaRecorder.start(); mState = 1; } catch (Exception e) { - Log.e(TAG, "Could start MediaRecorder: " + e.toString()); + Log.e(TAG, "Could start MediaRecorder: ", e); mMediaRecorder.release(); mMediaRecorder = null; mState = 0; @@ -439,7 +439,7 @@ public class ScoAudioTest extends Activity { mMediaRecorder.stop(); mMediaRecorder.reset(); } catch (Exception e) { - Log.e(TAG, "Could not stop MediaRecorder: " + e.toString()); + Log.e(TAG, "Could not stop MediaRecorder: ", e); mMediaRecorder.release(); mMediaRecorder = null; } finally { @@ -466,7 +466,7 @@ public class ScoAudioTest extends Activity { mMediaRecorder.prepare(); } catch (Exception e) { - Log.e(TAG, "Could not prepare MediaRecorder: " + e.toString()); + Log.e(TAG, "Could not prepare MediaRecorder: ", e); mMediaRecorder.release(); mMediaRecorder = null; } @@ -475,9 +475,14 @@ public class ScoAudioTest extends Activity { @Override public void stop() { if (mMediaRecorder != null) { - mMediaRecorder.stop(); - mMediaRecorder.release(); - mMediaRecorder = null; + try { + mMediaRecorder.stop(); + } catch (Exception e) { + Log.e(TAG, "Could not stop MediaRecorder: ", e); + } finally { + mMediaRecorder.release(); + mMediaRecorder = null; + } } updatePlayPauseButton(); } diff --git a/media/tests/SoundPoolTest/AndroidManifest.xml b/media/tests/SoundPoolTest/AndroidManifest.xml index 126276c..8a29052 100644 --- a/media/tests/SoundPoolTest/AndroidManifest.xml +++ b/media/tests/SoundPoolTest/AndroidManifest.xml @@ -8,4 +8,5 @@ package="com.android.soundpooltest"> </intent-filter> </activity> </application> + <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="8"/> </manifest> diff --git a/media/tests/SoundPoolTest/src/com/android/SoundPoolTest.java b/media/tests/SoundPoolTest/src/com/android/SoundPoolTest.java index 33db2dd..cc3306c 100644 --- a/media/tests/SoundPoolTest/src/com/android/SoundPoolTest.java +++ b/media/tests/SoundPoolTest/src/com/android/SoundPoolTest.java @@ -143,7 +143,7 @@ public class SoundPoolTest extends Activity if (DEBUG) Log.d(LOG_TAG, "Stop note " + id); sleep(50); } - if (DEBUG) Log.d(LOG_TAG, "End scale test"); + if (DEBUG) Log.d(LOG_TAG, "End sounds test"); return true; } @@ -165,7 +165,7 @@ public class SoundPoolTest extends Activity if (DEBUG) Log.d(LOG_TAG, "Stop note " + id); sleep(50); } - if (DEBUG) Log.d(LOG_TAG, "End sounds test"); + if (DEBUG) Log.d(LOG_TAG, "End scale test"); return true; } @@ -189,6 +189,7 @@ public class SoundPoolTest extends Activity if (DEBUG) Log.d(LOG_TAG, "Change rate " + mScale[step]); } mSoundPool.stop(id); + if (DEBUG) Log.d(LOG_TAG, "Stop note " + id); if (DEBUG) Log.d(LOG_TAG, "End rate test"); return true; } @@ -205,34 +206,38 @@ public class SoundPoolTest extends Activity Log.e(LOG_TAG, "Error occurred starting note"); return false; } - sleep(250); + sleep(1000); // play a low priority sound - int id = mSoundPool.play(mSounds[0], DEFAULT_VOLUME, DEFAULT_VOLUME, + int id = mSoundPool.play(mSounds[1], DEFAULT_VOLUME, DEFAULT_VOLUME, LOW_PRIORITY, DEFAULT_LOOP, 1.0f); - if (id > 0) { + if (id != 0) { Log.e(LOG_TAG, "Normal > Low priority test failed"); result = false; mSoundPool.stop(id); } else { - Log.e(LOG_TAG, "Normal > Low priority test passed"); + sleep(1000); + Log.i(LOG_TAG, "Normal > Low priority test passed"); } - sleep(250); // play a high priority sound - id = mSoundPool.play(mSounds[0], DEFAULT_VOLUME, DEFAULT_VOLUME, + id = mSoundPool.play(mSounds[2], DEFAULT_VOLUME, DEFAULT_VOLUME, HIGH_PRIORITY, DEFAULT_LOOP, 1.0f); if (id == 0) { Log.e(LOG_TAG, "High > Normal priority test failed"); result = false; } else { - Log.e(LOG_TAG, "High > Normal priority test passed"); + sleep(1000); + Log.i(LOG_TAG, "Stopping high priority"); + mSoundPool.stop(id); + sleep(1000); + Log.i(LOG_TAG, "High > Normal priority test passed"); } - sleep(250); - mSoundPool.stop(id); // stop normal note + Log.i(LOG_TAG, "Stopping normal priority"); mSoundPool.stop(normalId); + sleep(1000); if (DEBUG) Log.d(LOG_TAG, "End priority test"); return result; @@ -250,17 +255,21 @@ public class SoundPoolTest extends Activity Log.e(LOG_TAG, "Error occurred starting note"); return false; } - sleep(250); + sleep(2500); // pause and resume sound a few times for (int count = 0; count < 5; count++) { + if (DEBUG) Log.d(LOG_TAG, "Pause note " + id); mSoundPool.pause(id); - sleep(250); + sleep(1000); + if (DEBUG) Log.d(LOG_TAG, "Resume note " + id); mSoundPool.resume(id); - sleep(250); + sleep(1000); } + if (DEBUG) Log.d(LOG_TAG, "Stop note " + id); mSoundPool.stop(id); + sleep(1000); // play 5 sounds, forces one to be stolen int ids[] = new int[5]; @@ -272,18 +281,21 @@ public class SoundPoolTest extends Activity Log.e(LOG_TAG, "Error occurred starting note"); return false; } - sleep(250); + sleep(1000); } // pause and resume sound a few times for (int count = 0; count < 5; count++) { + if (DEBUG) Log.d(LOG_TAG, "autoPause"); mSoundPool.autoPause(); - sleep(250); + sleep(1000); + if (DEBUG) Log.d(LOG_TAG, "autoResume"); mSoundPool.autoResume(); - sleep(250); + sleep(1000); } for (int i = 0; i < 5; i++) { + if (DEBUG) Log.d(LOG_TAG, "Stop note " + ids[i]); mSoundPool.stop(ids[i]); } @@ -302,9 +314,9 @@ public class SoundPoolTest extends Activity return false; } - // pan from left to right + // pan from right to left for (int count = 0; count < 101; count++) { - sleep(20); + sleep(50); double radians = PI_OVER_2 * count / 100.0; float leftVolume = (float) Math.sin(radians); float rightVolume = (float) Math.cos(radians); diff --git a/media/tests/audiotests/Android.mk b/media/tests/audiotests/Android.mk new file mode 100644 index 0000000..69f0bb5 --- /dev/null +++ b/media/tests/audiotests/Android.mk @@ -0,0 +1,21 @@ +ifeq ($(TARGET_ARCH),arm) + +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE:= shared_mem_test +LOCAL_SRC_FILES := \ + shared_mem_test.cpp +LOCAL_SHARED_LIBRARIES := \ + libc \ + libcutils \ + libutils \ + libbinder \ + libhardware_legacy \ + libmedia +LOCAL_MODULE_TAGS := tests + +include $(BUILD_EXECUTABLE) + +endif diff --git a/media/tests/audiotests/shared_mem_test.cpp b/media/tests/audiotests/shared_mem_test.cpp new file mode 100644 index 0000000..992c900 --- /dev/null +++ b/media/tests/audiotests/shared_mem_test.cpp @@ -0,0 +1,216 @@ +// Copyright 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. + +// +#define LOG_NDEBUG 0 +#define LOG_TAG "shared_mem_test" + +#include <stdlib.h> +#include <stdio.h> +#include <cutils/properties.h> +#include <media/AudioSystem.h> +#include <media/AudioTrack.h> +#include <math.h> + +#include "shared_mem_test.h" +#include <binder/MemoryDealer.h> +#include <binder/MemoryHeapBase.h> +#include <binder/MemoryBase.h> +#include <binder/ProcessState.h> + + +#include <utils/Log.h> + +#include <fcntl.h> + +namespace android { + +/************************************************************ +* +* Constructor +* +************************************************************/ +AudioTrackTest::AudioTrackTest(void) { + + InitSine(); // init sine table + +} + + +/************************************************************ +* +* +************************************************************/ +void AudioTrackTest::Execute(void) { + if (Test01() == 0) { + ALOGD("01 passed\n"); + } else { + ALOGD("01 failed\n"); + } +} + +/************************************************************ +* +* Shared memory test +* +************************************************************/ +#define BUF_SZ 44100 + +int AudioTrackTest::Test01() { + + sp<MemoryDealer> heap; + sp<IMemory> iMem; + uint8_t* p; + + short smpBuf[BUF_SZ]; + long rate = 44100; + unsigned long phi; + unsigned long dPhi; + long amplitude; + long freq = 1237; + float f0; + + f0 = pow(2., 32.) * freq / (float)rate; + dPhi = (unsigned long)f0; + amplitude = 1000; + phi = 0; + Generate(smpBuf, BUF_SZ, amplitude, phi, dPhi); // fill buffer + + for (int i = 0; i < 1024; i++) { + heap = new MemoryDealer(1024*1024, "AudioTrack Heap Base"); + + iMem = heap->allocate(BUF_SZ*sizeof(short)); + + p = static_cast<uint8_t*>(iMem->pointer()); + memcpy(p, smpBuf, BUF_SZ*sizeof(short)); + + sp<AudioTrack> track = new AudioTrack(AUDIO_STREAM_MUSIC,// stream type + rate, + AUDIO_FORMAT_PCM_16_BIT,// word length, PCM + AUDIO_CHANNEL_OUT_MONO, + iMem); + + status_t status = track->initCheck(); + if(status != NO_ERROR) { + track.clear(); + ALOGD("Failed for initCheck()"); + return -1; + } + + // start play + ALOGD("start"); + track->start(); + + usleep(20000); + + ALOGD("stop"); + track->stop(); + iMem.clear(); + heap.clear(); + usleep(20000); + } + + return 0; + +} + +/************************************************************ +* +* Generate a mono buffer +* Error is less than 3lsb +* +************************************************************/ +void AudioTrackTest::Generate(short *buffer, long bufferSz, long amplitude, unsigned long &phi, long dPhi) +{ + long pi13 = 25736; // 2^13*pi + // fill buffer + for(int i0=0; i0<bufferSz; i0++) { + long sample; + long l0, l1; + + buffer[i0] = ComputeSine( amplitude, phi); + phi += dPhi; + } +} + +/************************************************************ +* +* Generate a sine +* Error is less than 3lsb +* +************************************************************/ +short AudioTrackTest::ComputeSine(long amplitude, long phi) +{ + long pi13 = 25736; // 2^13*pi + long sample; + long l0, l1; + + sample = (amplitude*sin1024[(phi>>22) & 0x3ff]) >> 15; + // correct with interpolation + l0 = (phi>>12) & 0x3ff; // 2^20 * x / (2*pi) + l1 = (amplitude*sin1024[((phi>>22) + 256) & 0x3ff]) >> 15; // 2^15*cosine + l0 = (l0 * l1) >> 10; + l0 = (l0 * pi13) >> 22; + sample = sample + l0; + + return (short)sample; +} + + +/************************************************************ +* +* init sine table +* +************************************************************/ +void AudioTrackTest::InitSine(void) { + double phi = 0; + double dPhi = 2 * M_PI / SIN_SZ; + for(int i0 = 0; i0<SIN_SZ; i0++) { + long d0; + + d0 = 32768. * sin(phi); + phi += dPhi; + if(d0 >= 32767) d0 = 32767; + if(d0 <= -32768) d0 = -32768; + sin1024[i0] = (short)d0; + } +} + +/************************************************************ +* +* main in name space +* +************************************************************/ +int main() { + ProcessState::self()->startThreadPool(); + AudioTrackTest *test; + + test = new AudioTrackTest(); + test->Execute(); + delete test; + + return 0; +} + +} + +/************************************************************ +* +* global main +* +************************************************************/ +int main(int argc, char *argv[]) { + + return android::main(); +} diff --git a/media/tests/audiotests/shared_mem_test.h b/media/tests/audiotests/shared_mem_test.h new file mode 100644 index 0000000..f495955 --- /dev/null +++ b/media/tests/audiotests/shared_mem_test.h @@ -0,0 +1,27 @@ +// Copyright 2008 The Android Open Source Project + +#ifndef AUDIOTRACKTEST_H_ +#define AUDIOTRACKTEST_H_ + +namespace android { + +class AudioTrackTest{ + public: + AudioTrackTest(void); + ~AudioTrackTest() {}; + + void Execute(void); + int Test01(); + + void Generate(short *buffer, long bufferSz, long amplitude, unsigned long &phi, long dPhi); + void InitSine(); + short ComputeSine(long amplitude, long phi); + + #define SIN_SZ 1024 + short sin1024[SIN_SZ]; // sine table 2*pi = 1024 +}; + +}; + + +#endif /*AUDIOTRACKTEST_H_*/ |