summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
Diffstat (limited to 'media')
-rw-r--r--media/java/android/drm/mobile1/DrmConstraintInfo.java96
-rw-r--r--media/java/android/drm/mobile1/DrmRawContent.java464
-rw-r--r--media/java/android/drm/mobile1/DrmRights.java136
-rw-r--r--media/java/android/drm/mobile1/DrmRightsManager.java255
-rw-r--r--media/java/android/drm/mobile1/package.html5
-rw-r--r--media/java/android/media/AudioManager.java248
-rw-r--r--media/java/android/media/AudioRecord.java111
-rw-r--r--media/java/android/media/AudioService.java2855
-rw-r--r--media/java/android/media/AudioSystem.java10
-rw-r--r--media/java/android/media/AudioTimestamp.java44
-rw-r--r--media/java/android/media/AudioTrack.java215
-rw-r--r--media/java/android/media/ExifInterface.java16
-rw-r--r--media/java/android/media/FocusRequester.java244
-rw-r--r--media/java/android/media/IAudioService.aidl72
-rw-r--r--media/java/android/media/IRemoteControlClient.aidl10
-rw-r--r--media/java/android/media/IRemoteControlDisplay.aidl6
-rw-r--r--media/java/android/media/Image.java180
-rw-r--r--media/java/android/media/ImageReader.java736
-rw-r--r--media/java/android/media/MediaCodec.java78
-rw-r--r--media/java/android/media/MediaCodecInfo.java21
-rw-r--r--media/java/android/media/MediaDrm.java19
-rw-r--r--media/java/android/media/MediaFocusControl.java2724
-rw-r--r--media/java/android/media/MediaFormat.java121
-rw-r--r--media/java/android/media/MediaMetadataEditor.java462
-rw-r--r--media/java/android/media/MediaMuxer.java36
-rw-r--r--media/java/android/media/MediaPlayer.java877
-rw-r--r--media/java/android/media/MediaRecorder.java36
-rw-r--r--media/java/android/media/MediaRouter.java139
-rw-r--r--media/java/android/media/MediaScannerConnection.java3
-rw-r--r--media/java/android/media/MediaTimeProvider.java90
-rw-r--r--media/java/android/media/Rating.aidl19
-rw-r--r--media/java/android/media/Rating.java284
-rw-r--r--media/java/android/media/RemoteControlClient.java392
-rw-r--r--media/java/android/media/RemoteController.java908
-rw-r--r--media/java/android/media/RemoteDisplay.java17
-rw-r--r--media/java/android/media/ResourceBusyException.java (renamed from media/java/android/drm/mobile1/DrmException.java)21
-rw-r--r--media/java/android/media/Ringtone.java19
-rw-r--r--media/java/android/media/RingtoneManager.java53
-rw-r--r--media/java/android/media/SoundPool.java471
-rw-r--r--media/java/android/media/SubtitleController.java492
-rw-r--r--media/java/android/media/SubtitleData.java88
-rw-r--r--media/java/android/media/SubtitleTrack.java705
-rw-r--r--media/java/android/media/VolumeController.java (renamed from media/libdrm/mobile1/include/drm_common_types.h)34
-rw-r--r--media/java/android/media/WebVttRenderer.java1842
-rw-r--r--media/java/android/media/audiofx/AudioEffect.java8
-rw-r--r--media/java/android/media/audiofx/LoudnessEnhancer.java291
-rw-r--r--media/java/android/media/audiofx/Visualizer.java101
-rw-r--r--media/jni/Android.mk3
-rw-r--r--media/jni/android_media_ImageReader.cpp879
-rw-r--r--media/jni/android_media_MediaCodec.cpp141
-rw-r--r--media/jni/android_media_MediaCodec.h2
-rw-r--r--media/jni/android_media_MediaCodecList.cpp8
-rw-r--r--media/jni/android_media_MediaDrm.cpp35
-rw-r--r--media/jni/android_media_MediaDrm.h2
-rw-r--r--media/jni/android_media_MediaExtractor.cpp1
-rw-r--r--media/jni/android_media_MediaMuxer.cpp15
-rw-r--r--media/jni/android_media_MediaPlayer.cpp51
-rw-r--r--media/jni/android_media_MediaScanner.cpp1
-rw-r--r--media/jni/android_media_Utils.cpp97
-rw-r--r--media/jni/android_mtp_MtpDatabase.cpp1
-rw-r--r--media/jni/android_mtp_MtpDevice.cpp1
-rw-r--r--media/jni/audioeffect/android_media_Visualizer.cpp72
-rw-r--r--media/jni/mediaeditor/VideoEditorClasses.cpp1
-rw-r--r--media/jni/mediaeditor/VideoEditorJava.cpp2
-rw-r--r--media/jni/mediaeditor/VideoEditorLogging.h10
-rw-r--r--media/jni/mediaeditor/VideoEditorOsal.cpp2
-rw-r--r--media/jni/mediaeditor/VideoEditorPropertiesMain.cpp2
-rw-r--r--media/jni/soundpool/Android.mk2
-rw-r--r--media/jni/soundpool/android_media_SoundPool_SoundPoolImpl.cpp (renamed from media/jni/soundpool/android_media_SoundPool.cpp)96
-rw-r--r--media/libdrm/Android.mk1
-rw-r--r--media/libdrm/MODULE_LICENSE_APACHE20
-rw-r--r--media/libdrm/NOTICE190
-rw-r--r--media/libdrm/mobile1/Android.mk83
-rw-r--r--media/libdrm/mobile1/include/jni/drm1_jni.h242
-rw-r--r--media/libdrm/mobile1/include/objmng/drm_decoder.h55
-rw-r--r--media/libdrm/mobile1/include/objmng/drm_file.h296
-rw-r--r--media/libdrm/mobile1/include/objmng/drm_i18n.h107
-rw-r--r--media/libdrm/mobile1/include/objmng/drm_inner.h90
-rw-r--r--media/libdrm/mobile1/include/objmng/drm_rights_manager.h183
-rw-r--r--media/libdrm/mobile1/include/objmng/drm_time.h77
-rw-r--r--media/libdrm/mobile1/include/objmng/svc_drm.h376
-rw-r--r--media/libdrm/mobile1/include/parser/parser_dcf.h91
-rw-r--r--media/libdrm/mobile1/include/parser/parser_dm.h101
-rw-r--r--media/libdrm/mobile1/include/parser/parser_rel.h123
-rw-r--r--media/libdrm/mobile1/include/xml/wbxml_tinyparser.h52
-rw-r--r--media/libdrm/mobile1/include/xml/xml_tinyParser.h171
-rw-r--r--media/libdrm/mobile1/src/jni/drm1_jni.c1166
-rw-r--r--media/libdrm/mobile1/src/objmng/drm_api.c1943
-rw-r--r--media/libdrm/mobile1/src/objmng/drm_decoder.c96
-rw-r--r--media/libdrm/mobile1/src/objmng/drm_file.c694
-rw-r--r--media/libdrm/mobile1/src/objmng/drm_i18n.c444
-rw-r--r--media/libdrm/mobile1/src/objmng/drm_rights_manager.c688
-rw-r--r--media/libdrm/mobile1/src/objmng/drm_time.c52
-rw-r--r--media/libdrm/mobile1/src/parser/parser_dcf.c153
-rw-r--r--media/libdrm/mobile1/src/parser/parser_dm.c279
-rw-r--r--media/libdrm/mobile1/src/parser/parser_rel.c663
-rw-r--r--media/libdrm/mobile1/src/xml/xml_tinyparser.c834
-rw-r--r--media/mca/filterfw/native/core/gl_env.cpp8
-rw-r--r--media/mca/filterfw/native/core/gl_env.h3
-rw-r--r--media/tests/MediaFrameworkTest/Android.mk2
-rw-r--r--media/tests/MediaFrameworkTest/AndroidManifest.xml5
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkIntegrationTestRunner.java72
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkTestRunner.java2
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java14
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CameraTest.java7
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java237
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTestUtils.java93
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java461
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java104
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraMetadataTest.java642
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraUtilsBinderDecoratorTest.java172
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraUtilsDecoratorTest.java171
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraUtilsRuntimeExceptionTest.java77
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraUtilsUncheckedThrowTest.java40
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ImageReaderTest.java181
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RationalTest.java151
-rw-r--r--media/tests/ScoAudioTest/src/com/android/scoaudiotest/ScoAudioTest.java17
-rw-r--r--media/tests/SoundPoolTest/AndroidManifest.xml1
-rw-r--r--media/tests/SoundPoolTest/src/com/android/SoundPoolTest.java50
-rw-r--r--media/tests/audiotests/Android.mk21
-rw-r--r--media/tests/audiotests/shared_mem_test.cpp216
-rw-r--r--media/tests/audiotests/shared_mem_test.h27
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("&amp;", "&", pos) ||
+ replaceEscape("&lt;", "<", pos) ||
+ replaceEscape("&gt;", ">", pos) ||
+ replaceEscape("&lrm;", "\u200e", pos) ||
+ replaceEscape("&rlm;", "\u200f", pos) ||
+ replaceEscape("&nbsp;", "\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, &params);
+
+ 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> &params);
+
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_*/