Merge "Prevent Hearing Aid fromn spamming logs" into pi-dev
diff --git a/include/media/ExtractorUtils.h b/include/media/ExtractorUtils.h
new file mode 120000
index 0000000..e2dd082
--- /dev/null
+++ b/include/media/ExtractorUtils.h
@@ -0,0 +1 @@
+../../media/libmediaextractor/include/media/ExtractorUtils.h
\ No newline at end of file
diff --git a/media/extractors/mkv/MatroskaExtractor.cpp b/media/extractors/mkv/MatroskaExtractor.cpp
index 3f832bc..fc60fd4 100644
--- a/media/extractors/mkv/MatroskaExtractor.cpp
+++ b/media/extractors/mkv/MatroskaExtractor.cpp
@@ -22,6 +22,7 @@
#include "MatroskaExtractor.h"
#include <media/DataSourceBase.h>
+#include <media/ExtractorUtils.h>
#include <media/MediaTrack.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/AUtils.h>
@@ -1108,7 +1109,7 @@
meta.setData(kKeyFlacMetadata, 0, codecPrivate, codecPrivateSize);
int32_t maxInputSize = 64 << 10;
- sp<FLACDecoder> flacDecoder = FLACDecoder::Create();
+ FLACDecoder *flacDecoder = FLACDecoder::Create();
if (flacDecoder != NULL
&& flacDecoder->parseMetadata((const uint8_t*)codecPrivate, codecPrivateSize) == OK) {
FLAC__StreamMetadata_StreamInfo streamInfo = flacDecoder->getStreamInfo();
@@ -1120,6 +1121,7 @@
&& streamInfo.channels != 0
&& ((streamInfo.bits_per_sample + 7) / 8) >
INT32_MAX / streamInfo.max_blocksize / streamInfo.channels) {
+ delete flacDecoder;
return ERROR_MALFORMED;
}
maxInputSize = ((streamInfo.bits_per_sample + 7) / 8)
@@ -1128,6 +1130,7 @@
}
meta.setInt32(kKeyMaxInputSize, maxInputSize);
+ delete flacDecoder;
return OK;
}
@@ -1143,13 +1146,13 @@
}
const mkvparser::Block::Frame &frame = block->GetFrame(0);
- sp<ABuffer> abuf = new ABuffer(frame.len);
- long n = frame.Read(mReader, abuf->data());
+ auto tmpData = heapbuffer<unsigned char>(frame.len);
+ long n = frame.Read(mReader, tmpData.get());
if (n != 0) {
return ERROR_MALFORMED;
}
- if (!MakeAVCCodecSpecificData(trackInfo->mMeta, abuf)) {
+ if (!MakeAVCCodecSpecificData(trackInfo->mMeta, tmpData.get(), frame.len)) {
return ERROR_MALFORMED;
}
diff --git a/media/extractors/ogg/OggExtractor.cpp b/media/extractors/ogg/OggExtractor.cpp
index 4d49013..b2fe69c 100644
--- a/media/extractors/ogg/OggExtractor.cpp
+++ b/media/extractors/ogg/OggExtractor.cpp
@@ -22,6 +22,7 @@
#include <cutils/properties.h>
#include <media/DataSourceBase.h>
+#include <media/ExtractorUtils.h>
#include <media/MediaTrack.h>
#include <media/VorbisComment.h>
#include <media/stagefright/foundation/ABuffer.h>
@@ -990,21 +991,11 @@
return OK;
}
-struct TmpData {
- uint8_t *data;
- TmpData(size_t size) {
- data = (uint8_t*) malloc(size);
- }
- ~TmpData() {
- free(data);
- }
-};
-
status_t MyOpusExtractor::verifyOpusComments(MediaBufferBase *buffer) {
// add artificial framing bit so we can reuse _vorbis_unpack_comment
int32_t commentSize = buffer->range_length() + 1;
- TmpData commentDataHolder(commentSize);
- uint8_t *commentData = commentDataHolder.data;
+ auto tmp = heapbuffer<uint8_t>(commentSize);
+ uint8_t *commentData = tmp.get();
if (commentData == nullptr) {
return ERROR_MALFORMED;
}
diff --git a/media/libaaudio/src/binding/AudioEndpointParcelable.cpp b/media/libaaudio/src/binding/AudioEndpointParcelable.cpp
index 9eed96d..61d7d27 100644
--- a/media/libaaudio/src/binding/AudioEndpointParcelable.cpp
+++ b/media/libaaudio/src/binding/AudioEndpointParcelable.cpp
@@ -64,27 +64,54 @@
* The read and write must be symmetric.
*/
status_t AudioEndpointParcelable::writeToParcel(Parcel* parcel) const {
- parcel->writeInt32(mNumSharedMemories);
+ status_t status = AAudioConvert_aaudioToAndroidStatus(validate());
+ if (status != NO_ERROR) goto error;
+
+ status = parcel->writeInt32(mNumSharedMemories);
+ if (status != NO_ERROR) goto error;
+
for (int i = 0; i < mNumSharedMemories; i++) {
- mSharedMemories[i].writeToParcel(parcel);
+ status = mSharedMemories[i].writeToParcel(parcel);
+ if (status != NO_ERROR) goto error;
}
- mUpMessageQueueParcelable.writeToParcel(parcel);
- mDownMessageQueueParcelable.writeToParcel(parcel);
- mUpDataQueueParcelable.writeToParcel(parcel);
- mDownDataQueueParcelable.writeToParcel(parcel);
- return NO_ERROR; // TODO check for errors above
+ status = mUpMessageQueueParcelable.writeToParcel(parcel);
+ if (status != NO_ERROR) goto error;
+ status = mDownMessageQueueParcelable.writeToParcel(parcel);
+ if (status != NO_ERROR) goto error;
+ status = mUpDataQueueParcelable.writeToParcel(parcel);
+ if (status != NO_ERROR) goto error;
+ status = mDownDataQueueParcelable.writeToParcel(parcel);
+ if (status != NO_ERROR) goto error;
+
+ return NO_ERROR;
+
+error:
+ ALOGE("%s returning %d", __func__, status);
+ return status;
}
status_t AudioEndpointParcelable::readFromParcel(const Parcel* parcel) {
- parcel->readInt32(&mNumSharedMemories);
+ status_t status = parcel->readInt32(&mNumSharedMemories);
+ if (status != NO_ERROR) goto error;
+
for (int i = 0; i < mNumSharedMemories; i++) {
mSharedMemories[i].readFromParcel(parcel);
+ if (status != NO_ERROR) goto error;
}
- mUpMessageQueueParcelable.readFromParcel(parcel);
- mDownMessageQueueParcelable.readFromParcel(parcel);
- mUpDataQueueParcelable.readFromParcel(parcel);
- mDownDataQueueParcelable.readFromParcel(parcel);
- return NO_ERROR; // TODO check for errors above
+ status = mUpMessageQueueParcelable.readFromParcel(parcel);
+ if (status != NO_ERROR) goto error;
+ status = mDownMessageQueueParcelable.readFromParcel(parcel);
+ if (status != NO_ERROR) goto error;
+ status = mUpDataQueueParcelable.readFromParcel(parcel);
+ if (status != NO_ERROR) goto error;
+ status = mDownDataQueueParcelable.readFromParcel(parcel);
+ if (status != NO_ERROR) goto error;
+
+ return AAudioConvert_aaudioToAndroidStatus(validate());
+
+error:
+ ALOGE("%s returning %d", __func__, status);
+ return status;
}
aaudio_result_t AudioEndpointParcelable::resolve(EndpointDescriptor *descriptor) {
@@ -109,35 +136,11 @@
return AAudioConvert_androidToAAudioResult(err);
}
-aaudio_result_t AudioEndpointParcelable::validate() {
- aaudio_result_t result;
+aaudio_result_t AudioEndpointParcelable::validate() const {
if (mNumSharedMemories < 0 || mNumSharedMemories >= MAX_SHARED_MEMORIES) {
ALOGE("invalid mNumSharedMemories = %d", mNumSharedMemories);
return AAUDIO_ERROR_INTERNAL;
}
- for (int i = 0; i < mNumSharedMemories; i++) {
- result = mSharedMemories[i].validate();
- if (result != AAUDIO_OK) {
- ALOGE("invalid mSharedMemories[%d] = %d", i, result);
- return result;
- }
- }
- if ((result = mUpMessageQueueParcelable.validate()) != AAUDIO_OK) {
- ALOGE("invalid mUpMessageQueueParcelable = %d", result);
- return result;
- }
- if ((result = mDownMessageQueueParcelable.validate()) != AAUDIO_OK) {
- ALOGE("invalid mDownMessageQueueParcelable = %d", result);
- return result;
- }
- if ((result = mUpDataQueueParcelable.validate()) != AAUDIO_OK) {
- ALOGE("invalid mUpDataQueueParcelable = %d", result);
- return result;
- }
- if ((result = mDownDataQueueParcelable.validate()) != AAUDIO_OK) {
- ALOGE("invalid mDownDataQueueParcelable = %d", result);
- return result;
- }
return AAUDIO_OK;
}
diff --git a/media/libaaudio/src/binding/AudioEndpointParcelable.h b/media/libaaudio/src/binding/AudioEndpointParcelable.h
index aa8573f..e4f8b9e 100644
--- a/media/libaaudio/src/binding/AudioEndpointParcelable.h
+++ b/media/libaaudio/src/binding/AudioEndpointParcelable.h
@@ -56,8 +56,6 @@
aaudio_result_t resolve(EndpointDescriptor *descriptor);
- aaudio_result_t validate();
-
aaudio_result_t close();
void dump();
@@ -70,6 +68,8 @@
RingBufferParcelable mDownDataQueueParcelable; // eg. playback
private:
+ aaudio_result_t validate() const;
+
int32_t mNumSharedMemories = 0;
SharedMemoryParcelable mSharedMemories[MAX_SHARED_MEMORIES];
};
diff --git a/media/libaaudio/src/binding/IAAudioService.cpp b/media/libaaudio/src/binding/IAAudioService.cpp
index b3c4934..620edc7 100644
--- a/media/libaaudio/src/binding/IAAudioService.cpp
+++ b/media/libaaudio/src/binding/IAAudioService.cpp
@@ -121,17 +121,11 @@
ALOGE("BpAAudioService::client GET_STREAM_DESCRIPTION passed result %d", result);
return result;
}
- err = parcelable.readFromParcel(&reply);;
+ err = parcelable.readFromParcel(&reply);
if (err != NO_ERROR) {
ALOGE("BpAAudioService::client transact(GET_STREAM_DESCRIPTION) read endpoint %d", err);
return AAudioConvert_androidToAAudioResult(err);
}
- //parcelable.dump();
- result = parcelable.validate();
- if (result != AAUDIO_OK) {
- ALOGE("BpAAudioService::client GET_STREAM_DESCRIPTION validation fails %d", result);
- return result;
- }
return result;
}
@@ -250,6 +244,7 @@
pid_t tid;
int64_t nanoseconds;
aaudio_result_t result;
+ status_t status = NO_ERROR;
ALOGV("BnAAudioService::onTransact(%i) %i", code, flags);
switch(code) {
@@ -294,21 +289,20 @@
case GET_STREAM_DESCRIPTION: {
CHECK_INTERFACE(IAAudioService, data, reply);
- data.readInt32(&streamHandle);
+ status = data.readInt32(&streamHandle);
+ if (status != NO_ERROR) {
+ return status;
+ }
aaudio::AudioEndpointParcelable parcelable;
result = getStreamDescription(streamHandle, parcelable);
if (result != AAUDIO_OK) {
return AAudioConvert_aaudioToAndroidStatus(result);
}
- result = parcelable.validate();
- if (result != AAUDIO_OK) {
- ALOGE("BnAAudioService::onTransact getStreamDescription() returns %d", result);
- parcelable.dump();
- return AAudioConvert_aaudioToAndroidStatus(result);
+ status = reply->writeInt32(result);
+ if (status != NO_ERROR) {
+ return status;
}
- reply->writeInt32(result);
- parcelable.writeToParcel(reply);
- return NO_ERROR;
+ return parcelable.writeToParcel(reply);
} break;
case START_STREAM: {
diff --git a/media/libaaudio/src/binding/RingBufferParcelable.cpp b/media/libaaudio/src/binding/RingBufferParcelable.cpp
index 2babbff..4996b3f 100644
--- a/media/libaaudio/src/binding/RingBufferParcelable.cpp
+++ b/media/libaaudio/src/binding/RingBufferParcelable.cpp
@@ -21,6 +21,7 @@
#include <stdint.h>
#include <binder/Parcelable.h>
+#include <utility/AAudioUtilities.h>
#include "binding/AAudioServiceDefinitions.h"
#include "binding/SharedRegionParcelable.h"
@@ -79,7 +80,10 @@
* The read and write must be symmetric.
*/
status_t RingBufferParcelable::writeToParcel(Parcel* parcel) const {
- status_t status = parcel->writeInt32(mCapacityInFrames);
+ status_t status = AAudioConvert_aaudioToAndroidStatus(validate());
+ if (status != NO_ERROR) goto error;
+
+ status = parcel->writeInt32(mCapacityInFrames);
if (status != NO_ERROR) goto error;
if (mCapacityInFrames > 0) {
status = parcel->writeInt32(mBytesPerFrame);
@@ -97,7 +101,7 @@
}
return NO_ERROR;
error:
- ALOGE("writeToParcel() error = %d", status);
+ ALOGE("%s returning %d", __func__, status);
return status;
}
@@ -118,9 +122,9 @@
status = mDataParcelable.readFromParcel(parcel);
if (status != NO_ERROR) goto error;
}
- return NO_ERROR;
+ return AAudioConvert_aaudioToAndroidStatus(validate());
error:
- ALOGE("readFromParcel() error = %d", status);
+ ALOGE("%s returning %d", __func__, status);
return status;
}
@@ -151,8 +155,7 @@
return AAUDIO_OK;
}
-aaudio_result_t RingBufferParcelable::validate() {
- aaudio_result_t result;
+aaudio_result_t RingBufferParcelable::validate() const {
if (mCapacityInFrames < 0 || mCapacityInFrames >= 32 * 1024) {
ALOGE("invalid mCapacityInFrames = %d", mCapacityInFrames);
return AAUDIO_ERROR_INTERNAL;
@@ -165,18 +168,6 @@
ALOGE("invalid mFramesPerBurst = %d", mFramesPerBurst);
return AAUDIO_ERROR_INTERNAL;
}
- if ((result = mReadCounterParcelable.validate()) != AAUDIO_OK) {
- ALOGE("invalid mReadCounterParcelable = %d", result);
- return result;
- }
- if ((result = mWriteCounterParcelable.validate()) != AAUDIO_OK) {
- ALOGE("invalid mWriteCounterParcelable = %d", result);
- return result;
- }
- if ((result = mDataParcelable.validate()) != AAUDIO_OK) {
- ALOGE("invalid mDataParcelable = %d", result);
- return result;
- }
return AAUDIO_OK;
}
diff --git a/media/libaaudio/src/binding/RingBufferParcelable.h b/media/libaaudio/src/binding/RingBufferParcelable.h
index bd562f2..1dbcf07 100644
--- a/media/libaaudio/src/binding/RingBufferParcelable.h
+++ b/media/libaaudio/src/binding/RingBufferParcelable.h
@@ -66,11 +66,12 @@
aaudio_result_t resolve(SharedMemoryParcelable *memoryParcels, RingBufferDescriptor *descriptor);
- aaudio_result_t validate();
-
void dump();
private:
+
+ aaudio_result_t validate() const;
+
SharedRegionParcelable mReadCounterParcelable;
SharedRegionParcelable mWriteCounterParcelable;
SharedRegionParcelable mDataParcelable;
diff --git a/media/libaaudio/src/binding/SharedMemoryParcelable.cpp b/media/libaaudio/src/binding/SharedMemoryParcelable.cpp
index 4e3e5d1..0b0cf77 100644
--- a/media/libaaudio/src/binding/SharedMemoryParcelable.cpp
+++ b/media/libaaudio/src/binding/SharedMemoryParcelable.cpp
@@ -48,7 +48,10 @@
}
status_t SharedMemoryParcelable::writeToParcel(Parcel* parcel) const {
- status_t status = parcel->writeInt32(mSizeInBytes);
+ status_t status = AAudioConvert_aaudioToAndroidStatus(validate());
+ if (status != NO_ERROR) return status;
+
+ status = parcel->writeInt32(mSizeInBytes);
if (status != NO_ERROR) return status;
if (mSizeInBytes > 0) {
ALOGV("writeToParcel() mFd = %d, this = %p\n", mFd.get(), this);
@@ -61,21 +64,27 @@
status_t SharedMemoryParcelable::readFromParcel(const Parcel* parcel) {
status_t status = parcel->readInt32(&mSizeInBytes);
- if (status != NO_ERROR) {
- return status;
- }
+ if (status != NO_ERROR) goto error;
+
if (mSizeInBytes > 0) {
// The Parcel owns the file descriptor and will close it later.
unique_fd mmapFd;
status = parcel->readUniqueFileDescriptor(&mmapFd);
if (status != NO_ERROR) {
ALOGE("readFromParcel() readUniqueFileDescriptor() failed : %d", status);
- } else {
- // Resolve the memory now while we still have the FD from the Parcel.
- // Closing the FD will not affect the shared memory once mmap() has been called.
- status = AAudioConvert_androidToAAudioResult(resolveSharedMemory(mmapFd));
+ goto error;
}
+
+ // Resolve the memory now while we still have the FD from the Parcel.
+ // Closing the FD will not affect the shared memory once mmap() has been called.
+ aaudio_result_t result = resolveSharedMemory(mmapFd);
+ status = AAudioConvert_aaudioToAndroidStatus(result);
+ if (status != NO_ERROR) goto error;
}
+
+ return AAudioConvert_aaudioToAndroidStatus(validate());
+
+error:
return status;
}
@@ -136,7 +145,7 @@
return mSizeInBytes;
}
-aaudio_result_t SharedMemoryParcelable::validate() {
+aaudio_result_t SharedMemoryParcelable::validate() const {
if (mSizeInBytes < 0 || mSizeInBytes >= MAX_MMAP_SIZE_BYTES) {
ALOGE("invalid mSizeInBytes = %d", mSizeInBytes);
return AAUDIO_ERROR_OUT_OF_RANGE;
diff --git a/media/libaaudio/src/binding/SharedMemoryParcelable.h b/media/libaaudio/src/binding/SharedMemoryParcelable.h
index 2a634e0..82c2240 100644
--- a/media/libaaudio/src/binding/SharedMemoryParcelable.h
+++ b/media/libaaudio/src/binding/SharedMemoryParcelable.h
@@ -61,8 +61,6 @@
int32_t getSizeInBytes();
- aaudio_result_t validate();
-
void dump();
protected:
@@ -74,6 +72,11 @@
android::base::unique_fd mFd;
int32_t mSizeInBytes = 0;
uint8_t *mResolvedAddress = MMAP_UNRESOLVED_ADDRESS;
+
+private:
+
+ aaudio_result_t validate() const;
+
};
} /* namespace aaudio */
diff --git a/media/libaaudio/src/binding/SharedRegionParcelable.cpp b/media/libaaudio/src/binding/SharedRegionParcelable.cpp
index 7aa80bf..c776116 100644
--- a/media/libaaudio/src/binding/SharedRegionParcelable.cpp
+++ b/media/libaaudio/src/binding/SharedRegionParcelable.cpp
@@ -24,6 +24,7 @@
#include <binder/Parcelable.h>
#include <aaudio/AAudio.h>
+#include <utility/AAudioUtilities.h>
#include "binding/SharedMemoryParcelable.h"
#include "binding/SharedRegionParcelable.h"
@@ -47,21 +48,38 @@
}
status_t SharedRegionParcelable::writeToParcel(Parcel* parcel) const {
- parcel->writeInt32(mSizeInBytes);
+ status_t status = AAudioConvert_aaudioToAndroidStatus(validate());
+ if (status != NO_ERROR) goto error;
+
+ status = parcel->writeInt32(mSizeInBytes);
+ if (status != NO_ERROR) goto error;
if (mSizeInBytes > 0) {
- parcel->writeInt32(mSharedMemoryIndex);
- parcel->writeInt32(mOffsetInBytes);
+ status = parcel->writeInt32(mSharedMemoryIndex);
+ if (status != NO_ERROR) goto error;
+ status = parcel->writeInt32(mOffsetInBytes);
+ if (status != NO_ERROR) goto error;
}
- return NO_ERROR; // TODO check for errors above
+ return NO_ERROR;
+
+error:
+ ALOGE("%s returning %d", __func__, status);
+ return status;
}
status_t SharedRegionParcelable::readFromParcel(const Parcel* parcel) {
- parcel->readInt32(&mSizeInBytes);
+ status_t status = parcel->readInt32(&mSizeInBytes);
+ if (status != NO_ERROR) goto error;
if (mSizeInBytes > 0) {
- parcel->readInt32(&mSharedMemoryIndex);
- parcel->readInt32(&mOffsetInBytes);
+ status = parcel->readInt32(&mSharedMemoryIndex);
+ if (status != NO_ERROR) goto error;
+ status = parcel->readInt32(&mOffsetInBytes);
+ if (status != NO_ERROR) goto error;
}
- return NO_ERROR; // TODO check for errors above
+ return AAudioConvert_aaudioToAndroidStatus(validate());
+
+error:
+ ALOGE("%s returning %d", __func__, status);
+ return status;
}
aaudio_result_t SharedRegionParcelable::resolve(SharedMemoryParcelable *memoryParcels,
@@ -78,7 +96,7 @@
return memoryParcel->resolve(mOffsetInBytes, mSizeInBytes, regionAddressPtr);
}
-aaudio_result_t SharedRegionParcelable::validate() {
+aaudio_result_t SharedRegionParcelable::validate() const {
if (mSizeInBytes < 0 || mSizeInBytes >= MAX_MMAP_SIZE_BYTES) {
ALOGE("invalid mSizeInBytes = %d", mSizeInBytes);
return AAUDIO_ERROR_OUT_OF_RANGE;
diff --git a/media/libaaudio/src/binding/SharedRegionParcelable.h b/media/libaaudio/src/binding/SharedRegionParcelable.h
index f6babfd..0cd8c04 100644
--- a/media/libaaudio/src/binding/SharedRegionParcelable.h
+++ b/media/libaaudio/src/binding/SharedRegionParcelable.h
@@ -47,14 +47,15 @@
bool isFileDescriptorSafe(SharedMemoryParcelable *memoryParcels);
- aaudio_result_t validate();
-
void dump();
protected:
int32_t mSharedMemoryIndex = -1;
int32_t mOffsetInBytes = 0;
int32_t mSizeInBytes = 0;
+
+private:
+ aaudio_result_t validate() const;
};
} /* namespace aaudio */
diff --git a/media/libaudiohal/4.0/DevicesFactoryHalHybrid.cpp b/media/libaudiohal/4.0/DevicesFactoryHalHybrid.cpp
index c43509c..7ff1ec7d 100644
--- a/media/libaudiohal/4.0/DevicesFactoryHalHybrid.cpp
+++ b/media/libaudiohal/4.0/DevicesFactoryHalHybrid.cpp
@@ -33,7 +33,8 @@
}
status_t DevicesFactoryHalHybrid::openDevice(const char *name, sp<DeviceHalInterface> *device) {
- if (mHidlFactory != 0 && strcmp(AUDIO_HARDWARE_MODULE_ID_A2DP, name) != 0) {
+ if (mHidlFactory != 0 && strcmp(AUDIO_HARDWARE_MODULE_ID_A2DP, name) != 0 &&
+ strcmp(AUDIO_HARDWARE_MODULE_ID_HEARING_AID, name) != 0) {
return mHidlFactory->openDevice(name, device);
}
return mLocalFactory->openDevice(name, device);
diff --git a/media/libmedia/NdkWrapper.cpp b/media/libmedia/NdkWrapper.cpp
index 5418af9..2cdb44e 100644
--- a/media/libmedia/NdkWrapper.cpp
+++ b/media/libmedia/NdkWrapper.cpp
@@ -1068,6 +1068,14 @@
return OK;
}
+status_t AMediaExtractorWrapper::disconnect() {
+ if (mAMediaExtractor != NULL) {
+ media_status_t err = AMediaExtractor_disconnect(mAMediaExtractor);
+ return translateErrorCode(err);
+ }
+ return DEAD_OBJECT;
+}
+
AMediaExtractor *AMediaExtractorWrapper::getAMediaExtractor() const {
return mAMediaExtractor;
}
diff --git a/media/libmedia/include/media/NdkWrapper.h b/media/libmedia/include/media/NdkWrapper.h
index 191665a..c97d171 100644
--- a/media/libmedia/include/media/NdkWrapper.h
+++ b/media/libmedia/include/media/NdkWrapper.h
@@ -287,6 +287,8 @@
status_t release();
+ status_t disconnect();
+
status_t setDataSource(int fd, off64_t offset, off64_t length);
status_t setDataSource(const char *location);
diff --git a/media/libmediaextractor/include/media/ExtractorUtils.h b/media/libmediaextractor/include/media/ExtractorUtils.h
new file mode 100644
index 0000000..22f9349
--- /dev/null
+++ b/media/libmediaextractor/include/media/ExtractorUtils.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2018 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 EXTRACTOR_UTILS_H_
+
+#define EXTRACTOR_UTILS_H_
+
+#include <memory>
+
+namespace android {
+
+template <class T>
+std::unique_ptr<T[]> heapbuffer(size_t size) {
+ return std::unique_ptr<T[]>(new (std::nothrow) T[size]);
+}
+
+} // namespace android
+
+#endif // UTILS_H_
diff --git a/media/libstagefright/ACodec.cpp b/media/libstagefright/ACodec.cpp
index b296622..6fd5677 100644
--- a/media/libstagefright/ACodec.cpp
+++ b/media/libstagefright/ACodec.cpp
@@ -37,7 +37,6 @@
#include <media/stagefright/BufferProducerWrapper.h>
#include <media/stagefright/MediaCodec.h>
-#include <media/stagefright/MediaCodecList.h>
#include <media/stagefright/MediaDefs.h>
#include <media/stagefright/OMXClient.h>
#include <media/stagefright/PersistentSurface.h>
@@ -6346,37 +6345,19 @@
sp<AMessage> notify = new AMessage(kWhatOMXDied, mCodec);
- Vector<AString> matchingCodecs;
- Vector<AString> owners;
-
- AString componentName;
- CHECK(msg->findString("componentName", &componentName));
-
- sp<IMediaCodecList> list = MediaCodecList::getInstance();
- if (list == nullptr) {
- ALOGE("Unable to obtain MediaCodecList while "
- "attempting to create codec \"%s\"",
- componentName.c_str());
- mCodec->signalError(OMX_ErrorUndefined, NO_INIT);
- return false;
- }
- ssize_t index = list->findCodecByName(componentName.c_str());
- if (index < 0) {
- ALOGE("Unable to find codec \"%s\"",
- componentName.c_str());
- mCodec->signalError(OMX_ErrorInvalidComponent, NAME_NOT_FOUND);
- return false;
- }
- sp<MediaCodecInfo> info = list->getCodecInfo(index);
+ sp<RefBase> obj;
+ CHECK(msg->findObject("codecInfo", &obj));
+ sp<MediaCodecInfo> info = (MediaCodecInfo *)obj.get();
if (info == nullptr) {
- ALOGE("Unexpected error (index out-of-bound) while "
- "retrieving information for codec \"%s\"",
- componentName.c_str());
+ ALOGE("Unexpected nullptr for codec information");
mCodec->signalError(OMX_ErrorUndefined, UNKNOWN_ERROR);
return false;
}
AString owner = (info->getOwnerName() == nullptr) ? "default" : info->getOwnerName();
+ AString componentName;
+ CHECK(msg->findString("componentName", &componentName));
+
sp<CodecObserver> observer = new CodecObserver;
sp<IOMX> omx;
sp<IOMXNode> omxNode;
diff --git a/media/libstagefright/Android.bp b/media/libstagefright/Android.bp
index 13d80f5..71bff84 100644
--- a/media/libstagefright/Android.bp
+++ b/media/libstagefright/Android.bp
@@ -49,6 +49,100 @@
}
cc_library_shared {
+ name: "libstagefright_codecbase",
+
+ export_include_dirs: ["include"],
+
+ srcs: [
+ "CodecBase.cpp",
+ "FrameRenderTracker.cpp",
+ ],
+
+ cflags: [
+ "-Werror",
+ "-Wall",
+ ],
+
+ shared_libs: [
+ "libgui",
+ "liblog",
+ "libmedia",
+ "libstagefright_foundation",
+ "libui",
+ "libutils",
+ "android.hardware.cas.native@1.0",
+ ],
+
+ sanitize: {
+ cfi: true,
+ misc_undefined: [
+ "unsigned-integer-overflow",
+ "signed-integer-overflow",
+ ],
+ diag: {
+ cfi: true,
+ },
+ },
+}
+
+cc_library_shared {
+ name: "libstagefright_ccodec",
+
+ local_include_dirs: ["include"],
+
+ srcs: [
+ "C2OMXNode.cpp",
+ "CCodec.cpp",
+ "CCodecBufferChannel.cpp",
+ "Codec2Buffer.cpp",
+ ],
+
+ cflags: [
+ "-Werror",
+ "-Wall",
+ ],
+
+ header_libs: [
+ "libstagefright_codec2_internal",
+ ],
+
+ shared_libs: [
+ "libbinder",
+ "libcutils",
+ "libgui",
+ "libhidlallocatorutils",
+ "libhidlbase",
+ "liblog",
+ "libmedia",
+ "libmedia_omx",
+ "libstagefright_codec2",
+ "libstagefright_codec2_vndk",
+ "libstagefright_codecbase",
+ "libstagefright_foundation",
+ "libstagefright_omx_utils",
+ "libui",
+ "libutils",
+ "libv4l2_c2componentstore",
+ "android.hardware.cas.native@1.0",
+
+ // TODO: do not link directly with impl
+ "libstagefright_bufferqueue_helper",
+ "android.hardware.media.c2@1.0-service-impl",
+ ],
+
+ sanitize: {
+ cfi: true,
+ misc_undefined: [
+ "unsigned-integer-overflow",
+ "signed-integer-overflow",
+ ],
+ diag: {
+ cfi: true,
+ },
+ },
+}
+
+cc_library_shared {
name: "libstagefright",
srcs: [
@@ -60,11 +154,7 @@
"AudioPresentationInfo.cpp",
"AudioSource.cpp",
"BufferImpl.cpp",
- "C2OMXNode.cpp",
- "CCodec.cpp",
- "CCodecBufferChannel.cpp",
"Codec2InfoBuilder.cpp",
- "CodecBase.cpp",
"CallbackDataSource.cpp",
"CallbackMediaSource.cpp",
"CameraSource.cpp",
@@ -74,7 +164,6 @@
"DataURISource.cpp",
"FileSource.cpp",
"FrameDecoder.cpp",
- "FrameRenderTracker.cpp",
"HTTPBase.cpp",
"HevcUtils.cpp",
"InterfaceUtils.cpp",
@@ -107,10 +196,6 @@
"VideoFrameScheduler.cpp",
],
- header_libs: [
- "libstagefright_codec2_internal",
- ],
-
shared_libs: [
"libaudioutils",
"libbinder",
@@ -131,8 +216,10 @@
"libui",
"libutils",
"libmedia_helper",
+ "libstagefright_ccodec",
"libstagefright_codec2",
"libstagefright_codec2_vndk",
+ "libstagefright_codecbase",
"libstagefright_foundation",
"libstagefright_omx",
"libstagefright_omx_utils",
@@ -149,10 +236,6 @@
"android.hardware.media.omx@1.0",
"android.hardware.graphics.allocator@2.0",
"android.hardware.graphics.mapper@2.0",
-
- // TODO: do not link directly with impl
- "android.hardware.media.c2@1.0-service-impl",
- "libstagefright_bufferqueue_helper",
],
static_libs: [
diff --git a/media/libstagefright/BufferImpl.cpp b/media/libstagefright/BufferImpl.cpp
index 7b3fa02..b760273 100644
--- a/media/libstagefright/BufferImpl.cpp
+++ b/media/libstagefright/BufferImpl.cpp
@@ -24,7 +24,6 @@
#include <media/ICrypto.h>
#include <utils/NativeHandle.h>
-#include "include/Codec2Buffer.h"
#include "include/SecureBuffer.h"
#include "include/SharedMemoryBuffer.h"
@@ -64,592 +63,4 @@
return ICrypto::kDestinationTypeNativeHandle;
}
-// Codec2Buffer
-
-bool Codec2Buffer::canCopyLinear(const std::shared_ptr<C2Buffer> &buffer) const {
- if (const_cast<Codec2Buffer *>(this)->base() == nullptr) {
- return false;
- }
- if (!buffer) {
- // Nothing to copy, so we can copy by doing nothing.
- return true;
- }
- if (buffer->data().type() != C2BufferData::LINEAR) {
- return false;
- }
- if (buffer->data().linearBlocks().size() == 0u) {
- // Nothing to copy, so we can copy by doing nothing.
- return true;
- } else if (buffer->data().linearBlocks().size() > 1u) {
- // We don't know how to copy more than one blocks.
- return false;
- }
- if (buffer->data().linearBlocks()[0].size() > capacity()) {
- // It won't fit.
- return false;
- }
- return true;
-}
-
-bool Codec2Buffer::copyLinear(const std::shared_ptr<C2Buffer> &buffer) {
- // We assume that all canCopyLinear() checks passed.
- if (!buffer || buffer->data().linearBlocks().size() == 0u) {
- setRange(0, 0);
- return true;
- }
- C2ReadView view = buffer->data().linearBlocks()[0].map().get();
- if (view.error() != C2_OK) {
- ALOGD("Error while mapping: %d", view.error());
- return false;
- }
- if (view.capacity() > capacity()) {
- ALOGD("C2ConstLinearBlock lied --- it actually doesn't fit: view(%u) > this(%zu)",
- view.capacity(), capacity());
- return false;
- }
- memcpy(base(), view.data(), view.capacity());
- setRange(0, view.capacity());
- return true;
-}
-
-// LocalLinearBuffer
-
-bool LocalLinearBuffer::canCopy(const std::shared_ptr<C2Buffer> &buffer) const {
- return canCopyLinear(buffer);
-}
-
-bool LocalLinearBuffer::copy(const std::shared_ptr<C2Buffer> &buffer) {
- return copyLinear(buffer);
-}
-
-// DummyContainerBuffer
-
-DummyContainerBuffer::DummyContainerBuffer(
- const sp<AMessage> &format, const std::shared_ptr<C2Buffer> &buffer)
- : Codec2Buffer(format, new ABuffer(nullptr, 1)),
- mBufferRef(buffer) {
- setRange(0, buffer ? 1 : 0);
-}
-
-std::shared_ptr<C2Buffer> DummyContainerBuffer::asC2Buffer() {
- return std::move(mBufferRef);
-}
-
-bool DummyContainerBuffer::canCopy(const std::shared_ptr<C2Buffer> &) const {
- return !mBufferRef;
-}
-
-bool DummyContainerBuffer::copy(const std::shared_ptr<C2Buffer> &buffer) {
- mBufferRef = buffer;
- setRange(0, mBufferRef ? 1 : 0);
- return true;
-}
-
-// LinearBlockBuffer
-
-// static
-sp<LinearBlockBuffer> LinearBlockBuffer::Allocate(
- const sp<AMessage> &format, const std::shared_ptr<C2LinearBlock> &block) {
- C2WriteView writeView(block->map().get());
- if (writeView.error() != C2_OK) {
- return nullptr;
- }
- return new LinearBlockBuffer(format, std::move(writeView), block);
-}
-
-std::shared_ptr<C2Buffer> LinearBlockBuffer::asC2Buffer() {
- return C2Buffer::CreateLinearBuffer(mBlock->share(offset(), size(), C2Fence()));
-}
-
-bool LinearBlockBuffer::canCopy(const std::shared_ptr<C2Buffer> &buffer) const {
- return canCopyLinear(buffer);
-}
-
-bool LinearBlockBuffer::copy(const std::shared_ptr<C2Buffer> &buffer) {
- return copyLinear(buffer);
-}
-
-LinearBlockBuffer::LinearBlockBuffer(
- const sp<AMessage> &format,
- C2WriteView&& writeView,
- const std::shared_ptr<C2LinearBlock> &block)
- : Codec2Buffer(format, new ABuffer(writeView.data(), writeView.size())),
- mWriteView(writeView),
- mBlock(block) {
-}
-
-// ConstLinearBlockBuffer
-
-// static
-sp<ConstLinearBlockBuffer> ConstLinearBlockBuffer::Allocate(
- const sp<AMessage> &format, const std::shared_ptr<C2Buffer> &buffer) {
- if (!buffer
- || buffer->data().type() != C2BufferData::LINEAR
- || buffer->data().linearBlocks().size() != 1u) {
- return nullptr;
- }
- C2ReadView readView(buffer->data().linearBlocks()[0].map().get());
- if (readView.error() != C2_OK) {
- return nullptr;
- }
- return new ConstLinearBlockBuffer(format, std::move(readView), buffer);
-}
-
-ConstLinearBlockBuffer::ConstLinearBlockBuffer(
- const sp<AMessage> &format,
- C2ReadView&& readView,
- const std::shared_ptr<C2Buffer> &buffer)
- : Codec2Buffer(format, new ABuffer(
- // NOTE: ABuffer only takes non-const pointer but this data is
- // supposed to be read-only.
- const_cast<uint8_t *>(readView.data()), readView.capacity())),
- mReadView(readView),
- mBufferRef(buffer) {
-}
-
-std::shared_ptr<C2Buffer> ConstLinearBlockBuffer::asC2Buffer() {
- return std::move(mBufferRef);
-}
-
-// GraphicView2MediaImageConverter
-
-namespace {
-
-class GraphicView2MediaImageConverter {
-public:
- explicit GraphicView2MediaImageConverter(const C2GraphicView &view)
- : mInitCheck(NO_INIT),
- mView(view),
- mWidth(view.width()),
- mHeight(view.height()),
- mAllocatedDepth(0),
- mBackBufferSize(0),
- mMediaImage(new ABuffer(sizeof(MediaImage2))) {
- if (view.error() != C2_OK) {
- ALOGD("Converter: view.error() = %d", view.error());
- mInitCheck = BAD_VALUE;
- return;
- }
- MediaImage2 *mediaImage = (MediaImage2 *)mMediaImage->base();
- const C2PlanarLayout &layout = view.layout();
- if (layout.numPlanes == 0) {
- ALOGD("Converter: 0 planes");
- mInitCheck = BAD_VALUE;
- return;
- }
- mAllocatedDepth = layout.planes[0].allocatedDepth;
- uint32_t bitDepth = layout.planes[0].bitDepth;
-
- switch (layout.type) {
- case C2PlanarLayout::TYPE_YUV:
- mediaImage->mType = MediaImage2::MEDIA_IMAGE_TYPE_YUV; break;
- case C2PlanarLayout::TYPE_YUVA:
- mediaImage->mType = MediaImage2::MEDIA_IMAGE_TYPE_YUVA; break;
- case C2PlanarLayout::TYPE_RGB:
- mediaImage->mType = MediaImage2::MEDIA_IMAGE_TYPE_RGB; break;
- case C2PlanarLayout::TYPE_RGBA:
- mediaImage->mType = MediaImage2::MEDIA_IMAGE_TYPE_RGBA; break;
- default:
- mediaImage->mType = MediaImage2::MEDIA_IMAGE_TYPE_UNKNOWN; break;
- }
- mediaImage->mNumPlanes = layout.numPlanes;
- mediaImage->mWidth = mWidth;
- mediaImage->mHeight = mHeight;
- mediaImage->mBitDepth = bitDepth;
- mediaImage->mBitDepthAllocated = mAllocatedDepth;
-
- uint32_t bufferSize = 0;
- for (uint32_t i = 0; i < layout.numPlanes; ++i) {
- const C2PlaneInfo &plane = layout.planes[i];
- if (plane.rightShift != 0) {
- ALOGV("rightShift value of %u unsupported", plane.rightShift);
- mInitCheck = BAD_VALUE;
- return;
- }
- if (plane.endianness != C2PlaneInfo::NATIVE) {
- ALOGV("endianness value of %u unsupported", plane.endianness);
- mInitCheck = BAD_VALUE;
- return;
- }
- if (plane.allocatedDepth != mAllocatedDepth || plane.bitDepth != bitDepth) {
- ALOGV("different allocatedDepth/bitDepth per plane unsupported");
- mInitCheck = BAD_VALUE;
- return;
- }
- bufferSize += mWidth * mHeight
- / plane.rowSampling / plane.colSampling * (plane.allocatedDepth / 8);
- }
-
- mBackBufferSize = bufferSize;
- mInitCheck = OK;
- }
-
- status_t initCheck() const { return mInitCheck; }
-
- uint32_t backBufferSize() const { return mBackBufferSize; }
-
- /**
- * Convert C2GraphicView to MediaImage2. Note that if not wrapped, the content
- * is not copied over in this function --- the caller should use
- * CopyGraphicView2MediaImage() function to do that explicitly.
- *
- * \param view[in] source C2GraphicView object.
- * \param alloc[in] allocator function for ABuffer.
- * \param mediaImage[out] destination MediaImage2 object.
- * \param buffer[out] new buffer object.
- * \param wrapped[out] whether we wrapped around existing map or
- * allocated a new buffer
- *
- * \return true if conversion succeeds,
- * false otherwise; all output params should be ignored.
- */
- sp<ABuffer> wrap() {
- MediaImage2 *mediaImage = getMediaImage();
- const C2PlanarLayout &layout = mView.layout();
- if (layout.numPlanes == 1) {
- const C2PlaneInfo &plane = layout.planes[0];
- ssize_t offset = plane.minOffset(mWidth, mHeight);
- mediaImage->mPlane[0].mOffset = -offset;
- mediaImage->mPlane[0].mColInc = plane.colInc;
- mediaImage->mPlane[0].mRowInc = plane.rowInc;
- mediaImage->mPlane[0].mHorizSubsampling = plane.colSampling;
- mediaImage->mPlane[0].mVertSubsampling = plane.rowSampling;
- return new ABuffer(
- const_cast<uint8_t *>(mView.data()[0] + offset),
- plane.maxOffset(mWidth, mHeight) - offset + 1);
- }
- const uint8_t *minPtr = mView.data()[0];
- const uint8_t *maxPtr = mView.data()[0];
- int32_t planeSize = 0;
- for (uint32_t i = 0; i < layout.numPlanes; ++i) {
- const C2PlaneInfo &plane = layout.planes[i];
- ssize_t minOffset = plane.minOffset(mWidth, mHeight);
- ssize_t maxOffset = plane.maxOffset(mWidth, mHeight);
- if (minPtr > mView.data()[i] + minOffset) {
- minPtr = mView.data()[i] + minOffset;
- }
- if (maxPtr < mView.data()[i] + maxOffset) {
- maxPtr = mView.data()[i] + maxOffset;
- }
- planeSize += std::abs(plane.rowInc) * mHeight
- / plane.rowSampling / plane.colSampling * (mAllocatedDepth / 8);
- }
-
- if ((maxPtr - minPtr + 1) <= planeSize) {
- // FIXME: this is risky as reading/writing data out of bound results in
- // an undefined behavior.
- for (uint32_t i = 0; i < layout.numPlanes; ++i) {
- const C2PlaneInfo &plane = layout.planes[i];
- mediaImage->mPlane[i].mOffset = mView.data()[i] - minPtr;
- mediaImage->mPlane[i].mColInc = plane.colInc;
- mediaImage->mPlane[i].mRowInc = plane.rowInc;
- mediaImage->mPlane[i].mHorizSubsampling = plane.colSampling;
- mediaImage->mPlane[i].mVertSubsampling = plane.rowSampling;
- }
- return new ABuffer(const_cast<uint8_t *>(minPtr), maxPtr - minPtr + 1);
- }
-
- return nullptr;
- }
-
- bool setBackBuffer(const sp<ABuffer> &backBuffer) {
- if (backBuffer->capacity() < mBackBufferSize) {
- return false;
- }
- backBuffer->setRange(0, mBackBufferSize);
-
- const C2PlanarLayout &layout = mView.layout();
- MediaImage2 *mediaImage = getMediaImage();
- uint32_t offset = 0;
- // TODO: keep interleaved planes together
- for (uint32_t i = 0; i < layout.numPlanes; ++i) {
- const C2PlaneInfo &plane = layout.planes[i];
- mediaImage->mPlane[i].mOffset = offset;
- mediaImage->mPlane[i].mColInc = mAllocatedDepth / 8;
- mediaImage->mPlane[i].mRowInc =
- mediaImage->mPlane[i].mColInc * mWidth / plane.colSampling;
- mediaImage->mPlane[i].mHorizSubsampling = plane.colSampling;
- mediaImage->mPlane[i].mVertSubsampling = plane.rowSampling;
- offset += mediaImage->mPlane[i].mRowInc * mHeight / plane.rowSampling;
- }
- mBackBuffer = backBuffer;
- return true;
- }
-
- /**
- * Copy C2GraphicView to MediaImage2. This function assumes that |mediaImage| is
- * an output from GraphicView2MediaImage(), so it mostly skips sanity check.
- *
- * \param view[in] source C2GraphicView object.
- * \param mediaImage[in] destination MediaImage2 object.
- * \param buffer[out] new buffer object.
- */
- void copy() {
- // TODO: more efficient copying --- e.g. one row at a time, copying
- // interleaved planes together, etc.
- const C2PlanarLayout &layout = mView.layout();
- MediaImage2 *mediaImage = getMediaImage();
- uint8_t *dst = mBackBuffer->base();
- for (uint32_t i = 0; i < layout.numPlanes; ++i) {
- const C2PlaneInfo &plane = layout.planes[i];
- const uint8_t *src = mView.data()[i];
- int32_t planeW = mWidth / plane.colSampling;
- int32_t planeH = mHeight / plane.rowSampling;
- for (int32_t row = 0; row < planeH; ++row) {
- for(int32_t col = 0; col < planeW; ++col) {
- memcpy(dst, src, mAllocatedDepth / 8);
- dst += mediaImage->mPlane[i].mColInc;
- src += plane.colInc;
- }
- dst -= mediaImage->mPlane[i].mColInc * planeW;
- dst += mediaImage->mPlane[i].mRowInc;
- src -= plane.colInc * planeW;
- src += plane.rowInc;
- }
- }
- }
-
- const sp<ABuffer> &imageData() const { return mMediaImage; }
-
-private:
- status_t mInitCheck;
-
- const C2GraphicView mView;
- uint32_t mWidth;
- uint32_t mHeight;
- uint32_t mAllocatedDepth;
- uint32_t mBackBufferSize;
- sp<ABuffer> mMediaImage;
- std::function<sp<ABuffer>(size_t)> mAlloc;
-
- sp<ABuffer> mBackBuffer;
-
- MediaImage2 *getMediaImage() {
- return (MediaImage2 *)mMediaImage->base();
- }
-};
-
-} // namespace
-
-// GraphicBlockBuffer
-
-// static
-sp<GraphicBlockBuffer> GraphicBlockBuffer::Allocate(
- const sp<AMessage> &format,
- const std::shared_ptr<C2GraphicBlock> &block,
- std::function<sp<ABuffer>(size_t)> alloc) {
- C2GraphicView view(block->map().get());
- if (view.error() != C2_OK) {
- ALOGD("C2GraphicBlock::map failed: %d", view.error());
- return nullptr;
- }
- GraphicView2MediaImageConverter converter(view);
- if (converter.initCheck() != OK) {
- ALOGD("Converter init failed: %d", converter.initCheck());
- return nullptr;
- }
- bool wrapped = true;
- sp<ABuffer> buffer = converter.wrap();
- if (buffer == nullptr) {
- buffer = alloc(converter.backBufferSize());
- if (!converter.setBackBuffer(buffer)) {
- ALOGD("Converter failed to set back buffer");
- return nullptr;
- }
- wrapped = false;
- }
- return new GraphicBlockBuffer(
- format,
- buffer,
- std::move(view),
- block,
- converter.imageData(),
- wrapped);
-}
-
-GraphicBlockBuffer::GraphicBlockBuffer(
- const sp<AMessage> &format,
- const sp<ABuffer> &buffer,
- C2GraphicView &&view,
- const std::shared_ptr<C2GraphicBlock> &block,
- const sp<ABuffer> &imageData,
- bool wrapped)
- : Codec2Buffer(format, buffer),
- mView(view),
- mBlock(block),
- mImageData(imageData),
- mWrapped(wrapped) {
- meta()->setBuffer("image-data", imageData);
-}
-
-std::shared_ptr<C2Buffer> GraphicBlockBuffer::asC2Buffer() {
- uint32_t width = mView.width();
- uint32_t height = mView.height();
- if (!mWrapped) {
- MediaImage2 *mediaImage = imageData();
- const C2PlanarLayout &layout = mView.layout();
- for (uint32_t i = 0; i < mediaImage->mNumPlanes; ++i) {
- const C2PlaneInfo &plane = layout.planes[i];
- int32_t planeW = width / plane.colSampling;
- int32_t planeH = height / plane.rowSampling;
- const uint8_t *src = base() + mediaImage->mPlane[i].mOffset;
- uint8_t *dst = mView.data()[i];
- for (int32_t row = 0; row < planeH; ++row) {
- for (int32_t col = 0; col < planeW; ++col) {
- memcpy(dst, src, mediaImage->mBitDepthAllocated / 8);
- src += mediaImage->mPlane[i].mColInc;
- dst += plane.colInc;
- }
- src -= mediaImage->mPlane[i].mColInc * planeW;
- dst -= plane.colInc * planeW;
- src += mediaImage->mPlane[i].mRowInc;
- dst += plane.rowInc;
- }
- }
- }
- return C2Buffer::CreateGraphicBuffer(
- mBlock->share(C2Rect(width, height), C2Fence()));
-}
-
-// ConstGraphicBlockBuffer
-
-// static
-sp<ConstGraphicBlockBuffer> ConstGraphicBlockBuffer::Allocate(
- const sp<AMessage> &format,
- const std::shared_ptr<C2Buffer> &buffer,
- std::function<sp<ABuffer>(size_t)> alloc) {
- if (!buffer
- || buffer->data().type() != C2BufferData::GRAPHIC
- || buffer->data().graphicBlocks().size() != 1u) {
- ALOGD("C2Buffer precond fail");
- return nullptr;
- }
- std::unique_ptr<const C2GraphicView> view(std::make_unique<const C2GraphicView>(
- buffer->data().graphicBlocks()[0].map().get()));
- std::unique_ptr<const C2GraphicView> holder;
-
- GraphicView2MediaImageConverter converter(*view);
- if (converter.initCheck() != OK) {
- ALOGD("Converter init failed: %d", converter.initCheck());
- return nullptr;
- }
- bool wrapped = true;
- sp<ABuffer> aBuffer = converter.wrap();
- if (aBuffer == nullptr) {
- aBuffer = alloc(converter.backBufferSize());
- if (!converter.setBackBuffer(aBuffer)) {
- ALOGD("Converter failed to set back buffer");
- return nullptr;
- }
- wrapped = false;
- converter.copy();
- // We don't need the view.
- holder = std::move(view);
- }
- return new ConstGraphicBlockBuffer(
- format,
- aBuffer,
- std::move(view),
- buffer,
- converter.imageData(),
- wrapped);
-}
-
-// static
-sp<ConstGraphicBlockBuffer> ConstGraphicBlockBuffer::AllocateEmpty(
- const sp<AMessage> &format,
- std::function<sp<ABuffer>(size_t)> alloc) {
- int32_t width, height;
- if (!format->findInt32("width", &width)
- || !format->findInt32("height", &height)) {
- ALOGD("format had no width / height");
- return nullptr;
- }
- sp<ABuffer> aBuffer(alloc(width * height * 4));
- return new ConstGraphicBlockBuffer(
- format,
- aBuffer,
- nullptr,
- nullptr,
- nullptr,
- false);
-}
-
-ConstGraphicBlockBuffer::ConstGraphicBlockBuffer(
- const sp<AMessage> &format,
- const sp<ABuffer> &aBuffer,
- std::unique_ptr<const C2GraphicView> &&view,
- const std::shared_ptr<C2Buffer> &buffer,
- const sp<ABuffer> &imageData,
- bool wrapped)
- : Codec2Buffer(format, aBuffer),
- mView(std::move(view)),
- mBufferRef(buffer),
- mWrapped(wrapped) {
- if (imageData != nullptr) {
- meta()->setBuffer("image-data", imageData);
- }
-}
-
-std::shared_ptr<C2Buffer> ConstGraphicBlockBuffer::asC2Buffer() {
- mView.reset();
- return std::move(mBufferRef);
-}
-
-bool ConstGraphicBlockBuffer::canCopy(const std::shared_ptr<C2Buffer> &buffer) const {
- if (mWrapped || mBufferRef) {
- ALOGD("ConstGraphicBlockBuffer::canCopy: %swrapped ; buffer ref %s",
- mWrapped ? "" : "not ", mBufferRef ? "exists" : "doesn't exist");
- return false;
- }
- if (!buffer) {
- // Nothing to copy, so we can copy by doing nothing.
- return true;
- }
- if (buffer->data().type() != C2BufferData::GRAPHIC) {
- ALOGD("ConstGraphicBlockBuffer::canCopy: buffer precondition unsatisfied");
- return false;
- }
- if (buffer->data().graphicBlocks().size() == 0) {
- return true;
- } else if (buffer->data().graphicBlocks().size() != 1u) {
- ALOGD("ConstGraphicBlockBuffer::canCopy: too many blocks");
- return false;
- }
- GraphicView2MediaImageConverter converter(
- buffer->data().graphicBlocks()[0].map().get());
- if (converter.initCheck() != OK) {
- ALOGD("ConstGraphicBlockBuffer::canCopy: converter init failed: %d", converter.initCheck());
- return false;
- }
- if (converter.backBufferSize() > capacity()) {
- ALOGD("ConstGraphicBlockBuffer::canCopy: insufficient capacity: req %u has %zu",
- converter.backBufferSize(), capacity());
- return false;
- }
- return true;
-}
-
-bool ConstGraphicBlockBuffer::copy(const std::shared_ptr<C2Buffer> &buffer) {
- if (!buffer || buffer->data().graphicBlocks().size() == 0) {
- setRange(0, 0);
- return true;
- }
- GraphicView2MediaImageConverter converter(
- buffer->data().graphicBlocks()[0].map().get());
- if (converter.initCheck() != OK) {
- ALOGD("ConstGraphicBlockBuffer::copy: converter init failed: %d", converter.initCheck());
- return false;
- }
- sp<ABuffer> aBuffer = new ABuffer(base(), capacity());
- if (!converter.setBackBuffer(aBuffer)) {
- ALOGD("ConstGraphicBlockBuffer::copy: set back buffer failed");
- return false;
- }
- converter.copy();
- meta()->setBuffer("image-data", converter.imageData());
- mBufferRef = buffer;
- return true;
-}
-
} // namespace android
diff --git a/media/libstagefright/CCodec.cpp b/media/libstagefright/CCodec.cpp
index 0bdd808..0a20d34 100644
--- a/media/libstagefright/CCodec.cpp
+++ b/media/libstagefright/CCodec.cpp
@@ -259,20 +259,26 @@
return;
}
- AString componentName;
- if (!msg->findString("componentName", &componentName)) {
- // TODO: find componentName appropriate with the media type
- }
+ sp<RefBase> codecInfo;
+ CHECK(msg->findObject("codecInfo", &codecInfo));
+ // For Codec 2.0 components, componentName == codecInfo->getCodecName().
sp<AMessage> allocMsg(new AMessage(kWhatAllocate, this));
- allocMsg->setString("componentName", componentName);
+ allocMsg->setObject("codecInfo", codecInfo);
allocMsg->post();
}
-void CCodec::allocate(const AString &componentName) {
- ALOGV("allocate(%s)", componentName.c_str());
+void CCodec::allocate(const sp<MediaCodecInfo> &codecInfo) {
+ if (codecInfo == nullptr) {
+ mCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
+ return;
+ }
+ ALOGV("allocate(%s)", codecInfo->getCodecName());
mListener.reset(new CCodecListener(this));
+ AString componentName = codecInfo->getCodecName();
+ // TODO: use codecInfo->getOwnerName() for connecting to remote process.
+
std::shared_ptr<C2Component> comp;
c2_status_t err = GetCodec2PlatformComponentStore()->createComponent(
componentName.c_str(), &comp);
@@ -812,9 +818,9 @@
case kWhatAllocate: {
// C2ComponentStore::createComponent() should return within 100ms.
setDeadline(now + 150ms, "allocate");
- AString componentName;
- CHECK(msg->findString("componentName", &componentName));
- allocate(componentName);
+ sp<RefBase> obj;
+ CHECK(msg->findObject("codecInfo", &obj));
+ allocate((MediaCodecInfo *)obj.get());
break;
}
case kWhatConfigure: {
diff --git a/media/libstagefright/CCodecBufferChannel.cpp b/media/libstagefright/CCodecBufferChannel.cpp
index 65d637b..cbe4f16 100644
--- a/media/libstagefright/CCodecBufferChannel.cpp
+++ b/media/libstagefright/CCodecBufferChannel.cpp
@@ -49,6 +49,8 @@
using namespace hardware::cas::V1_0;
using namespace hardware::cas::native::V1_0;
+using CasStatus = hardware::cas::V1_0::Status;
+
/**
* Base class for representation of buffers at one port.
*/
@@ -181,6 +183,7 @@
// TODO: get this info from component
const static size_t kMinBufferArraySize = 16;
const static size_t kLinearBufferSize = 524288;
+const static size_t kMaxGraphicBufferRefCount = 4;
/**
* Simple local buffer pool backed by std::vector.
@@ -291,21 +294,6 @@
DISALLOW_EVIL_CONSTRUCTORS(LocalBufferPool);
};
-sp<LinearBlockBuffer> AllocateLinearBuffer(
- const std::shared_ptr<C2BlockPool> &pool,
- const sp<AMessage> &format,
- size_t size,
- const C2MemoryUsage &usage) {
- std::shared_ptr<C2LinearBlock> block;
-
- c2_status_t err = pool->fetchLinearBlock(size, usage, &block);
- if (err != C2_OK) {
- return nullptr;
- }
-
- return LinearBlockBuffer::Allocate(format, block);
-}
-
sp<GraphicBlockBuffer> AllocateGraphicBuffer(
const std::shared_ptr<C2BlockPool> &pool,
const sp<AMessage> &format,
@@ -572,9 +560,7 @@
bool requestNewBuffer(size_t *index, sp<MediaCodecBuffer> *buffer) override {
// TODO: proper max input size
// TODO: read usage from intf
- C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
- sp<LinearBlockBuffer> newBuffer = AllocateLinearBuffer(
- mPool, mFormat, kLinearBufferSize, usage);
+ sp<Codec2Buffer> newBuffer = alloc(kLinearBufferSize);
if (newBuffer == nullptr) {
return false;
}
@@ -598,17 +584,88 @@
array->initialize(
mImpl,
kMinBufferArraySize,
- [pool = mPool, format = mFormat] () -> sp<Codec2Buffer> {
- C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
- return AllocateLinearBuffer(pool, format, kLinearBufferSize, usage);
- });
+ [this] () -> sp<Codec2Buffer> { return alloc(kLinearBufferSize); });
return std::move(array);
}
+ virtual sp<Codec2Buffer> alloc(size_t size) const {
+ C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
+ std::shared_ptr<C2LinearBlock> block;
+
+ c2_status_t err = mPool->fetchLinearBlock(size, usage, &block);
+ if (err != C2_OK) {
+ return nullptr;
+ }
+
+ return LinearBlockBuffer::Allocate(mFormat, block);
+ }
+
private:
FlexBuffersImpl mImpl;
};
+class EncryptedLinearInputBuffers : public LinearInputBuffers {
+public:
+ EncryptedLinearInputBuffers(
+ bool secure,
+ const sp<MemoryDealer> &dealer,
+ const sp<ICrypto> &crypto,
+ int32_t heapSeqNum)
+ : mUsage({0, 0}),
+ mDealer(dealer),
+ mCrypto(crypto),
+ mHeapSeqNum(heapSeqNum) {
+ if (secure) {
+ mUsage = { C2MemoryUsage::READ_PROTECTED, 0 };
+ } else {
+ mUsage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
+ }
+ for (size_t i = 0; i < kMinBufferArraySize; ++i) {
+ sp<IMemory> memory = mDealer->allocate(kLinearBufferSize);
+ if (memory == nullptr) {
+ ALOGD("Failed to allocate memory from dealer: only %zu slots allocated", i);
+ break;
+ }
+ mMemoryVector.push_back({std::weak_ptr<C2LinearBlock>(), memory});
+ }
+ }
+
+ ~EncryptedLinearInputBuffers() override {
+ }
+
+ sp<Codec2Buffer> alloc(size_t size) const override {
+ sp<IMemory> memory;
+ for (const Entry &entry : mMemoryVector) {
+ if (entry.block.expired()) {
+ memory = entry.memory;
+ break;
+ }
+ }
+ if (memory == nullptr) {
+ return nullptr;
+ }
+
+ std::shared_ptr<C2LinearBlock> block;
+ c2_status_t err = mPool->fetchLinearBlock(size, mUsage, &block);
+ if (err != C2_OK) {
+ return nullptr;
+ }
+
+ return new EncryptedLinearBlockBuffer(mFormat, block, memory, mHeapSeqNum);
+ }
+
+private:
+ C2MemoryUsage mUsage;
+ sp<MemoryDealer> mDealer;
+ sp<ICrypto> mCrypto;
+ int32_t mHeapSeqNum;
+ struct Entry {
+ std::weak_ptr<C2LinearBlock> block;
+ sp<IMemory> memory;
+ };
+ std::vector<Entry> mMemoryVector;
+};
+
class GraphicInputBuffers : public CCodecBufferChannel::InputBuffers {
public:
GraphicInputBuffers() : mLocalBufferPool(LocalBufferPool::Create(1920 * 1080 * 16)) {}
@@ -956,7 +1013,8 @@
CCodecBufferChannel::CCodecBufferChannel(
const std::function<void(status_t, enum ActionCode)> &onError)
- : mOnError(onError),
+ : mHeapSeqNum(-1),
+ mOnError(onError),
mFrameIndex(0u),
mFirstValidFrameIndex(0u) {
}
@@ -978,13 +1036,7 @@
return OK;
}
-status_t CCodecBufferChannel::queueInputBuffer(const sp<MediaCodecBuffer> &buffer) {
- QueueGuard guard(mSync);
- if (!guard.isRunning()) {
- ALOGW("No more buffers should be queued at current state.");
- return -ENOSYS;
- }
-
+status_t CCodecBufferChannel::queueInputBufferInternal(const sp<MediaCodecBuffer> &buffer) {
int64_t timeUs;
CHECK(buffer->meta()->findInt64("timeUs", &timeUs));
@@ -1005,7 +1057,11 @@
work->input.buffers.clear();
{
Mutexed<std::unique_ptr<InputBuffers>>::Locked buffers(mInputBuffers);
- work->input.buffers.push_back((*buffers)->releaseBuffer(buffer));
+ std::shared_ptr<C2Buffer> c2buffer = (*buffers)->releaseBuffer(buffer);
+ if (!c2buffer) {
+ return -ENOENT;
+ }
+ work->input.buffers.push_back(c2buffer);
}
// TODO: fill info's
@@ -1017,22 +1073,103 @@
return mComponent->queue_nb(&items);
}
+status_t CCodecBufferChannel::queueInputBuffer(const sp<MediaCodecBuffer> &buffer) {
+ QueueGuard guard(mSync);
+ if (!guard.isRunning()) {
+ ALOGW("No more buffers should be queued at current state.");
+ return -ENOSYS;
+ }
+ return queueInputBufferInternal(buffer);
+}
+
status_t CCodecBufferChannel::queueSecureInputBuffer(
const sp<MediaCodecBuffer> &buffer, bool secure, const uint8_t *key,
const uint8_t *iv, CryptoPlugin::Mode mode, CryptoPlugin::Pattern pattern,
const CryptoPlugin::SubSample *subSamples, size_t numSubSamples,
AString *errorDetailMsg) {
- // TODO
- (void) buffer;
- (void) secure;
- (void) key;
- (void) iv;
- (void) mode;
- (void) pattern;
- (void) subSamples;
- (void) numSubSamples;
- (void) errorDetailMsg;
- return -ENOSYS;
+ QueueGuard guard(mSync);
+ if (!guard.isRunning()) {
+ ALOGW("No more buffers should be queued at current state.");
+ return -ENOSYS;
+ }
+
+ if (!hasCryptoOrDescrambler()) {
+ return -ENOSYS;
+ }
+ sp<EncryptedLinearBlockBuffer> encryptedBuffer((EncryptedLinearBlockBuffer *)buffer.get());
+
+ ssize_t result = -1;
+ if (mCrypto != nullptr) {
+ ICrypto::DestinationBuffer destination;
+ if (secure) {
+ destination.mType = ICrypto::kDestinationTypeNativeHandle;
+ destination.mHandle = encryptedBuffer->handle();
+ } else {
+ destination.mType = ICrypto::kDestinationTypeSharedMemory;
+ destination.mSharedMemory = mDecryptDestination;
+ }
+ ICrypto::SourceBuffer source;
+ encryptedBuffer->fillSourceBuffer(&source);
+ result = mCrypto->decrypt(
+ key, iv, mode, pattern, source, buffer->offset(),
+ subSamples, numSubSamples, destination, errorDetailMsg);
+ if (result < 0) {
+ return result;
+ }
+ if (destination.mType == ICrypto::kDestinationTypeSharedMemory) {
+ encryptedBuffer->copyDecryptedContent(mDecryptDestination, result);
+ }
+ } else {
+ // Here we cast CryptoPlugin::SubSample to hardware::cas::native::V1_0::SubSample
+ // directly, the structure definitions should match as checked in DescramblerImpl.cpp.
+ hidl_vec<SubSample> hidlSubSamples;
+ hidlSubSamples.setToExternal((SubSample *)subSamples, numSubSamples, false /*own*/);
+
+ hardware::cas::native::V1_0::SharedBuffer srcBuffer;
+ encryptedBuffer->fillSourceBuffer(&srcBuffer);
+
+ DestinationBuffer dstBuffer;
+ if (secure) {
+ dstBuffer.type = BufferType::NATIVE_HANDLE;
+ dstBuffer.secureMemory = hidl_handle(encryptedBuffer->handle());
+ } else {
+ dstBuffer.type = BufferType::SHARED_MEMORY;
+ dstBuffer.nonsecureMemory = srcBuffer;
+ }
+
+ CasStatus status = CasStatus::OK;
+ hidl_string detailedError;
+
+ auto returnVoid = mDescrambler->descramble(
+ key != NULL ? (ScramblingControl)key[0] : ScramblingControl::UNSCRAMBLED,
+ hidlSubSamples,
+ srcBuffer,
+ 0,
+ dstBuffer,
+ 0,
+ [&status, &result, &detailedError] (
+ CasStatus _status, uint32_t _bytesWritten,
+ const hidl_string& _detailedError) {
+ status = _status;
+ result = (ssize_t)_bytesWritten;
+ detailedError = _detailedError;
+ });
+
+ if (!returnVoid.isOk() || status != CasStatus::OK || result < 0) {
+ ALOGE("descramble failed, trans=%s, status=%d, result=%zd",
+ returnVoid.description().c_str(), status, result);
+ return UNKNOWN_ERROR;
+ }
+
+ ALOGV("descramble succeeded, %zd bytes", result);
+
+ if (dstBuffer.type == BufferType::SHARED_MEMORY) {
+ encryptedBuffer->copyDecryptedContentFromMemory(result);
+ }
+ }
+
+ buffer->setRange(0, result);
+ return queueInputBufferInternal(buffer);
}
void CCodecBufferChannel::feedInputBufferIfAvailable() {
@@ -1061,8 +1198,8 @@
c2Buffer = (*buffers)->releaseBuffer(buffer);
}
- Mutexed<sp<Surface>>::Locked surface(mSurface);
- if (*surface == nullptr) {
+ Mutexed<OutputSurface>::Locked output(mOutputSurface);
+ if (output->surface == nullptr) {
ALOGE("no surface");
return OK;
}
@@ -1079,7 +1216,7 @@
GraphicBuffer::CLONE_HANDLE,
blocks.front().width(),
blocks.front().height(),
- HAL_PIXEL_FORMAT_YV12,
+ HAL_PIXEL_FORMAT_YCbCr_420_888,
// TODO
1,
(uint64_t)GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
@@ -1087,7 +1224,7 @@
blocks.front().width()));
native_handle_delete(grallocHandle);
- status_t result = (*surface)->attachBuffer(graphicBuffer.get());
+ status_t result = output->surface->attachBuffer(graphicBuffer.get());
if (result != OK) {
ALOGE("attachBuffer failed: %d", result);
return result;
@@ -1095,23 +1232,32 @@
// TODO: read and set crop
- result = native_window_set_buffers_timestamp((*surface).get(), timestampNs);
+ result = native_window_set_buffers_timestamp(output->surface.get(), timestampNs);
ALOGW_IF(result != OK, "failed to set buffer timestamp: %d", result);
// TODO: fix after C2Fence implementation
#if 0
const C2Fence &fence = blocks.front().fence();
- result = ((ANativeWindow *)(*surface).get())->queueBuffer(
- (*surface).get(), graphicBuffer.get(), fence.valid() ? fence.fd() : -1);
+ result = ((ANativeWindow *)output->surface.get())->queueBuffer(
+ output->surface.get(), graphicBuffer.get(), fence.valid() ? fence.fd() : -1);
#else
- result = ((ANativeWindow *)(*surface).get())->queueBuffer(
- (*surface).get(), graphicBuffer.get(), -1);
+ result = ((ANativeWindow *)output->surface.get())->queueBuffer(
+ output->surface.get(), graphicBuffer.get(), -1);
#endif
if (result != OK) {
ALOGE("queueBuffer failed: %d", result);
return result;
}
+ // XXX: Hack to keep C2Buffers unreleased until the consumer is done
+ // reading the content. Eventually IGBP-based C2BlockPool should handle
+ // the lifecycle.
+ output->bufferRefs.push_back(c2Buffer);
+ if (output->bufferRefs.size() > output->maxBufferCount + 1) {
+ output->bufferRefs.pop_front();
+ ALOGV("%zu buffer refs remaining", output->bufferRefs.size());
+ }
+
return OK;
}
@@ -1166,6 +1312,7 @@
if (err != C2_OK) {
return UNKNOWN_ERROR;
}
+ bool secure = mComponent->intf()->getName().find(".secure") != std::string::npos;
if (inputFormat != nullptr) {
Mutexed<std::unique_ptr<InputBuffers>>::Locked buffers(mInputBuffers);
@@ -1178,7 +1325,24 @@
buffers->reset(new GraphicInputBuffers);
}
} else {
- buffers->reset(new LinearInputBuffers);
+ if (hasCryptoOrDescrambler()) {
+ if (mDealer == nullptr) {
+ mDealer = new MemoryDealer(
+ align(kLinearBufferSize, MemoryDealer::getAllocationAlignment())
+ * (kMinBufferArraySize + 1),
+ "EncryptedLinearInputBuffers");
+ mDecryptDestination = mDealer->allocate(kLinearBufferSize);
+ }
+ if (mCrypto != nullptr && mHeapSeqNum < 0) {
+ mHeapSeqNum = mCrypto->setHeap(mDealer->getMemoryHeap());
+ } else {
+ mHeapSeqNum = -1;
+ }
+ buffers->reset(new EncryptedLinearInputBuffers(
+ secure, mDealer, mCrypto, mHeapSeqNum));
+ } else {
+ buffers->reset(new LinearInputBuffers);
+ }
}
(*buffers)->setFormat(inputFormat);
@@ -1200,8 +1364,8 @@
if (outputFormat != nullptr) {
bool hasOutputSurface = false;
{
- Mutexed<sp<Surface>>::Locked surface(mSurface);
- hasOutputSurface = (*surface != nullptr);
+ Mutexed<OutputSurface>::Locked output(mOutputSurface);
+ hasOutputSurface = (output->surface != nullptr);
}
Mutexed<std::unique_ptr<OutputBuffers>>::Locked buffers(mOutputBuffers);
@@ -1381,7 +1545,7 @@
newSurface->setScalingMode(NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW);
}
- Mutexed<sp<Surface>>::Locked surface(mSurface);
+ Mutexed<OutputSurface>::Locked output(mOutputSurface);
// if (newSurface == nullptr) {
// if (*surface != nullptr) {
// ALOGW("cannot unset a surface");
@@ -1395,7 +1559,11 @@
// return INVALID_OPERATION;
// }
- *surface = newSurface;
+ output->surface = newSurface;
+ output->bufferRefs.clear();
+ // XXX: hack
+ output->maxBufferCount = kMaxGraphicBufferRefCount;
+
return OK;
}
diff --git a/media/libstagefright/Codec2Buffer.cpp b/media/libstagefright/Codec2Buffer.cpp
new file mode 100644
index 0000000..d2ef229
--- /dev/null
+++ b/media/libstagefright/Codec2Buffer.cpp
@@ -0,0 +1,672 @@
+/*
+ * Copyright 2018, 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 "Codec2Buffer"
+#include <utils/Log.h>
+
+#include <hidlmemory/FrameworkUtils.h>
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/AMessage.h>
+
+#include "include/Codec2Buffer.h"
+
+namespace android {
+
+// Codec2Buffer
+
+bool Codec2Buffer::canCopyLinear(const std::shared_ptr<C2Buffer> &buffer) const {
+ if (const_cast<Codec2Buffer *>(this)->base() == nullptr) {
+ return false;
+ }
+ if (!buffer) {
+ // Nothing to copy, so we can copy by doing nothing.
+ return true;
+ }
+ if (buffer->data().type() != C2BufferData::LINEAR) {
+ return false;
+ }
+ if (buffer->data().linearBlocks().size() == 0u) {
+ // Nothing to copy, so we can copy by doing nothing.
+ return true;
+ } else if (buffer->data().linearBlocks().size() > 1u) {
+ // We don't know how to copy more than one blocks.
+ return false;
+ }
+ if (buffer->data().linearBlocks()[0].size() > capacity()) {
+ // It won't fit.
+ return false;
+ }
+ return true;
+}
+
+bool Codec2Buffer::copyLinear(const std::shared_ptr<C2Buffer> &buffer) {
+ // We assume that all canCopyLinear() checks passed.
+ if (!buffer || buffer->data().linearBlocks().size() == 0u) {
+ setRange(0, 0);
+ return true;
+ }
+ C2ReadView view = buffer->data().linearBlocks()[0].map().get();
+ if (view.error() != C2_OK) {
+ ALOGD("Error while mapping: %d", view.error());
+ return false;
+ }
+ if (view.capacity() > capacity()) {
+ ALOGD("C2ConstLinearBlock lied --- it actually doesn't fit: view(%u) > this(%zu)",
+ view.capacity(), capacity());
+ return false;
+ }
+ memcpy(base(), view.data(), view.capacity());
+ setRange(0, view.capacity());
+ return true;
+}
+
+// LocalLinearBuffer
+
+bool LocalLinearBuffer::canCopy(const std::shared_ptr<C2Buffer> &buffer) const {
+ return canCopyLinear(buffer);
+}
+
+bool LocalLinearBuffer::copy(const std::shared_ptr<C2Buffer> &buffer) {
+ return copyLinear(buffer);
+}
+
+// DummyContainerBuffer
+
+DummyContainerBuffer::DummyContainerBuffer(
+ const sp<AMessage> &format, const std::shared_ptr<C2Buffer> &buffer)
+ : Codec2Buffer(format, new ABuffer(nullptr, 1)),
+ mBufferRef(buffer) {
+ setRange(0, buffer ? 1 : 0);
+}
+
+std::shared_ptr<C2Buffer> DummyContainerBuffer::asC2Buffer() {
+ return std::move(mBufferRef);
+}
+
+bool DummyContainerBuffer::canCopy(const std::shared_ptr<C2Buffer> &) const {
+ return !mBufferRef;
+}
+
+bool DummyContainerBuffer::copy(const std::shared_ptr<C2Buffer> &buffer) {
+ mBufferRef = buffer;
+ setRange(0, mBufferRef ? 1 : 0);
+ return true;
+}
+
+// LinearBlockBuffer
+
+// static
+sp<LinearBlockBuffer> LinearBlockBuffer::Allocate(
+ const sp<AMessage> &format, const std::shared_ptr<C2LinearBlock> &block) {
+ C2WriteView writeView(block->map().get());
+ if (writeView.error() != C2_OK) {
+ return nullptr;
+ }
+ return new LinearBlockBuffer(format, std::move(writeView), block);
+}
+
+std::shared_ptr<C2Buffer> LinearBlockBuffer::asC2Buffer() {
+ return C2Buffer::CreateLinearBuffer(mBlock->share(offset(), size(), C2Fence()));
+}
+
+bool LinearBlockBuffer::canCopy(const std::shared_ptr<C2Buffer> &buffer) const {
+ return canCopyLinear(buffer);
+}
+
+bool LinearBlockBuffer::copy(const std::shared_ptr<C2Buffer> &buffer) {
+ return copyLinear(buffer);
+}
+
+LinearBlockBuffer::LinearBlockBuffer(
+ const sp<AMessage> &format,
+ C2WriteView&& writeView,
+ const std::shared_ptr<C2LinearBlock> &block)
+ : Codec2Buffer(format, new ABuffer(writeView.data(), writeView.size())),
+ mWriteView(writeView),
+ mBlock(block) {
+}
+
+// ConstLinearBlockBuffer
+
+// static
+sp<ConstLinearBlockBuffer> ConstLinearBlockBuffer::Allocate(
+ const sp<AMessage> &format, const std::shared_ptr<C2Buffer> &buffer) {
+ if (!buffer
+ || buffer->data().type() != C2BufferData::LINEAR
+ || buffer->data().linearBlocks().size() != 1u) {
+ return nullptr;
+ }
+ C2ReadView readView(buffer->data().linearBlocks()[0].map().get());
+ if (readView.error() != C2_OK) {
+ return nullptr;
+ }
+ return new ConstLinearBlockBuffer(format, std::move(readView), buffer);
+}
+
+ConstLinearBlockBuffer::ConstLinearBlockBuffer(
+ const sp<AMessage> &format,
+ C2ReadView&& readView,
+ const std::shared_ptr<C2Buffer> &buffer)
+ : Codec2Buffer(format, new ABuffer(
+ // NOTE: ABuffer only takes non-const pointer but this data is
+ // supposed to be read-only.
+ const_cast<uint8_t *>(readView.data()), readView.capacity())),
+ mReadView(readView),
+ mBufferRef(buffer) {
+}
+
+std::shared_ptr<C2Buffer> ConstLinearBlockBuffer::asC2Buffer() {
+ return std::move(mBufferRef);
+}
+
+// GraphicView2MediaImageConverter
+
+namespace {
+
+class GraphicView2MediaImageConverter {
+public:
+ explicit GraphicView2MediaImageConverter(const C2GraphicView &view)
+ : mInitCheck(NO_INIT),
+ mView(view),
+ mWidth(view.width()),
+ mHeight(view.height()),
+ mAllocatedDepth(0),
+ mBackBufferSize(0),
+ mMediaImage(new ABuffer(sizeof(MediaImage2))) {
+ if (view.error() != C2_OK) {
+ ALOGD("Converter: view.error() = %d", view.error());
+ mInitCheck = BAD_VALUE;
+ return;
+ }
+ MediaImage2 *mediaImage = (MediaImage2 *)mMediaImage->base();
+ const C2PlanarLayout &layout = view.layout();
+ if (layout.numPlanes == 0) {
+ ALOGD("Converter: 0 planes");
+ mInitCheck = BAD_VALUE;
+ return;
+ }
+ mAllocatedDepth = layout.planes[0].allocatedDepth;
+ uint32_t bitDepth = layout.planes[0].bitDepth;
+
+ switch (layout.type) {
+ case C2PlanarLayout::TYPE_YUV:
+ mediaImage->mType = MediaImage2::MEDIA_IMAGE_TYPE_YUV; break;
+ case C2PlanarLayout::TYPE_YUVA:
+ mediaImage->mType = MediaImage2::MEDIA_IMAGE_TYPE_YUVA; break;
+ case C2PlanarLayout::TYPE_RGB:
+ mediaImage->mType = MediaImage2::MEDIA_IMAGE_TYPE_RGB; break;
+ case C2PlanarLayout::TYPE_RGBA:
+ mediaImage->mType = MediaImage2::MEDIA_IMAGE_TYPE_RGBA; break;
+ default:
+ mediaImage->mType = MediaImage2::MEDIA_IMAGE_TYPE_UNKNOWN; break;
+ }
+ mediaImage->mNumPlanes = layout.numPlanes;
+ mediaImage->mWidth = mWidth;
+ mediaImage->mHeight = mHeight;
+ mediaImage->mBitDepth = bitDepth;
+ mediaImage->mBitDepthAllocated = mAllocatedDepth;
+
+ uint32_t bufferSize = 0;
+ for (uint32_t i = 0; i < layout.numPlanes; ++i) {
+ const C2PlaneInfo &plane = layout.planes[i];
+ if (plane.rightShift != 0) {
+ ALOGV("rightShift value of %u unsupported", plane.rightShift);
+ mInitCheck = BAD_VALUE;
+ return;
+ }
+ if (plane.endianness != C2PlaneInfo::NATIVE) {
+ ALOGV("endianness value of %u unsupported", plane.endianness);
+ mInitCheck = BAD_VALUE;
+ return;
+ }
+ if (plane.allocatedDepth != mAllocatedDepth || plane.bitDepth != bitDepth) {
+ ALOGV("different allocatedDepth/bitDepth per plane unsupported");
+ mInitCheck = BAD_VALUE;
+ return;
+ }
+ bufferSize += mWidth * mHeight
+ / plane.rowSampling / plane.colSampling * (plane.allocatedDepth / 8);
+ }
+
+ mBackBufferSize = bufferSize;
+ mInitCheck = OK;
+ }
+
+ status_t initCheck() const { return mInitCheck; }
+
+ uint32_t backBufferSize() const { return mBackBufferSize; }
+
+ /**
+ * Convert C2GraphicView to MediaImage2. Note that if not wrapped, the content
+ * is not copied over in this function --- the caller should use
+ * CopyGraphicView2MediaImage() function to do that explicitly.
+ *
+ * \param view[in] source C2GraphicView object.
+ * \param alloc[in] allocator function for ABuffer.
+ * \param mediaImage[out] destination MediaImage2 object.
+ * \param buffer[out] new buffer object.
+ * \param wrapped[out] whether we wrapped around existing map or
+ * allocated a new buffer
+ *
+ * \return true if conversion succeeds,
+ * false otherwise; all output params should be ignored.
+ */
+ sp<ABuffer> wrap() {
+ MediaImage2 *mediaImage = getMediaImage();
+ const C2PlanarLayout &layout = mView.layout();
+ if (layout.numPlanes == 1) {
+ const C2PlaneInfo &plane = layout.planes[0];
+ ssize_t offset = plane.minOffset(mWidth, mHeight);
+ mediaImage->mPlane[0].mOffset = -offset;
+ mediaImage->mPlane[0].mColInc = plane.colInc;
+ mediaImage->mPlane[0].mRowInc = plane.rowInc;
+ mediaImage->mPlane[0].mHorizSubsampling = plane.colSampling;
+ mediaImage->mPlane[0].mVertSubsampling = plane.rowSampling;
+ return new ABuffer(
+ const_cast<uint8_t *>(mView.data()[0] + offset),
+ plane.maxOffset(mWidth, mHeight) - offset + 1);
+ }
+ const uint8_t *minPtr = mView.data()[0];
+ const uint8_t *maxPtr = mView.data()[0];
+ int32_t planeSize = 0;
+ for (uint32_t i = 0; i < layout.numPlanes; ++i) {
+ const C2PlaneInfo &plane = layout.planes[i];
+ ssize_t minOffset = plane.minOffset(mWidth, mHeight);
+ ssize_t maxOffset = plane.maxOffset(mWidth, mHeight);
+ if (minPtr > mView.data()[i] + minOffset) {
+ minPtr = mView.data()[i] + minOffset;
+ }
+ if (maxPtr < mView.data()[i] + maxOffset) {
+ maxPtr = mView.data()[i] + maxOffset;
+ }
+ planeSize += std::abs(plane.rowInc) * mHeight
+ / plane.rowSampling / plane.colSampling * (mAllocatedDepth / 8);
+ }
+
+ if ((maxPtr - minPtr + 1) <= planeSize) {
+ // FIXME: this is risky as reading/writing data out of bound results in
+ // an undefined behavior.
+ for (uint32_t i = 0; i < layout.numPlanes; ++i) {
+ const C2PlaneInfo &plane = layout.planes[i];
+ mediaImage->mPlane[i].mOffset = mView.data()[i] - minPtr;
+ mediaImage->mPlane[i].mColInc = plane.colInc;
+ mediaImage->mPlane[i].mRowInc = plane.rowInc;
+ mediaImage->mPlane[i].mHorizSubsampling = plane.colSampling;
+ mediaImage->mPlane[i].mVertSubsampling = plane.rowSampling;
+ }
+ return new ABuffer(const_cast<uint8_t *>(minPtr), maxPtr - minPtr + 1);
+ }
+
+ return nullptr;
+ }
+
+ bool setBackBuffer(const sp<ABuffer> &backBuffer) {
+ if (backBuffer->capacity() < mBackBufferSize) {
+ return false;
+ }
+ backBuffer->setRange(0, mBackBufferSize);
+
+ const C2PlanarLayout &layout = mView.layout();
+ MediaImage2 *mediaImage = getMediaImage();
+ uint32_t offset = 0;
+ // TODO: keep interleaved planes together
+ for (uint32_t i = 0; i < layout.numPlanes; ++i) {
+ const C2PlaneInfo &plane = layout.planes[i];
+ mediaImage->mPlane[i].mOffset = offset;
+ mediaImage->mPlane[i].mColInc = mAllocatedDepth / 8;
+ mediaImage->mPlane[i].mRowInc =
+ mediaImage->mPlane[i].mColInc * mWidth / plane.colSampling;
+ mediaImage->mPlane[i].mHorizSubsampling = plane.colSampling;
+ mediaImage->mPlane[i].mVertSubsampling = plane.rowSampling;
+ offset += mediaImage->mPlane[i].mRowInc * mHeight / plane.rowSampling;
+ }
+ mBackBuffer = backBuffer;
+ return true;
+ }
+
+ /**
+ * Copy C2GraphicView to MediaImage2. This function assumes that |mediaImage| is
+ * an output from GraphicView2MediaImage(), so it mostly skips sanity check.
+ *
+ * \param view[in] source C2GraphicView object.
+ * \param mediaImage[in] destination MediaImage2 object.
+ * \param buffer[out] new buffer object.
+ */
+ void copy() {
+ // TODO: more efficient copying --- e.g. one row at a time, copying
+ // interleaved planes together, etc.
+ const C2PlanarLayout &layout = mView.layout();
+ MediaImage2 *mediaImage = getMediaImage();
+ uint8_t *dst = mBackBuffer->base();
+ for (uint32_t i = 0; i < layout.numPlanes; ++i) {
+ const C2PlaneInfo &plane = layout.planes[i];
+ const uint8_t *src = mView.data()[i];
+ int32_t planeW = mWidth / plane.colSampling;
+ int32_t planeH = mHeight / plane.rowSampling;
+ for (int32_t row = 0; row < planeH; ++row) {
+ for(int32_t col = 0; col < planeW; ++col) {
+ memcpy(dst, src, mAllocatedDepth / 8);
+ dst += mediaImage->mPlane[i].mColInc;
+ src += plane.colInc;
+ }
+ dst -= mediaImage->mPlane[i].mColInc * planeW;
+ dst += mediaImage->mPlane[i].mRowInc;
+ src -= plane.colInc * planeW;
+ src += plane.rowInc;
+ }
+ }
+ }
+
+ const sp<ABuffer> &imageData() const { return mMediaImage; }
+
+private:
+ status_t mInitCheck;
+
+ const C2GraphicView mView;
+ uint32_t mWidth;
+ uint32_t mHeight;
+ uint32_t mAllocatedDepth;
+ uint32_t mBackBufferSize;
+ sp<ABuffer> mMediaImage;
+ std::function<sp<ABuffer>(size_t)> mAlloc;
+
+ sp<ABuffer> mBackBuffer;
+
+ MediaImage2 *getMediaImage() {
+ return (MediaImage2 *)mMediaImage->base();
+ }
+};
+
+} // namespace
+
+// GraphicBlockBuffer
+
+// static
+sp<GraphicBlockBuffer> GraphicBlockBuffer::Allocate(
+ const sp<AMessage> &format,
+ const std::shared_ptr<C2GraphicBlock> &block,
+ std::function<sp<ABuffer>(size_t)> alloc) {
+ C2GraphicView view(block->map().get());
+ if (view.error() != C2_OK) {
+ ALOGD("C2GraphicBlock::map failed: %d", view.error());
+ return nullptr;
+ }
+ GraphicView2MediaImageConverter converter(view);
+ if (converter.initCheck() != OK) {
+ ALOGD("Converter init failed: %d", converter.initCheck());
+ return nullptr;
+ }
+ bool wrapped = true;
+ sp<ABuffer> buffer = converter.wrap();
+ if (buffer == nullptr) {
+ buffer = alloc(converter.backBufferSize());
+ if (!converter.setBackBuffer(buffer)) {
+ ALOGD("Converter failed to set back buffer");
+ return nullptr;
+ }
+ wrapped = false;
+ }
+ return new GraphicBlockBuffer(
+ format,
+ buffer,
+ std::move(view),
+ block,
+ converter.imageData(),
+ wrapped);
+}
+
+GraphicBlockBuffer::GraphicBlockBuffer(
+ const sp<AMessage> &format,
+ const sp<ABuffer> &buffer,
+ C2GraphicView &&view,
+ const std::shared_ptr<C2GraphicBlock> &block,
+ const sp<ABuffer> &imageData,
+ bool wrapped)
+ : Codec2Buffer(format, buffer),
+ mView(view),
+ mBlock(block),
+ mImageData(imageData),
+ mWrapped(wrapped) {
+ meta()->setBuffer("image-data", imageData);
+}
+
+std::shared_ptr<C2Buffer> GraphicBlockBuffer::asC2Buffer() {
+ uint32_t width = mView.width();
+ uint32_t height = mView.height();
+ if (!mWrapped) {
+ MediaImage2 *mediaImage = imageData();
+ const C2PlanarLayout &layout = mView.layout();
+ for (uint32_t i = 0; i < mediaImage->mNumPlanes; ++i) {
+ const C2PlaneInfo &plane = layout.planes[i];
+ int32_t planeW = width / plane.colSampling;
+ int32_t planeH = height / plane.rowSampling;
+ const uint8_t *src = base() + mediaImage->mPlane[i].mOffset;
+ uint8_t *dst = mView.data()[i];
+ for (int32_t row = 0; row < planeH; ++row) {
+ for (int32_t col = 0; col < planeW; ++col) {
+ memcpy(dst, src, mediaImage->mBitDepthAllocated / 8);
+ src += mediaImage->mPlane[i].mColInc;
+ dst += plane.colInc;
+ }
+ src -= mediaImage->mPlane[i].mColInc * planeW;
+ dst -= plane.colInc * planeW;
+ src += mediaImage->mPlane[i].mRowInc;
+ dst += plane.rowInc;
+ }
+ }
+ }
+ return C2Buffer::CreateGraphicBuffer(
+ mBlock->share(C2Rect(width, height), C2Fence()));
+}
+
+// ConstGraphicBlockBuffer
+
+// static
+sp<ConstGraphicBlockBuffer> ConstGraphicBlockBuffer::Allocate(
+ const sp<AMessage> &format,
+ const std::shared_ptr<C2Buffer> &buffer,
+ std::function<sp<ABuffer>(size_t)> alloc) {
+ if (!buffer
+ || buffer->data().type() != C2BufferData::GRAPHIC
+ || buffer->data().graphicBlocks().size() != 1u) {
+ ALOGD("C2Buffer precond fail");
+ return nullptr;
+ }
+ std::unique_ptr<const C2GraphicView> view(std::make_unique<const C2GraphicView>(
+ buffer->data().graphicBlocks()[0].map().get()));
+ std::unique_ptr<const C2GraphicView> holder;
+
+ GraphicView2MediaImageConverter converter(*view);
+ if (converter.initCheck() != OK) {
+ ALOGD("Converter init failed: %d", converter.initCheck());
+ return nullptr;
+ }
+ bool wrapped = true;
+ sp<ABuffer> aBuffer = converter.wrap();
+ if (aBuffer == nullptr) {
+ aBuffer = alloc(converter.backBufferSize());
+ if (!converter.setBackBuffer(aBuffer)) {
+ ALOGD("Converter failed to set back buffer");
+ return nullptr;
+ }
+ wrapped = false;
+ converter.copy();
+ // We don't need the view.
+ holder = std::move(view);
+ }
+ return new ConstGraphicBlockBuffer(
+ format,
+ aBuffer,
+ std::move(view),
+ buffer,
+ converter.imageData(),
+ wrapped);
+}
+
+// static
+sp<ConstGraphicBlockBuffer> ConstGraphicBlockBuffer::AllocateEmpty(
+ const sp<AMessage> &format,
+ std::function<sp<ABuffer>(size_t)> alloc) {
+ int32_t width, height;
+ if (!format->findInt32("width", &width)
+ || !format->findInt32("height", &height)) {
+ ALOGD("format had no width / height");
+ return nullptr;
+ }
+ sp<ABuffer> aBuffer(alloc(width * height * 4));
+ return new ConstGraphicBlockBuffer(
+ format,
+ aBuffer,
+ nullptr,
+ nullptr,
+ nullptr,
+ false);
+}
+
+ConstGraphicBlockBuffer::ConstGraphicBlockBuffer(
+ const sp<AMessage> &format,
+ const sp<ABuffer> &aBuffer,
+ std::unique_ptr<const C2GraphicView> &&view,
+ const std::shared_ptr<C2Buffer> &buffer,
+ const sp<ABuffer> &imageData,
+ bool wrapped)
+ : Codec2Buffer(format, aBuffer),
+ mView(std::move(view)),
+ mBufferRef(buffer),
+ mWrapped(wrapped) {
+ if (imageData != nullptr) {
+ meta()->setBuffer("image-data", imageData);
+ }
+}
+
+std::shared_ptr<C2Buffer> ConstGraphicBlockBuffer::asC2Buffer() {
+ mView.reset();
+ return std::move(mBufferRef);
+}
+
+bool ConstGraphicBlockBuffer::canCopy(const std::shared_ptr<C2Buffer> &buffer) const {
+ if (mWrapped || mBufferRef) {
+ ALOGD("ConstGraphicBlockBuffer::canCopy: %swrapped ; buffer ref %s",
+ mWrapped ? "" : "not ", mBufferRef ? "exists" : "doesn't exist");
+ return false;
+ }
+ if (!buffer) {
+ // Nothing to copy, so we can copy by doing nothing.
+ return true;
+ }
+ if (buffer->data().type() != C2BufferData::GRAPHIC) {
+ ALOGD("ConstGraphicBlockBuffer::canCopy: buffer precondition unsatisfied");
+ return false;
+ }
+ if (buffer->data().graphicBlocks().size() == 0) {
+ return true;
+ } else if (buffer->data().graphicBlocks().size() != 1u) {
+ ALOGD("ConstGraphicBlockBuffer::canCopy: too many blocks");
+ return false;
+ }
+ GraphicView2MediaImageConverter converter(
+ buffer->data().graphicBlocks()[0].map().get());
+ if (converter.initCheck() != OK) {
+ ALOGD("ConstGraphicBlockBuffer::canCopy: converter init failed: %d", converter.initCheck());
+ return false;
+ }
+ if (converter.backBufferSize() > capacity()) {
+ ALOGD("ConstGraphicBlockBuffer::canCopy: insufficient capacity: req %u has %zu",
+ converter.backBufferSize(), capacity());
+ return false;
+ }
+ return true;
+}
+
+bool ConstGraphicBlockBuffer::copy(const std::shared_ptr<C2Buffer> &buffer) {
+ if (!buffer || buffer->data().graphicBlocks().size() == 0) {
+ setRange(0, 0);
+ return true;
+ }
+ GraphicView2MediaImageConverter converter(
+ buffer->data().graphicBlocks()[0].map().get());
+ if (converter.initCheck() != OK) {
+ ALOGD("ConstGraphicBlockBuffer::copy: converter init failed: %d", converter.initCheck());
+ return false;
+ }
+ sp<ABuffer> aBuffer = new ABuffer(base(), capacity());
+ if (!converter.setBackBuffer(aBuffer)) {
+ ALOGD("ConstGraphicBlockBuffer::copy: set back buffer failed");
+ return false;
+ }
+ converter.copy();
+ meta()->setBuffer("image-data", converter.imageData());
+ mBufferRef = buffer;
+ return true;
+}
+
+// EncryptedLinearBlockBuffer
+
+EncryptedLinearBlockBuffer::EncryptedLinearBlockBuffer(
+ const sp<AMessage> &format,
+ const std::shared_ptr<C2LinearBlock> &block,
+ const sp<IMemory> &memory,
+ int32_t heapSeqNum)
+ : Codec2Buffer(format, new ABuffer(memory->pointer(), memory->size())),
+ mBlock(block),
+ mMemory(memory),
+ mHeapSeqNum(heapSeqNum) {
+}
+
+std::shared_ptr<C2Buffer> EncryptedLinearBlockBuffer::asC2Buffer() {
+ return C2Buffer::CreateLinearBuffer(mBlock->share(offset(), size(), C2Fence()));
+}
+
+void EncryptedLinearBlockBuffer::fillSourceBuffer(
+ ICrypto::SourceBuffer *source) {
+ source->mSharedMemory = mMemory;
+ source->mHeapSeqNum = mHeapSeqNum;
+}
+
+void EncryptedLinearBlockBuffer::fillSourceBuffer(
+ hardware::cas::native::V1_0::SharedBuffer *source) {
+ ssize_t offset;
+ size_t size;
+
+ mHidlMemory = hardware::fromHeap(mMemory->getMemory(&offset, &size));
+ source->heapBase = *mHidlMemory;
+ source->offset = offset;
+ source->size = size;
+}
+
+bool EncryptedLinearBlockBuffer::copyDecryptedContent(
+ const sp<IMemory> &decrypted, size_t length) {
+ C2WriteView view = mBlock->map().get();
+ if (view.error() != C2_OK) {
+ return false;
+ }
+ if (view.size() < length) {
+ return false;
+ }
+ memcpy(view.data(), decrypted->pointer(), length);
+ return true;
+}
+
+bool EncryptedLinearBlockBuffer::copyDecryptedContentFromMemory(size_t length) {
+ return copyDecryptedContent(mMemory, length);
+}
+
+native_handle_t *EncryptedLinearBlockBuffer::handle() const {
+ return const_cast<native_handle_t *>(mBlock->handle());
+}
+
+} // namespace android
diff --git a/media/libstagefright/Codec2InfoBuilder.cpp b/media/libstagefright/Codec2InfoBuilder.cpp
index 7ce2ff1..78c4e38 100644
--- a/media/libstagefright/Codec2InfoBuilder.cpp
+++ b/media/libstagefright/Codec2InfoBuilder.cpp
@@ -31,6 +31,63 @@
using ConstTraitsPtr = std::shared_ptr<const C2Component::Traits>;
+struct ProfileLevel {
+ uint32_t profile;
+ uint32_t level;
+};
+static const ProfileLevel kAvcProfileLevels[] = {
+ { 0x01, 0x0001 }, // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel1 },
+ { 0x01, 0x0002 }, // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel1b },
+ { 0x01, 0x0004 }, // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel11 },
+ { 0x01, 0x0008 }, // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel12 },
+ { 0x01, 0x0010 }, // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel13 },
+ { 0x01, 0x0020 }, // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel2 },
+ { 0x01, 0x0040 }, // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel21 },
+ { 0x01, 0x0080 }, // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel22 },
+ { 0x01, 0x0100 }, // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel3 },
+ { 0x01, 0x0200 }, // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel31 },
+ { 0x01, 0x0400 }, // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel32 },
+ { 0x01, 0x0800 }, // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel4 },
+ { 0x01, 0x1000 }, // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel41 },
+ { 0x01, 0x2000 }, // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel42 },
+ { 0x01, 0x4000 }, // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel5 },
+ { 0x01, 0x8000 }, // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel51 },
+
+ { 0x02, 0x0001 }, // { OMX_VIDEO_AVCProfileMain, OMX_VIDEO_AVCLevel1 },
+ { 0x02, 0x0002 }, // { OMX_VIDEO_AVCProfileMain, OMX_VIDEO_AVCLevel1b },
+ { 0x02, 0x0004 }, // { OMX_VIDEO_AVCProfileMain, OMX_VIDEO_AVCLevel11 },
+ { 0x02, 0x0008 }, // { OMX_VIDEO_AVCProfileMain, OMX_VIDEO_AVCLevel12 },
+ { 0x02, 0x0010 }, // { OMX_VIDEO_AVCProfileMain, OMX_VIDEO_AVCLevel13 },
+ { 0x02, 0x0020 }, // { OMX_VIDEO_AVCProfileMain, OMX_VIDEO_AVCLevel2 },
+ { 0x02, 0x0040 }, // { OMX_VIDEO_AVCProfileMain, OMX_VIDEO_AVCLevel21 },
+ { 0x02, 0x0080 }, // { OMX_VIDEO_AVCProfileMain, OMX_VIDEO_AVCLevel22 },
+ { 0x02, 0x0100 }, // { OMX_VIDEO_AVCProfileMain, OMX_VIDEO_AVCLevel3 },
+ { 0x02, 0x0200 }, // { OMX_VIDEO_AVCProfileMain, OMX_VIDEO_AVCLevel31 },
+ { 0x02, 0x0400 }, // { OMX_VIDEO_AVCProfileMain, OMX_VIDEO_AVCLevel32 },
+ { 0x02, 0x0800 }, // { OMX_VIDEO_AVCProfileMain, OMX_VIDEO_AVCLevel4 },
+ { 0x02, 0x1000 }, // { OMX_VIDEO_AVCProfileMain, OMX_VIDEO_AVCLevel41 },
+ { 0x02, 0x2000 }, // { OMX_VIDEO_AVCProfileMain, OMX_VIDEO_AVCLevel42 },
+ { 0x02, 0x4000 }, // { OMX_VIDEO_AVCProfileMain, OMX_VIDEO_AVCLevel5 },
+ { 0x02, 0x8000 }, // { OMX_VIDEO_AVCProfileMain, OMX_VIDEO_AVCLevel51 },
+
+ { 0x04, 0x0001 }, // { OMX_VIDEO_AVCProfileHigh, OMX_VIDEO_AVCLevel1 },
+ { 0x04, 0x0002 }, // { OMX_VIDEO_AVCProfileHigh, OMX_VIDEO_AVCLevel1b },
+ { 0x04, 0x0004 }, // { OMX_VIDEO_AVCProfileHigh, OMX_VIDEO_AVCLevel11 },
+ { 0x04, 0x0008 }, // { OMX_VIDEO_AVCProfileHigh, OMX_VIDEO_AVCLevel12 },
+ { 0x04, 0x0010 }, // { OMX_VIDEO_AVCProfileHigh, OMX_VIDEO_AVCLevel13 },
+ { 0x04, 0x0020 }, // { OMX_VIDEO_AVCProfileHigh, OMX_VIDEO_AVCLevel2 },
+ { 0x04, 0x0040 }, // { OMX_VIDEO_AVCProfileHigh, OMX_VIDEO_AVCLevel21 },
+ { 0x04, 0x0080 }, // { OMX_VIDEO_AVCProfileHigh, OMX_VIDEO_AVCLevel22 },
+ { 0x04, 0x0100 }, // { OMX_VIDEO_AVCProfileHigh, OMX_VIDEO_AVCLevel3 },
+ { 0x04, 0x0200 }, // { OMX_VIDEO_AVCProfileHigh, OMX_VIDEO_AVCLevel31 },
+ { 0x04, 0x0400 }, // { OMX_VIDEO_AVCProfileHigh, OMX_VIDEO_AVCLevel32 },
+ { 0x04, 0x0800 }, // { OMX_VIDEO_AVCProfileHigh, OMX_VIDEO_AVCLevel4 },
+ { 0x04, 0x1000 }, // { OMX_VIDEO_AVCProfileHigh, OMX_VIDEO_AVCLevel41 },
+ { 0x04, 0x2000 }, // { OMX_VIDEO_AVCProfileHigh, OMX_VIDEO_AVCLevel42 },
+ { 0x04, 0x4000 }, // { OMX_VIDEO_AVCProfileHigh, OMX_VIDEO_AVCLevel5 },
+ { 0x04, 0x8000 }, // { OMX_VIDEO_AVCProfileHigh, OMX_VIDEO_AVCLevel51 },
+};
+
status_t Codec2InfoBuilder::buildMediaCodecList(MediaCodecListWriter* writer) {
// Obtain C2ComponentStore
std::shared_ptr<C2ComponentStore> store = GetCodec2PlatformComponentStore();
@@ -86,6 +143,12 @@
caps->addDetail(key.c_str(), value.c_str());
}
}
+ // TODO: get this from intf(), and apply to other codecs as well.
+ if (mediaType.find("video/avc") != std::string::npos && !encoder) {
+ for (const auto& pl : kAvcProfileLevels) {
+ caps->addProfileLevel(pl.profile, pl.level);
+ }
+ }
// TODO: get this from intf().
if (mediaType.find("video") != std::string::npos && !encoder) {
caps->addColorFormat(0x7F420888); // COLOR_FormatYUV420Flexible
diff --git a/media/libstagefright/MPEG4Writer.cpp b/media/libstagefright/MPEG4Writer.cpp
index cfbbcb2..768df26 100644
--- a/media/libstagefright/MPEG4Writer.cpp
+++ b/media/libstagefright/MPEG4Writer.cpp
@@ -1376,15 +1376,12 @@
}
void MPEG4Writer::addMultipleLengthPrefixedSamples_l(MediaBuffer *buffer) {
- const size_t kExtensionNALSearchRange = 64; // bytes to look for non-VCL NALUs
-
const uint8_t *dataStart = (const uint8_t *)buffer->data() + buffer->range_offset();
const uint8_t *currentNalStart = dataStart;
const uint8_t *nextNalStart;
const uint8_t *data = dataStart;
size_t nextNalSize;
- size_t searchSize = buffer->range_length() > kExtensionNALSearchRange ?
- kExtensionNALSearchRange : buffer->range_length();
+ size_t searchSize = buffer->range_length();
while (getNextNALUnit(&data, &searchSize, &nextNalStart,
&nextNalSize, true) == OK) {
@@ -1962,6 +1959,17 @@
return;
}
+ // Rotation angle in HEIF is CCW, framework angle is CW.
+ int32_t heifRotation = 0;
+ switch(mRotation) {
+ case 90: heifRotation = 3; break;
+ case 180: heifRotation = 2; break;
+ case 270: heifRotation = 1; break;
+ default: break; // don't set if invalid
+ }
+
+ bool hasGrid = (mNumTiles > 1);
+
if (mProperties.empty()) {
mProperties.push_back(mOwner->addProperty_l({
.type = FOURCC('h', 'v', 'c', 'C'),
@@ -1970,22 +1978,29 @@
mProperties.push_back(mOwner->addProperty_l({
.type = FOURCC('i', 's', 'p', 'e'),
- .width = (mNumTiles > 1) ? mGridWidth : mWidth,
- .height = (mNumTiles > 1) ? mGridHeight : mHeight,
+ .width = hasGrid ? mGridWidth : mWidth,
+ .height = hasGrid ? mGridHeight : mHeight,
}));
+
+ if (!hasGrid && heifRotation > 0) {
+ mProperties.push_back(mOwner->addProperty_l({
+ .type = FOURCC('i', 'r', 'o', 't'),
+ .rotation = heifRotation,
+ }));
+ }
}
uint16_t itemId = mOwner->addItem_l({
.itemType = "hvc1",
- .isPrimary = (mNumTiles > 1) ? false : (mIsPrimary != 0),
- .isHidden = (mNumTiles > 1),
+ .isPrimary = hasGrid ? false : (mIsPrimary != 0),
+ .isHidden = hasGrid,
.offset = (uint32_t)offset,
.size = (uint32_t)size,
.properties = mProperties,
});
mTileIndex++;
- if (mNumTiles > 1) {
+ if (hasGrid) {
mDimgRefs.push_back(itemId);
if (mTileIndex == mNumTiles) {
@@ -1995,6 +2010,12 @@
.width = mWidth,
.height = mHeight,
}));
+ if (heifRotation > 0) {
+ mProperties.push_back(mOwner->addProperty_l({
+ .type = FOURCC('i', 'r', 'o', 't'),
+ .rotation = heifRotation,
+ }));
+ }
mOwner->addItem_l({
.itemType = "grid",
.isPrimary = (mIsPrimary != 0),
@@ -2305,7 +2326,8 @@
mStartTimeRealUs = startTimeUs;
int32_t rotationDegrees;
- if (mIsVideo && params && params->findInt32(kKeyRotation, &rotationDegrees)) {
+ if ((mIsVideo || mIsHeic) && params &&
+ params->findInt32(kKeyRotation, &rotationDegrees)) {
mRotation = rotationDegrees;
}
@@ -3430,16 +3452,36 @@
int32_t MPEG4Writer::Track::getMetaSizeIncrease() const {
CHECK(mIsHeic);
- return 20 // 1. 'ispe' property
- + (8 + mCodecSpecificDataSize) // 2. 'hvcC' property
- + (20 // 3. extra 'ispe'
- + (8 + 2 + 2 + mNumTiles * 2) // 4. 'dimg' ref
- + 12) // 5. ImageGrid in 'idat' (worst case)
- * (mNumTiles > 1) // - (3~5: applicable only if grid)
- + (16 // 6. increase to 'iloc'
- + 21 // 7. increase to 'iinf'
- + (3 + 2 * 2)) // 8. increase to 'ipma' (worst case)
- * (mNumTiles + 1); // - (6~8: are per-item)
+
+ int32_t grid = (mNumTiles > 1);
+
+ // Note that the rotation angle is in the file meta, and we don't have
+ // it until start, so here the calculation has to assume rotation.
+
+ // increase to ipco
+ int32_t increase = 20 * (grid + 1) // 'ispe' property
+ + (8 + mCodecSpecificDataSize) // 'hvcC' property
+ + 9; // 'irot' property (worst case)
+
+ // increase to iref and idat
+ if (grid) {
+ increase += (8 + 2 + 2 + mNumTiles * 2) // 'dimg' in iref
+ + 12; // ImageGrid in 'idat' (worst case)
+ }
+
+ // increase to iloc, iinf and ipma
+ increase += (16 // increase to 'iloc'
+ + 21 // increase to 'iinf'
+ + (3 + 2 * 2)) // increase to 'ipma' (worst case, 2 props x 2 bytes)
+ * (mNumTiles + grid);
+
+ // adjust to ipma:
+ // if rotation is present and only one tile, it could ref 3 properties
+ if (!grid) {
+ increase += 2;
+ }
+
+ return increase;
}
status_t MPEG4Writer::Track::checkCodecSpecificData() const {
@@ -4267,24 +4309,38 @@
numProperties = 32767;
}
for (size_t propIndex = 0; propIndex < numProperties; propIndex++) {
- if (mProperties[propIndex].type == FOURCC('h', 'v', 'c', 'C')) {
- beginBox("hvcC");
- sp<ABuffer> hvcc = mProperties[propIndex].hvcc;
- // Patch avcc's lengthSize field to match the number
- // of bytes we use to indicate the size of a nal unit.
- uint8_t *ptr = (uint8_t *)hvcc->data();
- ptr[21] = (ptr[21] & 0xfc) | (useNalLengthFour() ? 3 : 1);
- write(hvcc->data(), hvcc->size());
- endBox();
- } else if (mProperties[propIndex].type == FOURCC('i', 's', 'p', 'e')) {
- beginBox("ispe");
- writeInt32(0); // Version = 0, Flags = 0
- writeInt32(mProperties[propIndex].width);
- writeInt32(mProperties[propIndex].height);
- endBox();
- } else {
- ALOGW("Skipping unrecognized property: type 0x%08x",
- mProperties[propIndex].type);
+ switch (mProperties[propIndex].type) {
+ case FOURCC('h', 'v', 'c', 'C'):
+ {
+ beginBox("hvcC");
+ sp<ABuffer> hvcc = mProperties[propIndex].hvcc;
+ // Patch avcc's lengthSize field to match the number
+ // of bytes we use to indicate the size of a nal unit.
+ uint8_t *ptr = (uint8_t *)hvcc->data();
+ ptr[21] = (ptr[21] & 0xfc) | (useNalLengthFour() ? 3 : 1);
+ write(hvcc->data(), hvcc->size());
+ endBox();
+ break;
+ }
+ case FOURCC('i', 's', 'p', 'e'):
+ {
+ beginBox("ispe");
+ writeInt32(0); // Version = 0, Flags = 0
+ writeInt32(mProperties[propIndex].width);
+ writeInt32(mProperties[propIndex].height);
+ endBox();
+ break;
+ }
+ case FOURCC('i', 'r', 'o', 't'):
+ {
+ beginBox("irot");
+ writeInt8(mProperties[propIndex].rotation);
+ endBox();
+ break;
+ }
+ default:
+ ALOGW("Skipping unrecognized property: type 0x%08x",
+ mProperties[propIndex].type);
}
}
endBox();
diff --git a/media/libstagefright/MediaCodec.cpp b/media/libstagefright/MediaCodec.cpp
index 7d5c63a..c66a18f 100644
--- a/media/libstagefright/MediaCodec.cpp
+++ b/media/libstagefright/MediaCodec.cpp
@@ -603,6 +603,8 @@
return NAME_NOT_FOUND;
}
+ mCodecInfo.clear();
+
bool secureCodec = false;
AString tmp = name;
if (tmp.endsWith(".secure")) {
@@ -614,17 +616,24 @@
mCodec = NULL; // remove the codec.
return NO_INIT; // if called from Java should raise IOException
}
- ssize_t codecIdx = mcl->findCodecByName(tmp.c_str());
- if (codecIdx >= 0) {
- const sp<MediaCodecInfo> info = mcl->getCodecInfo(codecIdx);
+ for (const AString &codecName : { name, tmp }) {
+ ssize_t codecIdx = mcl->findCodecByName(codecName.c_str());
+ if (codecIdx < 0) {
+ continue;
+ }
+ mCodecInfo = mcl->getCodecInfo(codecIdx);
Vector<AString> mimes;
- info->getSupportedMimes(&mimes);
+ mCodecInfo->getSupportedMimes(&mimes);
for (size_t i = 0; i < mimes.size(); i++) {
if (mimes[i].startsWith("video/")) {
mIsVideo = true;
break;
}
}
+ break;
+ }
+ if (mCodecInfo == nullptr) {
+ return NAME_NOT_FOUND;
}
if (mIsVideo) {
@@ -651,6 +660,9 @@
new BufferCallback(new AMessage(kWhatCodecNotify, this))));
sp<AMessage> msg = new AMessage(kWhatInit, this);
+ msg->setObject("codecInfo", mCodecInfo);
+ // name may be different from mCodecInfo->getCodecName() if we stripped
+ // ".secure"
msg->setString("name", name);
if (mAnalyticsItem != NULL) {
@@ -1972,11 +1984,14 @@
mReplyID = replyID;
setState(INITIALIZING);
+ sp<RefBase> codecInfo;
+ CHECK(msg->findObject("codecInfo", &codecInfo));
AString name;
CHECK(msg->findString("name", &name));
sp<AMessage> format = new AMessage;
- format->setString("componentName", name.c_str());
+ format->setObject("codecInfo", codecInfo);
+ format->setString("componentName", name);
mCodec->initiateAllocateComponent(format);
break;
diff --git a/media/libstagefright/MetaDataUtils.cpp b/media/libstagefright/MetaDataUtils.cpp
index af8f539..04f6ade 100644
--- a/media/libstagefright/MetaDataUtils.cpp
+++ b/media/libstagefright/MetaDataUtils.cpp
@@ -24,11 +24,12 @@
namespace android {
-bool MakeAVCCodecSpecificData(MetaDataBase &meta, const sp<ABuffer> &accessUnit) {
+bool MakeAVCCodecSpecificData(MetaDataBase &meta, const uint8_t *data, size_t size) {
int32_t width;
int32_t height;
int32_t sarWidth;
int32_t sarHeight;
+ sp<ABuffer> accessUnit = new ABuffer((void*)data, size);
sp<ABuffer> csd = MakeAVCCodecSpecificData(accessUnit, &width, &height, &sarWidth, &sarHeight);
if (csd == nullptr) {
return false;
diff --git a/media/libstagefright/NuMediaExtractor.cpp b/media/libstagefright/NuMediaExtractor.cpp
index f6fc813..8c0cd3e 100644
--- a/media/libstagefright/NuMediaExtractor.cpp
+++ b/media/libstagefright/NuMediaExtractor.cpp
@@ -205,6 +205,15 @@
return OK;
}
+void NuMediaExtractor::disconnect() {
+ if (mDataSource != NULL) {
+ // disconnect data source
+ if (mDataSource->flags() & DataSource::kIsCachingDataSource) {
+ static_cast<NuCachedSource2 *>(mDataSource.get())->disconnect();
+ }
+ }
+}
+
status_t NuMediaExtractor::updateDurationAndBitrate() {
if (mImpl->countTracks() > kMaxTrackCount) {
return ERROR_UNSUPPORTED;
diff --git a/media/libstagefright/bqhelper/GraphicBufferSource.cpp b/media/libstagefright/bqhelper/GraphicBufferSource.cpp
index 87d2555..68ae8ec 100644
--- a/media/libstagefright/bqhelper/GraphicBufferSource.cpp
+++ b/media/libstagefright/bqhelper/GraphicBufferSource.cpp
@@ -421,26 +421,31 @@
}
Status GraphicBufferSource::release(){
- Mutex::Autolock autoLock(mMutex);
- if (mLooper != NULL) {
- mLooper->unregisterHandler(mReflector->id());
- mReflector.clear();
+ sp<ALooper> looper;
+ {
+ Mutex::Autolock autoLock(mMutex);
+ looper = mLooper;
+ if (mLooper != NULL) {
+ mLooper->unregisterHandler(mReflector->id());
+ mReflector.clear();
- mLooper->stop();
- mLooper.clear();
+ mLooper.clear();
+ }
+
+ ALOGV("--> release; available=%zu+%d eos=%d eosSent=%d acquired=%d",
+ mAvailableBuffers.size(), mNumAvailableUnacquiredBuffers,
+ mEndOfStream, mEndOfStreamSent, mNumOutstandingAcquires);
+
+ // Codec is no longer executing. Releasing all buffers to bq.
+ mFreeCodecBuffers.clear();
+ mSubmittedCodecBuffers.clear();
+ mLatestBuffer.mBuffer.reset();
+ mComponent.clear();
+ mExecuting = false;
}
-
- ALOGV("--> release; available=%zu+%d eos=%d eosSent=%d acquired=%d",
- mAvailableBuffers.size(), mNumAvailableUnacquiredBuffers,
- mEndOfStream, mEndOfStreamSent, mNumOutstandingAcquires);
-
- // Codec is no longer executing. Releasing all buffers to bq.
- mFreeCodecBuffers.clear();
- mSubmittedCodecBuffers.clear();
- mLatestBuffer.mBuffer.reset();
- mComponent.clear();
- mExecuting = false;
-
+ if (looper != NULL) {
+ looper->stop();
+ }
return Status::ok();
}
diff --git a/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.cpp b/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.cpp
index 0f1fecc..87138af 100644
--- a/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.cpp
+++ b/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.cpp
@@ -48,6 +48,7 @@
}
C2SoftFlacDecoder::~C2SoftFlacDecoder() {
+ delete mFLACDecoder;
}
c2_status_t C2SoftFlacDecoder::onInit() {
@@ -77,6 +78,9 @@
}
status_t C2SoftFlacDecoder::initDecoder() {
+ if (mFLACDecoder) {
+ delete mFLACDecoder;
+ }
mFLACDecoder = FLACDecoder::Create();
if (!mFLACDecoder) {
ALOGE("initDecoder: failed to create FLACDecoder");
diff --git a/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.h b/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.h
index a5c01a9..43d913b 100644
--- a/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.h
+++ b/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.h
@@ -46,7 +46,7 @@
kMaxBlockSize = 4096
};
- sp<FLACDecoder> mFLACDecoder;
+ FLACDecoder *mFLACDecoder;
FLAC__StreamMetadata_StreamInfo mStreamInfo;
bool mSignalledError;
bool mSignalledOutputEos;
diff --git a/media/libstagefright/codecs/flac/dec/SoftFlacDecoder.cpp b/media/libstagefright/codecs/flac/dec/SoftFlacDecoder.cpp
index 4ab1ab2..d0b72b7 100644
--- a/media/libstagefright/codecs/flac/dec/SoftFlacDecoder.cpp
+++ b/media/libstagefright/codecs/flac/dec/SoftFlacDecoder.cpp
@@ -57,6 +57,7 @@
SoftFlacDecoder::~SoftFlacDecoder() {
ALOGV("dtor:");
+ delete mFLACDecoder;
}
void SoftFlacDecoder::initPorts() {
diff --git a/media/libstagefright/codecs/flac/dec/SoftFlacDecoder.h b/media/libstagefright/codecs/flac/dec/SoftFlacDecoder.h
index 4a21c34..0f17ed8 100644
--- a/media/libstagefright/codecs/flac/dec/SoftFlacDecoder.h
+++ b/media/libstagefright/codecs/flac/dec/SoftFlacDecoder.h
@@ -50,7 +50,7 @@
kNumOutputBuffers = 4,
};
- sp<FLACDecoder> mFLACDecoder;
+ FLACDecoder *mFLACDecoder;
FLAC__StreamMetadata_StreamInfo mStreamInfo;
bool mHasStreamInfo;
size_t mInputBufferCount;
diff --git a/media/libstagefright/flac/dec/FLACDecoder.cpp b/media/libstagefright/flac/dec/FLACDecoder.cpp
index 8c7137c..e0e9211 100644
--- a/media/libstagefright/flac/dec/FLACDecoder.cpp
+++ b/media/libstagefright/flac/dec/FLACDecoder.cpp
@@ -220,9 +220,10 @@
}
// static
-sp<FLACDecoder> FLACDecoder::Create() {
- sp<FLACDecoder> decoder = new FLACDecoder();
- if (decoder->init() != OK) {
+FLACDecoder *FLACDecoder::Create() {
+ FLACDecoder *decoder = new (std::nothrow) FLACDecoder();
+ if (decoder == NULL || decoder->init() != OK) {
+ delete decoder;
return NULL;
}
return decoder;
diff --git a/media/libstagefright/flac/dec/FLACDecoder.h b/media/libstagefright/flac/dec/FLACDecoder.h
index 36282a8..1a33cae 100644
--- a/media/libstagefright/flac/dec/FLACDecoder.h
+++ b/media/libstagefright/flac/dec/FLACDecoder.h
@@ -26,14 +26,14 @@
namespace android {
// packet based FLAC decoder, wrapps libFLAC stream decoder.
-class FLACDecoder : public RefBase {
+class FLACDecoder {
public:
enum {
kMaxChannels = 8,
};
- static sp<FLACDecoder> Create();
+ static FLACDecoder *Create();
FLAC__StreamMetadata_StreamInfo getStreamInfo() const {
return mStreamInfo;
@@ -43,10 +43,10 @@
status_t decodeOneFrame(const uint8_t *inBuffer, size_t inBufferLen,
short *outBuffer, size_t *outBufferLen);
void flush();
+ virtual ~FLACDecoder();
protected:
FLACDecoder();
- virtual ~FLACDecoder() override;
private:
// stream properties
diff --git a/media/libstagefright/include/CCodecBufferChannel.h b/media/libstagefright/include/CCodecBufferChannel.h
index 51eee10..c8618db 100644
--- a/media/libstagefright/include/CCodecBufferChannel.h
+++ b/media/libstagefright/include/CCodecBufferChannel.h
@@ -162,6 +162,7 @@
};
void feedInputBufferIfAvailable();
+ status_t queueInputBufferInternal(const sp<MediaCodecBuffer> &buffer);
QueueSync mSync;
sp<MemoryDealer> mDealer;
@@ -180,7 +181,13 @@
std::atomic_uint64_t mFirstValidFrameIndex;
sp<MemoryDealer> makeMemoryDealer(size_t heapSize);
- Mutexed<sp<Surface>> mSurface;
+
+ struct OutputSurface {
+ sp<Surface> surface;
+ std::list<std::shared_ptr<C2Buffer>> bufferRefs;
+ size_t maxBufferCount;
+ };
+ Mutexed<OutputSurface> mOutputSurface;
std::shared_ptr<InputSurfaceWrapper> mInputSurface;
diff --git a/media/libstagefright/include/Codec2Buffer.h b/media/libstagefright/include/Codec2Buffer.h
index eeb889d..b2e3b1b 100644
--- a/media/libstagefright/include/Codec2Buffer.h
+++ b/media/libstagefright/include/Codec2Buffer.h
@@ -20,8 +20,11 @@
#include <C2Buffer.h>
+#include <android/hardware/cas/native/1.0/types.h>
+#include <binder/IMemory.h>
#include <media/hardware/VideoAPI.h>
#include <media/MediaCodecBuffer.h>
+#include <media/ICrypto.h>
namespace android {
@@ -271,6 +274,79 @@
const bool mWrapped;
};
+/**
+ * MediaCodecBuffer implementation wraps around C2LinearBlock for component
+ * and IMemory for client. Underlying C2LinearBlock won't be mapped for secure
+ * usecases..
+ */
+class EncryptedLinearBlockBuffer : public Codec2Buffer {
+public:
+ /**
+ * Construct a new EncryptedLinearBufferBlock wrapping around C2LinearBlock
+ * object and writable IMemory region.
+ *
+ * \param format mandatory buffer format for MediaCodecBuffer
+ * \param block C2LinearBlock object to wrap around.
+ * \param memory IMemory object to store encrypted content.
+ * \param heapSeqNum Heap sequence number from ICrypto; -1 if N/A
+ */
+ EncryptedLinearBlockBuffer(
+ const sp<AMessage> &format,
+ const std::shared_ptr<C2LinearBlock> &block,
+ const sp<IMemory> &memory,
+ int32_t heapSeqNum = -1);
+ EncryptedLinearBlockBuffer() = delete;
+
+ virtual ~EncryptedLinearBlockBuffer() = default;
+
+ std::shared_ptr<C2Buffer> asC2Buffer() override;
+
+ /**
+ * Fill the source buffer structure with appropriate value based on
+ * internal IMemory object.
+ *
+ * \param source source buffer structure to fill.
+ */
+ void fillSourceBuffer(ICrypto::SourceBuffer *source);
+ void fillSourceBuffer(
+ hardware::cas::native::V1_0::SharedBuffer *source);
+
+ /**
+ * Copy the content of |decrypted| into C2LinearBlock inside. This shall
+ * only be called in non-secure usecases.
+ *
+ * \param decrypted decrypted content to copy from.
+ * \param length length of the content
+ * \return true if successful
+ * false otherwise.
+ */
+ bool copyDecryptedContent(const sp<IMemory> &decrypted, size_t length);
+
+ /**
+ * Copy the content of internal IMemory object into C2LinearBlock inside.
+ * This shall only be called in non-secure usecases.
+ *
+ * \param length length of the content
+ * \return true if successful
+ * false otherwise.
+ */
+ bool copyDecryptedContentFromMemory(size_t length);
+
+ /**
+ * Return native handle of secure buffer understood by ICrypto.
+ *
+ * \return secure buffer handle
+ */
+ native_handle_t *handle() const;
+
+private:
+
+ std::shared_ptr<C2LinearBlock> mBlock;
+ sp<IMemory> mMemory;
+ sp<hardware::HidlMemory> mHidlMemory;
+ int32_t mHeapSeqNum;
+};
+
} // namespace android
#endif // CODEC2_BUFFER_H_
diff --git a/media/libstagefright/include/media/stagefright/CCodec.h b/media/libstagefright/include/media/stagefright/CCodec.h
index 078b03e..86fbd3a 100644
--- a/media/libstagefright/include/media/stagefright/CCodec.h
+++ b/media/libstagefright/include/media/stagefright/CCodec.h
@@ -36,6 +36,7 @@
class CCodecBufferChannel;
class InputSurfaceWrapper;
+struct MediaCodecInfo;
class CCodec : public CodecBase {
public:
@@ -74,7 +75,7 @@
void initiateStop();
void initiateRelease(bool sendCallback = true);
- void allocate(const AString &componentName);
+ void allocate(const sp<MediaCodecInfo> &codecInfo);
void configure(const sp<AMessage> &msg);
void start();
void stop();
diff --git a/media/libstagefright/include/media/stagefright/MPEG4Writer.h b/media/libstagefright/include/media/stagefright/MPEG4Writer.h
index 2a062cc..7b41362 100644
--- a/media/libstagefright/include/media/stagefright/MPEG4Writer.h
+++ b/media/libstagefright/include/media/stagefright/MPEG4Writer.h
@@ -195,6 +195,7 @@
uint32_t type;
int32_t width;
int32_t height;
+ int32_t rotation;
sp<ABuffer> hvcc;
} ItemProperty;
diff --git a/media/libstagefright/include/media/stagefright/MediaCodec.h b/media/libstagefright/include/media/stagefright/MediaCodec.h
index e7faea5..5a7d26a 100644
--- a/media/libstagefright/include/media/stagefright/MediaCodec.h
+++ b/media/libstagefright/include/media/stagefright/MediaCodec.h
@@ -308,6 +308,7 @@
sp<ALooper> mCodecLooper;
sp<CodecBase> mCodec;
AString mComponentName;
+ sp<MediaCodecInfo> mCodecInfo;
sp<AReplyToken> mReplyID;
uint32_t mFlags;
status_t mStickyError;
diff --git a/media/libstagefright/include/media/stagefright/MetaDataUtils.h b/media/libstagefright/include/media/stagefright/MetaDataUtils.h
index 3af2218..d5a8080 100644
--- a/media/libstagefright/include/media/stagefright/MetaDataUtils.h
+++ b/media/libstagefright/include/media/stagefright/MetaDataUtils.h
@@ -23,7 +23,7 @@
namespace android {
struct ABuffer;
-bool MakeAVCCodecSpecificData(MetaDataBase &meta, const sp<ABuffer> &accessUnit);
+bool MakeAVCCodecSpecificData(MetaDataBase &meta, const uint8_t *data, size_t size);
bool MakeAACCodecSpecificData(MetaDataBase &meta, unsigned profile, unsigned sampling_freq_index,
unsigned channel_configuration);
diff --git a/media/libstagefright/include/media/stagefright/NuMediaExtractor.h b/media/libstagefright/include/media/stagefright/NuMediaExtractor.h
index 5e5ef6e..675c932 100644
--- a/media/libstagefright/include/media/stagefright/NuMediaExtractor.h
+++ b/media/libstagefright/include/media/stagefright/NuMediaExtractor.h
@@ -64,6 +64,8 @@
status_t setMediaCas(const HInterfaceToken &casToken);
+ void disconnect();
+
size_t countTracks() const;
status_t getTrackFormat(size_t index, sp<AMessage> *format, uint32_t flags = 0) const;
diff --git a/media/libstagefright/mpeg2ts/ESQueue.cpp b/media/libstagefright/mpeg2ts/ESQueue.cpp
index 090d4e1..0fa9fcb 100644
--- a/media/libstagefright/mpeg2ts/ESQueue.cpp
+++ b/media/libstagefright/mpeg2ts/ESQueue.cpp
@@ -634,7 +634,7 @@
if (mFormat == NULL) {
mFormat = new MetaData;
- if (!MakeAVCCodecSpecificData(*mFormat, accessUnit)) {
+ if (!MakeAVCCodecSpecificData(*mFormat, accessUnit->data(), accessUnit->size())) {
mFormat.clear();
}
}
@@ -1010,7 +1010,7 @@
}
if (mFormat == NULL) {
mFormat = new MetaData;
- if (!MakeAVCCodecSpecificData(*mFormat, mBuffer)) {
+ if (!MakeAVCCodecSpecificData(*mFormat, mBuffer->data(), mBuffer->size())) {
ALOGW("Creating dummy AVC format for scrambled content");
mFormat = new MetaData;
mFormat->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_AVC);
@@ -1172,7 +1172,9 @@
if (mFormat == NULL) {
mFormat = new MetaData;
- if (!MakeAVCCodecSpecificData(*mFormat, accessUnit)) {
+ if (!MakeAVCCodecSpecificData(*mFormat,
+ accessUnit->data(),
+ accessUnit->size())) {
mFormat.clear();
}
}
diff --git a/media/ndk/NdkMediaExtractor.cpp b/media/ndk/NdkMediaExtractor.cpp
index ac837a3..b5e60a4 100644
--- a/media/ndk/NdkMediaExtractor.cpp
+++ b/media/ndk/NdkMediaExtractor.cpp
@@ -475,5 +475,11 @@
return AMEDIA_OK;
}
+EXPORT
+media_status_t AMediaExtractor_disconnect(AMediaExtractor * ex) {
+ ex->mImpl->disconnect();
+ return AMEDIA_OK;
+}
+
} // extern "C"
diff --git a/media/ndk/include/media/NdkMediaExtractor.h b/media/ndk/include/media/NdkMediaExtractor.h
index f7b9cfd..1d295e4 100644
--- a/media/ndk/include/media/NdkMediaExtractor.h
+++ b/media/ndk/include/media/NdkMediaExtractor.h
@@ -216,6 +216,12 @@
#endif /* __ANDROID_API__ >= 28 */
+#if __ANDROID_API__ >= 29
+
+media_status_t AMediaExtractor_disconnect(AMediaExtractor *ex);
+
+#endif /* __ANDROID_API__ >= 29 */
+
#endif /* __ANDROID_API__ >= 21 */
__END_DECLS
diff --git a/packages/MediaComponents/Android.mk b/packages/MediaComponents/Android.mk
index 19e4b24..b0d8e7d 100644
--- a/packages/MediaComponents/Android.mk
+++ b/packages/MediaComponents/Android.mk
@@ -19,7 +19,7 @@
ifneq ($(TARGET_BUILD_PDK),true)
# Build MediaComponents only if this is not a PDK build. MediaComponents won't
# build in PDK builds because frameworks/base/core/java is not available but
-# IMediaSession2.aidl and IMediaSession2Callback.aidl are using classes from
+# IMediaSession2.aidl and IMediaController2.aidl are using classes from
# frameworks/base/core/java.
include $(CLEAR_VARS)
diff --git a/packages/MediaComponents/res/drawable/custom_progress.xml b/packages/MediaComponents/res/drawable/custom_progress.xml
new file mode 100644
index 0000000..9731a6e
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/custom_progress.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright 2018 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.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
+ <item android:id="@android:id/background">
+ <shape android:shape="rectangle" >
+ <solid android:color="#26000000" />
+ </shape>
+ </item>
+ <item android:id="@android:id/secondaryProgress">
+ <clip>
+ <shape android:shape="rectangle" >
+ <solid android:color="#5Cffffff" />
+ </shape>
+ </clip>
+ </item>
+ <item android:id="@android:id/progress">
+ <clip>
+ <shape android:shape="rectangle" >
+ <solid android:color="#ffffff" />
+ </shape>
+ </clip>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/MediaComponents/res/drawable/custom_progress_thumb.xml b/packages/MediaComponents/res/drawable/custom_progress_thumb.xml
new file mode 100644
index 0000000..2e247f2
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/custom_progress_thumb.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright 2018 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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="oval" >
+ <solid android:color="#ffffff" />
+ <size
+ android:height="12dp"
+ android:width="12dp" />
+</shape>
\ No newline at end of file
diff --git a/packages/MediaComponents/res/drawable/ic_high_quality.xml b/packages/MediaComponents/res/drawable/ic_high_quality.xml
index e27d3e2..f76e22f 100644
--- a/packages/MediaComponents/res/drawable/ic_high_quality.xml
+++ b/packages/MediaComponents/res/drawable/ic_high_quality.xml
@@ -1,9 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
+ android:width="34dp"
+ android:height="34dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+
+ <path
+ android:pathData="M0 0h24v24H0z" />
<path
android:fillColor="#FFFFFF"
- android:pathData="M19,4L5,4c-1.11,0 -2,0.9 -2,2v12c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,6c0,-1.1 -0.9,-2 -2,-2zM11,15L9.5,15v-2h-2v2L6,15L6,9h1.5v2.5h2L9.5,9L11,9v6zM18,14c0,0.55 -0.45,1 -1,1h-0.75v1.5h-1.5L14.75,15L14,15c-0.55,0 -1,-0.45 -1,-1v-4c0,-0.55 0.45,-1 1,-1h3c0.55,0 1,0.45 1,1v4zM14.5,13.5h2v-3h-2v3z"/>
+ android:pathData="M19 4H5c-1.11 0-2 0.9-2 2v12c0 1.1 0.89 2 2 2h14c1.1 0 2-0.9 2-2V6c0-1.1-0.9-2-2-2zm-8 11H9.5v-2h-2v2H6V9h1.5v2.5h2V9H11v6zm7-1c0 0.55-0.45 1-1 1h-0.75v1.5h-1.5V15H14c-0.55 0-1-0.45-1-1v-4c0-0.55 0.45 -1 1-1h3c0.55 0 1 0.45 1 1v4zm-3.5-0.5h2v-3h-2v3z" />
</vector>
\ No newline at end of file
diff --git a/packages/MediaComponents/res/drawable/ic_replay_circle_filled.xml b/packages/MediaComponents/res/drawable/ic_replay_circle_filled.xml
index 389396b..a56d5d9 100644
--- a/packages/MediaComponents/res/drawable/ic_replay_circle_filled.xml
+++ b/packages/MediaComponents/res/drawable/ic_replay_circle_filled.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
+ android:width="40dp"
+ android:height="40dp"
android:viewportWidth="24"
android:viewportHeight="24">
diff --git a/packages/MediaComponents/res/drawable/ic_closed_caption_off.xml b/packages/MediaComponents/res/drawable/ic_subtitle_off.xml
similarity index 75%
rename from packages/MediaComponents/res/drawable/ic_closed_caption_off.xml
rename to packages/MediaComponents/res/drawable/ic_subtitle_off.xml
index a79cd11..c0a727a 100644
--- a/packages/MediaComponents/res/drawable/ic_closed_caption_off.xml
+++ b/packages/MediaComponents/res/drawable/ic_subtitle_off.xml
@@ -9,8 +9,7 @@
android:pathData="M0,0h24v24H0V0z" />
<path
android:fillColor="#FFFFFF"
- android:pathData="M19,5.5c0.27,0,0.5,0.23,0.5,0.5v12c0,0.27-0.23,0.5-0.5,0.5H5c-0.28,0-0.5-0.22-0.5-0.5V6c0-0.28,0.22-0.5,0.5-0.5H19
-M19,4H5C3.89,4,3,4.9,3,6v12c0,1.1,0.89,2,2,2h14c1.1,0,2-0.9,2-2V6C21,4.9,20.1,4,19,4L19,4z" />
+ android:pathData="M19.5,5.5v13h-15v-13H19.5z M19,4H5C3.89,4,3,4.9,3,6v12c0,1.1,0.89,2,2,2h14c1.1,0,2-0.9,2-2V6C21,4.9,20.1,4,19,4L19,4z" />
<path
android:fillColor="#FFFFFF"
android:pathData="M11,11H9.5v-0.5h-2v3h2V13H11v1c0,0.55-0.45,1-1,1H7c-0.55,0-1-0.45-1-1v-4c0-0.55,0.45-1,1-1h3c0.55,0,1,0.45,1,1V11z" />
diff --git a/packages/MediaComponents/res/drawable/ic_subtitle_on.xml b/packages/MediaComponents/res/drawable/ic_subtitle_on.xml
new file mode 100644
index 0000000..7c91c06
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_subtitle_on.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+
+ <path
+ android:pathData="M0 0h24v24H0z" />
+ <path
+ android:fillColor="#FFFFFF"
+ android:pathData="M19 4H5c-1.11 0-2 0.9-2 2v12c0 1.1 0.89 2 2 2h14c1.1 0 2-0.9 2-2V6c0-1.1-0.9-2-2-2zm-8 7H9.5v-0.5h-2v3h2V13H11v1c0 0.55-0.45 1-1 1H7c-0.55 0-1-0.45-1-1v-4c0-0.55 0.45 -1 1-1h3c0.55 0 1 0.45 1 1v1zm7 0h-1.5v-0.5h-2v3h2V13H18v1c0 0.55-0.45 1-1 1h-3c-0.55 0-1-0.45-1-1v-4c0-0.55 0.45 -1 1-1h3c0.55 0 1 0.45 1 1v1z" />
+</vector>
\ No newline at end of file
diff --git a/packages/MediaComponents/res/layout/media_controller.xml b/packages/MediaComponents/res/layout/media_controller.xml
index 8488c84..eab2309 100644
--- a/packages/MediaComponents/res/layout/media_controller.xml
+++ b/packages/MediaComponents/res/layout/media_controller.xml
@@ -129,10 +129,10 @@
<SeekBar
android:id="@+id/mediacontroller_progress"
android:layout_width="match_parent"
- android:layout_height="32dp"
- android:padding="0dp"
- android:progressTint="#FFFFFFFF"
- android:thumbTint="#FFFFFFFF"/>
+ android:layout_height="12dp"
+ android:maxHeight="2dp"
+ android:minHeight="2dp"
+ android:padding="0dp"/>
<RelativeLayout
android:layout_width="match_parent"
@@ -199,6 +199,7 @@
<ImageButton
android:id="@+id/subtitle"
android:scaleType="fitCenter"
+ android:visibility="gone"
style="@style/BottomBarButton.CC" />
<ImageButton
android:id="@+id/fullscreen"
@@ -224,9 +225,6 @@
android:layout_height="wrap_content" />
<ImageButton
- android:id="@+id/aspect_ratio"
- style="@style/BottomBarButton.AspectRatio" />
- <ImageButton
android:id="@+id/video_quality"
style="@style/BottomBarButton.VideoQuality" />
<ImageButton
diff --git a/packages/MediaComponents/res/layout/settings_list_item.xml b/packages/MediaComponents/res/layout/settings_list_item.xml
index e7522b7..81b3275 100644
--- a/packages/MediaComponents/res/layout/settings_list_item.xml
+++ b/packages/MediaComponents/res/layout/settings_list_item.xml
@@ -28,14 +28,6 @@
android:orientation="horizontal">
<ImageView
- android:id="@+id/check"
- android:layout_width="@dimen/MediaControlView2_settings_icon_size"
- android:layout_height="@dimen/MediaControlView2_settings_icon_size"
- android:gravity="center"
- android:paddingLeft="2dp"
- android:src="@drawable/ic_check"/>
-
- <ImageView
android:id="@+id/icon"
android:layout_width="@dimen/MediaControlView2_settings_icon_size"
android:layout_height="@dimen/MediaControlView2_settings_icon_size"
diff --git a/packages/MediaComponents/res/layout/sub_settings_list_item.xml b/packages/MediaComponents/res/layout/sub_settings_list_item.xml
new file mode 100644
index 0000000..9de7f2b
--- /dev/null
+++ b/packages/MediaComponents/res/layout/sub_settings_list_item.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2018 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="@dimen/MediaControlView2_settings_width"
+ android:layout_height="@dimen/MediaControlView2_settings_height"
+ android:orientation="horizontal"
+ android:background="@color/black_transparent_70">
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/MediaControlView2_settings_height"
+ android:paddingRight="2dp"
+ android:gravity="center"
+ android:orientation="horizontal">
+
+ <ImageView
+ android:id="@+id/check"
+ android:layout_width="@dimen/MediaControlView2_settings_icon_size"
+ android:layout_height="@dimen/MediaControlView2_settings_icon_size"
+ android:gravity="center"
+ android:paddingLeft="2dp"
+ android:src="@drawable/ic_check"/>
+ </LinearLayout>
+
+ <RelativeLayout
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/MediaControlView2_settings_height"
+ android:gravity="center"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/text"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/MediaControlView2_text_width"
+ android:paddingLeft="2dp"
+ android:textColor="@color/white"
+ android:textSize="@dimen/MediaControlView2_settings_main_text_size"/>
+ </RelativeLayout>
+
+</LinearLayout>
diff --git a/packages/MediaComponents/res/values/arrays.xml b/packages/MediaComponents/res/values/arrays.xml
new file mode 100644
index 0000000..1187320
--- /dev/null
+++ b/packages/MediaComponents/res/values/arrays.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2018 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.
+-->
+
+<resources>
+ <integer-array name="speed_multiplied_by_100">
+ <item>25</item>
+ <item>50</item>
+ <item>75</item>
+ <item>100</item>
+ <item>125</item>
+ <item>150</item>
+ <item>200</item>
+ </integer-array>
+</resources>
\ No newline at end of file
diff --git a/packages/MediaComponents/res/values/strings.xml b/packages/MediaComponents/res/values/strings.xml
index 305cef4..c80aaf3 100644
--- a/packages/MediaComponents/res/values/strings.xml
+++ b/packages/MediaComponents/res/values/strings.xml
@@ -111,15 +111,18 @@
<string name="MediaControlView2_audio_track_none_text">None</string>
<string name="MediaControlView2_video_quality_text">Video quality</string>
<string name="MediaControlView2_video_quality_auto_text">Auto</string>
- <string name="MediaControlView2_playback_speed_text">Playback speed</string>
- <string name="MediaControlView2_playback_speed_0_25x_text">0.25x</string>
- <string name="MediaControlView2_playback_speed_0_5x_text">0.5x</string>
- <string name="MediaControlView2_playback_speed_0_75x_text">0.75x</string>
- <string name="MediaControlView2_playback_speed_1x_text">Normal</string>
- <string name="MediaControlView2_playback_speed_1_25x_text">1.25x</string>
- <string name="MediaControlView2_playback_speed_1_5x_text">1.5x</string>
- <string name="MediaControlView2_playback_speed_2x_text">2x</string>
<string name="MediaControlView2_help_text">Help & feedback</string>
+ <string name="MediaControlView2_playback_speed_text">Playback speed</string>
+ <string-array name="MediaControlView2_playback_speeds">
+ <item>0.25x</item>
+ <item>0.5x</item>
+ <item>0.75x</item>
+ <item>Normal</item>
+ <item>1.25x</item>
+ <item>1.5x</item>
+ <item>2x</item>
+ </string-array>
+
<!-- Text for displaying subtitle track number. -->
<string name="MediaControlView2_subtitle_track_number_text">
Track <xliff:g id="track_number" example="1">%1$s</xliff:g>
diff --git a/packages/MediaComponents/res/values/style.xml b/packages/MediaComponents/res/values/style.xml
index 23c7bc9..e6ed039 100644
--- a/packages/MediaComponents/res/values/style.xml
+++ b/packages/MediaComponents/res/values/style.xml
@@ -4,6 +4,7 @@
<item name="android:background">@null</item>
<item name="android:layout_width">70dp</item>
<item name="android:layout_height">40dp</item>
+ <item name="android:visibility">gone</item>
</style>
<style name="TransportControlsButton.Previous">
@@ -55,7 +56,7 @@
</style>
<style name="BottomBarButton.CC">
- <item name="android:src">@drawable/ic_media_subtitle_disabled</item>
+ <item name="android:src">@drawable/ic_subtitle_off</item>
</style>
<style name="BottomBarButton.FullScreen">
@@ -74,12 +75,8 @@
<item name="android:src">@drawable/ic_settings</item>
</style>
- <style name="BottomBarButton.AspectRatio">
- <item name="android:src">@drawable/ic_aspect_ratio</item>
- </style>
-
<style name="BottomBarButton.Mute">
- <item name="android:src">@drawable/ic_mute</item>
+ <item name="android:src">@drawable/ic_unmute</item>
</style>
<style name="BottomBarButton.VideoQuality">
diff --git a/packages/MediaComponents/runcts.sh b/packages/MediaComponents/runcts.sh
index 0cf0e44..61b1a1e 100644
--- a/packages/MediaComponents/runcts.sh
+++ b/packages/MediaComponents/runcts.sh
@@ -23,6 +23,7 @@
echo ' -h|--help: This help'
echo ' --skip: Skip build and flash. Just rerun-tests'
echo ' --min: Only rebuild tests and updatable library.'
+ echo ' --test: Only rebuild tests'
echo ' -s [device_id]: Specify a device name to run test against.'
echo ' You can define ${ADBHOST} instead.'
echo ' -r [count]: Repeat tests for given count. It will stop when fails.'
@@ -58,6 +59,7 @@
while true; do
local OPTION_SKIP="false"
local OPTION_MIN="false"
+ local OPTION_TEST="false"
local OPTION_REPEAT_COUNT="1"
local OPTION_IGNORE="false"
local OPTION_TEST_TARGET="${DEFAULT_TEST_TARGET}"
@@ -74,6 +76,9 @@
--min)
OPTION_MIN="true"
;;
+ --test)
+ OPTION_TEST="true"
+ ;;
-s)
shift
adbhost_local=${1}
@@ -133,36 +138,43 @@
fi
# Build test apk and required apk.
- local build_targets="${BUILD_TARGETS[@]}"
- if [[ "${OPTION_MIN}" != "true" ]]; then
- build_targets="${build_targets} droid"
+ local build_targets
+ if [[ "${OPTION_TEST}" == "true" ]]; then
+ build_targets="${INSTALL_TARGETS[@]}"
+ elif [[ "${OPTION_MIN}" == "true" ]]; then
+ build_targets="${BUILD_TARGETS[@]}"
+ else
+ build_targets="${BUILD_TARGETS[@]} droid"
fi
m ${build_targets} -j || break
- local device_build_type="$(${adb} shell getprop ro.build.type)"
- if [[ "${device_build_type}" == "user" ]]; then
- # User build. Cannot adb sync
- ${adb} reboot bootloader
- fastboot flashall
- else
- ${adb} root
- local device_verity_mode="$(${adb} shell getprop ro.boot.veritymode)"
- if [[ "${device_verity_mode}" != "disabled" ]]; then
- ${adb} disable-verity
- ${adb} reboot
- ${adb} wait-for-device || break
+ if [[ "${OPTION_TEST}" != "true" ]]; then
+ # Flash only when needed
+ local device_build_type="$(${adb} shell getprop ro.build.type)"
+ if [[ "${device_build_type}" == "user" ]]; then
+ # User build. Cannot adb sync
+ ${adb} reboot bootloader
+ fastboot flashall
+ else
${adb} root
+ local device_verity_mode="$(${adb} shell getprop ro.boot.veritymode)"
+ if [[ "${device_verity_mode}" != "disabled" ]]; then
+ ${adb} disable-verity
+ ${adb} reboot
+ ${adb} wait-for-device || break
+ ${adb} root
+ fi
+ ${adb} remount
+ ${adb} shell stop
+ ${adb} shell setprop log.tag.MediaSessionService DEBUG
+ ${adb} sync
+ ${adb} shell start
fi
- ${adb} remount
- ${adb} shell stop
- ${adb} shell setprop log.tag.MediaSessionService DEBUG
- ${adb} sync
- ${adb} shell start
+ ${adb} wait-for-device || break
+ # Ensure package manager is loaded.
+ # TODO(jaewan): Find better way to wait
+ sleep 15
fi
- ${adb} wait-for-device || break
- # Ensure package manager is loaded.
- # TODO(jaewan): Find better way to wait
- sleep 15
# Install apks
local install_failed="false"
diff --git a/packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl b/packages/MediaComponents/src/com/android/media/IMediaController2.aidl
similarity index 71%
rename from packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl
rename to packages/MediaComponents/src/com/android/media/IMediaController2.aidl
index 3d812f8..d6c8e21 100644
--- a/packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl
+++ b/packages/MediaComponents/src/com/android/media/IMediaController2.aidl
@@ -23,21 +23,27 @@
import com.android.media.IMediaSession2;
/**
- * Interface from MediaSession2 to MediaSession2Record.
+ * Interface from MediaSession2 to MediaController2.
* <p>
* Keep this interface oneway. Otherwise a malicious app may implement fake version of this,
* and holds calls from session to make session owner(s) frozen.
*/
-oneway interface IMediaSession2Callback {
- void onPlaybackStateChanged(in Bundle state);
- void onPlaylistChanged(in List<Bundle> playlist);
+// TODO(jaewan): (Post P) Handle when the playlist becomes too huge.
+// Note that ParcelledSliceList isn't a good idea for the purpose. (see: b/37493677)
+oneway interface IMediaController2 {
+ void onPlayerStateChanged(int state);
+ void onPositionChanged(long eventTimeMs, long positionMs);
+ void onPlaybackSpeedChanged(float speed);
+ void onBufferedPositionChanged(long bufferedPositionMs);
+ void onPlaylistChanged(in List<Bundle> playlist, in Bundle metadata);
+ void onPlaylistMetadataChanged(in Bundle metadata);
void onPlaylistParamsChanged(in Bundle params);
void onPlaybackInfoChanged(in Bundle playbackInfo);
- // TODO(jaewan): Handle when the playlist becomes too huge.
- void onConnected(IMediaSession2 sessionBinder, in Bundle commandGroup, in Bundle playbackState,
- in Bundle playbackInfo, in Bundle params, in List<Bundle> playlist,
- in PendingIntent sessionActivity);
+ void onConnected(IMediaSession2 sessionBinder, in Bundle commandGroup,
+ int playerState, long positionEventTimeMs, long positionMs, float playbackSpeed,
+ long bufferedPositionMs, in Bundle playbackInfo, in Bundle params,
+ in List<Bundle> playlist, in PendingIntent sessionActivity);
void onDisconnected();
void onCustomLayoutChanged(in List<Bundle> commandButtonlist);
diff --git a/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl b/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl
index ef7120d..a241abc 100644
--- a/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl
+++ b/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl
@@ -20,55 +20,61 @@
import android.os.ResultReceiver;
import android.net.Uri;
-import com.android.media.IMediaSession2Callback;
+import com.android.media.IMediaController2;
/**
- * Interface to MediaSession2.
+ * Interface from MediaController2 to MediaSession2.
* <p>
* Keep this interface oneway. Otherwise a malicious app may implement fake version of this,
* and holds calls from session to make session owner(s) frozen.
*/
+ // TODO(jaewan): (Post P) Handle when the playlist becomes too huge.
+ // Note that ParcelledSliceList isn't a good idea for the purpose. (see: b/37493677)
oneway interface IMediaSession2 {
// TODO(jaewan): add onCommand() to send private command
- // TODO(jaewan): Due to the nature of oneway calls, APIs can be called in out of order
- // Add id for individual calls to address this.
- // TODO(jaewan): We may consider to add another binder just for the connection
+ // TODO(jaewan): (Post P) We may consider to add another binder just for the connection
// not to expose other methods to the controller whose connection wasn't accepted.
// But this would be enough for now because it's the same as existing
// MediaBrowser and MediaBrowserService.
- void connect(IMediaSession2Callback caller, String callingPackage);
- void release(IMediaSession2Callback caller);
+ void connect(IMediaController2 caller, String callingPackage);
+ void release(IMediaController2 caller);
- void setVolumeTo(IMediaSession2Callback caller, int value, int flags);
- void adjustVolume(IMediaSession2Callback caller, int direction, int flags);
+ void setVolumeTo(IMediaController2 caller, int value, int flags);
+ void adjustVolume(IMediaController2 caller, int direction, int flags);
//////////////////////////////////////////////////////////////////////////////////////////////
// send command
//////////////////////////////////////////////////////////////////////////////////////////////
- void sendTransportControlCommand(IMediaSession2Callback caller,
+ void sendTransportControlCommand(IMediaController2 caller,
int commandCode, in Bundle args);
- void sendCustomCommand(IMediaSession2Callback caller, in Bundle command, in Bundle args,
+ void sendCustomCommand(IMediaController2 caller, in Bundle command, in Bundle args,
in ResultReceiver receiver);
- void prepareFromUri(IMediaSession2Callback caller, in Uri uri, in Bundle extras);
- void prepareFromSearch(IMediaSession2Callback caller, String query, in Bundle extras);
- void prepareFromMediaId(IMediaSession2Callback caller, String mediaId, in Bundle extras);
- void playFromUri(IMediaSession2Callback caller, in Uri uri, in Bundle extras);
- void playFromSearch(IMediaSession2Callback caller, String query, in Bundle extras);
- void playFromMediaId(IMediaSession2Callback caller, String mediaId, in Bundle extras);
- void setRating(IMediaSession2Callback caller, String mediaId, in Bundle rating);
+ void prepareFromUri(IMediaController2 caller, in Uri uri, in Bundle extras);
+ void prepareFromSearch(IMediaController2 caller, String query, in Bundle extras);
+ void prepareFromMediaId(IMediaController2 caller, String mediaId, in Bundle extras);
+ void playFromUri(IMediaController2 caller, in Uri uri, in Bundle extras);
+ void playFromSearch(IMediaController2 caller, String query, in Bundle extras);
+ void playFromMediaId(IMediaController2 caller, String mediaId, in Bundle extras);
+ void setRating(IMediaController2 caller, String mediaId, in Bundle rating);
+
+ void setPlaylist(IMediaController2 caller, in List<Bundle> playlist, in Bundle metadata);
+ void updatePlaylistMetadata(IMediaController2 caller, in Bundle metadata);
+ void addPlaylistItem(IMediaController2 caller, int index, in Bundle mediaItem);
+ void removePlaylistItem(IMediaController2 caller, in Bundle mediaItem);
+ void replacePlaylistItem(IMediaController2 caller, int index, in Bundle mediaItem);
//////////////////////////////////////////////////////////////////////////////////////////////
// library service specific
//////////////////////////////////////////////////////////////////////////////////////////////
- void getLibraryRoot(IMediaSession2Callback caller, in Bundle rootHints);
- void getItem(IMediaSession2Callback caller, String mediaId);
- void getChildren(IMediaSession2Callback caller, String parentId, int page, int pageSize,
+ void getLibraryRoot(IMediaController2 caller, in Bundle rootHints);
+ void getItem(IMediaController2 caller, String mediaId);
+ void getChildren(IMediaController2 caller, String parentId, int page, int pageSize,
in Bundle extras);
- void search(IMediaSession2Callback caller, String query, in Bundle extras);
- void getSearchResult(IMediaSession2Callback caller, String query, int page, int pageSize,
+ void search(IMediaController2 caller, String query, in Bundle extras);
+ void getSearchResult(IMediaController2 caller, String query, int page, int pageSize,
in Bundle extras);
- void subscribe(IMediaSession2Callback caller, String parentId, in Bundle extras);
- void unsubscribe(IMediaSession2Callback caller, String parentId);
+ void subscribe(IMediaController2 caller, String parentId, in Bundle extras);
+ void unsubscribe(IMediaController2 caller, String parentId);
}
diff --git a/packages/MediaComponents/src/com/android/media/MediaController2Impl.java b/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
index 96fdda0..b6d3e55 100644
--- a/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
@@ -16,7 +16,18 @@
package com.android.media;
-import static android.media.MediaSession2.*;
+import static android.media.MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME;
+import static android.media.MediaSession2.COMMAND_CODE_PLAYLIST_ADD_ITEM;
+import static android.media.MediaSession2.COMMAND_CODE_PLAYLIST_REMOVE_ITEM;
+import static android.media.MediaSession2.COMMAND_CODE_PLAYLIST_REPLACE_ITEM;
+import static android.media.MediaSession2.COMMAND_CODE_PLAYLIST_SET_LIST;
+import static android.media.MediaSession2.COMMAND_CODE_PLAYLIST_SET_LIST_METADATA;
+import static android.media.MediaSession2.COMMAND_CODE_PLAY_FROM_MEDIA_ID;
+import static android.media.MediaSession2.COMMAND_CODE_PLAY_FROM_SEARCH;
+import static android.media.MediaSession2.COMMAND_CODE_PLAY_FROM_URI;
+import static android.media.MediaSession2.COMMAND_CODE_PREPARE_FROM_MEDIA_ID;
+import static android.media.MediaSession2.COMMAND_CODE_PREPARE_FROM_SEARCH;
+import static android.media.MediaSession2.COMMAND_CODE_PREPARE_FROM_URI;
import android.app.PendingIntent;
import android.content.ComponentName;
@@ -28,13 +39,13 @@
import android.media.MediaController2.ControllerCallback;
import android.media.MediaController2.PlaybackInfo;
import android.media.MediaItem2;
+import android.media.MediaMetadata2;
import android.media.MediaSession2;
import android.media.MediaSession2.Command;
import android.media.MediaSession2.CommandButton;
import android.media.MediaSession2.CommandGroup;
import android.media.MediaSession2.PlaylistParams;
import android.media.MediaSessionService2;
-import android.media.PlaybackState2;
import android.media.Rating2;
import android.media.SessionToken2;
import android.media.update.MediaController2Provider;
@@ -61,7 +72,7 @@
private final Context mContext;
private final Object mLock = new Object();
- private final MediaSession2CallbackStub mSessionCallbackStub;
+ private final MediaController2Stub mControllerStub;
private final SessionToken2 mToken;
private final ControllerCallback mCallback;
private final Executor mCallbackExecutor;
@@ -72,12 +83,22 @@
@GuardedBy("mLock")
private boolean mIsReleased;
@GuardedBy("mLock")
- private PlaybackState2 mPlaybackState;
- @GuardedBy("mLock")
private List<MediaItem2> mPlaylist;
@GuardedBy("mLock")
+ private MediaMetadata2 mPlaylistMetadata;
+ @GuardedBy("mLock")
private PlaylistParams mPlaylistParams;
@GuardedBy("mLock")
+ private int mPlayerState;
+ @GuardedBy("mLock")
+ private long mPositionEventTimeMs;
+ @GuardedBy("mLock")
+ private long mPositionMs;
+ @GuardedBy("mLock")
+ private float mPlaybackSpeed;
+ @GuardedBy("mLock")
+ private long mBufferedPositionMs;
+ @GuardedBy("mLock")
private PlaybackInfo mPlaybackInfo;
@GuardedBy("mLock")
private PendingIntent mSessionActivity;
@@ -110,7 +131,7 @@
throw new IllegalArgumentException("executor shouldn't be null");
}
mContext = context;
- mSessionCallbackStub = new MediaSession2CallbackStub(this);
+ mControllerStub = new MediaController2Stub(this);
mToken = token;
mCallback = callback;
mCallbackExecutor = executor;
@@ -191,7 +212,7 @@
private void connectToSession(IMediaSession2 sessionBinder) {
try {
- sessionBinder.connect(mSessionCallbackStub, mContext.getPackageName());
+ sessionBinder.connect(mControllerStub, mContext.getPackageName());
} catch (RemoteException e) {
Log.w(TAG, "Failed to call connection request. Framework will retry"
+ " automatically");
@@ -216,12 +237,12 @@
}
binder = mSessionBinder;
mSessionBinder = null;
- mSessionCallbackStub.destroy();
+ mControllerStub.destroy();
}
if (binder != null) {
try {
binder.asBinder().unlinkToDeath(mDeathRecipient, 0);
- binder.release(mSessionCallbackStub);
+ binder.release(mControllerStub);
} catch (RemoteException e) {
// No-op.
}
@@ -235,8 +256,8 @@
return mSessionBinder;
}
- MediaSession2CallbackStub getControllerStub() {
- return mSessionCallbackStub;
+ MediaController2Stub getControllerStub() {
+ return mControllerStub;
}
Executor getCallbackExecutor() {
@@ -331,7 +352,7 @@
final IMediaSession2 binder = mSessionBinder;
if (binder != null) {
try {
- binder.sendTransportControlCommand(mSessionCallbackStub, commandCode, args);
+ binder.sendTransportControlCommand(mControllerStub, commandCode, args);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to the service or the session is gone", e);
}
@@ -354,7 +375,7 @@
final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_PLAYBACK_SET_VOLUME);
if (binder != null) {
try {
- binder.setVolumeTo(mSessionCallbackStub, value, flags);
+ binder.setVolumeTo(mControllerStub, value, flags);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to the service or the session is gone", e);
}
@@ -369,7 +390,7 @@
final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_PLAYBACK_SET_VOLUME);
if (binder != null) {
try {
- binder.adjustVolume(mSessionCallbackStub, direction, flags);
+ binder.adjustVolume(mControllerStub, direction, flags);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to the service or the session is gone", e);
}
@@ -386,7 +407,7 @@
}
if (binder != null) {
try {
- binder.prepareFromUri(mSessionCallbackStub, uri, extras);
+ binder.prepareFromUri(mControllerStub, uri, extras);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to the service or the session is gone", e);
}
@@ -403,7 +424,7 @@
}
if (binder != null) {
try {
- binder.prepareFromSearch(mSessionCallbackStub, query, extras);
+ binder.prepareFromSearch(mControllerStub, query, extras);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to the service or the session is gone", e);
}
@@ -420,7 +441,7 @@
}
if (binder != null) {
try {
- binder.prepareFromMediaId(mSessionCallbackStub, mediaId, extras);
+ binder.prepareFromMediaId(mControllerStub, mediaId, extras);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to the service or the session is gone", e);
}
@@ -437,7 +458,7 @@
}
if (binder != null) {
try {
- binder.playFromUri(mSessionCallbackStub, uri, extras);
+ binder.playFromUri(mControllerStub, uri, extras);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to the service or the session is gone", e);
}
@@ -454,7 +475,7 @@
}
if (binder != null) {
try {
- binder.playFromSearch(mSessionCallbackStub, query, extras);
+ binder.playFromSearch(mControllerStub, query, extras);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to the service or the session is gone", e);
}
@@ -471,7 +492,7 @@
}
if (binder != null) {
try {
- binder.playFromMediaId(mSessionCallbackStub, mediaId, extras);
+ binder.playFromMediaId(mControllerStub, mediaId, extras);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to the service or the session is gone", e);
}
@@ -492,7 +513,7 @@
final IMediaSession2 binder = mSessionBinder;
if (binder != null) {
try {
- binder.setRating(mSessionCallbackStub, mediaId, rating.toBundle());
+ binder.setRating(mControllerStub, mediaId, rating.toBundle());
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to the service or the session is gone", e);
}
@@ -509,7 +530,7 @@
final IMediaSession2 binder = getSessionBinderIfAble(command);
if (binder != null) {
try {
- binder.sendCustomCommand(mSessionCallbackStub, command.toBundle(), args, cb);
+ binder.sendCustomCommand(mControllerStub, command.toBundle(), args, cb);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to the service or the session is gone", e);
}
@@ -526,6 +547,51 @@
}
@Override
+ public void setPlaylist_impl(List<MediaItem2> list, MediaMetadata2 metadata) {
+ if (list == null) {
+ throw new IllegalArgumentException("list shouldn't be null");
+ }
+ final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_PLAYLIST_SET_LIST);
+ if (binder != null) {
+ List<Bundle> bundleList = new ArrayList<>();
+ for (int i = 0; i < list.size(); i++) {
+ bundleList.add(list.get(i).toBundle());
+ }
+ Bundle metadataBundle = (metadata == null) ? null : metadata.toBundle();
+ try {
+ binder.setPlaylist(mControllerStub, bundleList, metadataBundle);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Cannot connect to the service or the session is gone", e);
+ }
+ } else {
+ Log.w(TAG, "Session isn't active", new IllegalStateException());
+ }
+ }
+
+ @Override
+ public MediaMetadata2 getPlaylistMetadata_impl() {
+ synchronized (mLock) {
+ return mPlaylistMetadata;
+ }
+ }
+
+ @Override
+ public void updatePlaylistMetadata_impl(MediaMetadata2 metadata) {
+ final IMediaSession2 binder = getSessionBinderIfAble(
+ COMMAND_CODE_PLAYLIST_SET_LIST_METADATA);
+ if (binder != null) {
+ Bundle metadataBundle = (metadata == null) ? null : metadata.toBundle();
+ try {
+ binder.updatePlaylistMetadata(mControllerStub, metadataBundle);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Cannot connect to the service or the session is gone", e);
+ }
+ } else {
+ Log.w(TAG, "Session isn't active", new IllegalStateException());
+ }
+ }
+
+ @Override
public void prepare_impl() {
sendTransportControlCommand(MediaSession2.COMMAND_CODE_PLAYBACK_PREPARE);
}
@@ -566,40 +632,60 @@
}
@Override
- public PlaybackState2 getPlaybackState_impl() {
- synchronized (mLock) {
- return mPlaybackState;
- }
- }
-
- @Override
public void addPlaylistItem_impl(int index, MediaItem2 item) {
- // TODO(jaewan): Implement (b/73149584)
if (index < 0) {
throw new IllegalArgumentException("index shouldn't be negative");
}
if (item == null) {
throw new IllegalArgumentException("item shouldn't be null");
}
+ final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_PLAYLIST_ADD_ITEM);
+ if (binder != null) {
+ try {
+ binder.addPlaylistItem(mControllerStub, index, item.toBundle());
+ } catch (RemoteException e) {
+ Log.w(TAG, "Cannot connect to the service or the session is gone", e);
+ }
+ } else {
+ Log.w(TAG, "Session isn't active", new IllegalStateException());
+ }
}
@Override
public void removePlaylistItem_impl(MediaItem2 item) {
- // TODO(jaewan): Implement (b/73149584)
if (item == null) {
throw new IllegalArgumentException("item shouldn't be null");
}
+ final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_PLAYLIST_REMOVE_ITEM);
+ if (binder != null) {
+ try {
+ binder.removePlaylistItem(mControllerStub, item.toBundle());
+ } catch (RemoteException e) {
+ Log.w(TAG, "Cannot connect to the service or the session is gone", e);
+ }
+ } else {
+ Log.w(TAG, "Session isn't active", new IllegalStateException());
+ }
}
@Override
public void replacePlaylistItem_impl(int index, MediaItem2 item) {
- // TODO: Implement this (b/73149407)
if (index < 0) {
throw new IllegalArgumentException("index shouldn't be negative");
}
if (item == null) {
throw new IllegalArgumentException("item shouldn't be null");
}
+ final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_PLAYLIST_REPLACE_ITEM);
+ if (binder != null) {
+ try {
+ binder.replacePlaylistItem(mControllerStub, index, item.toBundle());
+ } catch (RemoteException e) {
+ Log.w(TAG, "Cannot connect to the service or the session is gone", e);
+ }
+ } else {
+ Log.w(TAG, "Session isn't active", new IllegalStateException());
+ }
}
@Override
@@ -616,38 +702,47 @@
}
}
+ // TODO(jaewan): Remove (b/74116823)
@Override
public void setPlaylistParams_impl(PlaylistParams params) {
if (params == null) {
throw new IllegalArgumentException("params shouldn't be null");
}
+ /*
Bundle args = new Bundle();
args.putBundle(MediaSession2Stub.ARGUMENT_KEY_PLAYLIST_PARAMS, params.toBundle());
sendTransportControlCommand(MediaSession2.COMMAND_CODE_PLAYBACK_SET_PLAYLIST_PARAMS, args);
+ */
}
@Override
public int getPlayerState_impl() {
- // TODO(jaewan): Implement
- return 0;
+ synchronized (mLock) {
+ return mPlayerState;
+ }
}
@Override
public long getPosition_impl() {
- // TODO(jaewan): Implement
- return 0;
+ synchronized (mLock) {
+ long timeDiff = System.currentTimeMillis() - mPositionEventTimeMs;
+ long expectedPosition = mPositionMs + (long) (mPlaybackSpeed * timeDiff);
+ return Math.max(0, expectedPosition);
+ }
}
@Override
public float getPlaybackSpeed_impl() {
- // TODO(jaewan): Implement
- return 0;
+ synchronized (mLock) {
+ return mPlaybackSpeed;
+ }
}
@Override
public long getBufferedPosition_impl() {
- // TODO(jaewan): Implement
- return 0;
+ synchronized (mLock) {
+ return mBufferedPositionMs;
+ }
}
@Override
@@ -656,15 +751,52 @@
return null;
}
- void pushPlaybackStateChanges(final PlaybackState2 state) {
+ void pushPlayerStateChanges(final int state) {
synchronized (mLock) {
- mPlaybackState = state;
+ mPlayerState = state;
}
mCallbackExecutor.execute(() -> {
if (!mInstance.isConnected()) {
return;
}
- mCallback.onPlaybackStateChanged(mInstance, state);
+ mCallback.onPlayerStateChanged(mInstance, state);
+ });
+ }
+
+ void pushPositionChanges(final long eventTimeMs, final long positionMs) {
+ synchronized (mLock) {
+ mPositionEventTimeMs = eventTimeMs;
+ mPositionMs = positionMs;
+ }
+ mCallbackExecutor.execute(() -> {
+ if (!mInstance.isConnected()) {
+ return;
+ }
+ mCallback.onPositionChanged(mInstance, eventTimeMs, positionMs);
+ });
+ }
+
+ void pushPlaybackSpeedChanges(final float speed) {
+ synchronized (mLock) {
+ mPlaybackSpeed = speed;
+ }
+ mCallbackExecutor.execute(() -> {
+ if (!mInstance.isConnected()) {
+ return;
+ }
+ mCallback.onPlaybackSpeedChanged(mInstance, speed);
+ });
+ }
+
+ void pushBufferedPositionChanges(final long bufferedPositionMs) {
+ synchronized (mLock) {
+ mBufferedPositionMs = bufferedPositionMs;
+ }
+ mCallbackExecutor.execute(() -> {
+ if (!mInstance.isConnected()) {
+ return;
+ }
+ mCallback.onBufferedPositionChanged(mInstance, bufferedPositionMs);
});
}
@@ -692,21 +824,42 @@
});
}
- void pushPlaylistChanges(final List<MediaItem2> playlist) {
+ void pushPlaylistChanges(final List<MediaItem2> playlist, final MediaMetadata2 metadata) {
synchronized (mLock) {
mPlaylist = playlist;
- mCallbackExecutor.execute(() -> {
- if (!mInstance.isConnected()) {
- return;
- }
- mCallback.onPlaylistChanged(mInstance, playlist);
- });
+ mPlaylistMetadata = metadata;
}
+ mCallbackExecutor.execute(() -> {
+ if (!mInstance.isConnected()) {
+ return;
+ }
+ // TODO(jaewan): Fix public API not to take playlistAgent.
+ mCallback.onPlaylistChanged(mInstance, null, playlist, metadata);
+ });
+ }
+
+ public void pushPlaylistMetadataChanges(MediaMetadata2 metadata) {
+ synchronized (mLock) {
+ mPlaylistMetadata = metadata;
+ }
+ mCallbackExecutor.execute(() -> {
+ if (!mInstance.isConnected()) {
+ return;
+ }
+ // TODO(jaewan): Fix public API not to take playlistAgent.
+ mCallback.onPlaylistMetadataChanged(mInstance, null, metadata);
+ });
}
// Should be used without a lock to prevent potential deadlock.
void onConnectedNotLocked(IMediaSession2 sessionBinder,
- final CommandGroup allowedCommands, final PlaybackState2 state, final PlaybackInfo info,
+ final CommandGroup allowedCommands,
+ final int playerState,
+ final long positionEventTimeMs,
+ final long positionMs,
+ final float playbackSpeed,
+ final long bufferedPositionMs,
+ final PlaybackInfo info,
final PlaylistParams params, final List<MediaItem2> playlist,
final PendingIntent sessionActivity) {
if (DEBUG) {
@@ -731,7 +884,11 @@
return;
}
mAllowedCommands = allowedCommands;
- mPlaybackState = state;
+ mPlayerState = playerState;
+ mPositionEventTimeMs = positionEventTimeMs;
+ mPositionMs = positionMs;
+ mPlaybackSpeed = playbackSpeed;
+ mBufferedPositionMs = bufferedPositionMs;
mPlaybackInfo = info;
mPlaylistParams = params;
mPlaylist = playlist;
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2CallbackStub.java b/packages/MediaComponents/src/com/android/media/MediaController2Stub.java
similarity index 80%
rename from packages/MediaComponents/src/com/android/media/MediaSession2CallbackStub.java
rename to packages/MediaComponents/src/com/android/media/MediaController2Stub.java
index 451368f..99bdfce 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSession2CallbackStub.java
+++ b/packages/MediaComponents/src/com/android/media/MediaController2Stub.java
@@ -20,11 +20,11 @@
import android.content.Context;
import android.media.MediaController2;
import android.media.MediaItem2;
+import android.media.MediaMetadata2;
import android.media.MediaSession2.Command;
import android.media.MediaSession2.CommandButton;
import android.media.MediaSession2.CommandGroup;
import android.media.MediaSession2.PlaylistParams;
-import android.media.PlaybackState2;
import android.os.Bundle;
import android.os.ResultReceiver;
import android.text.TextUtils;
@@ -37,13 +37,13 @@
import java.util.ArrayList;
import java.util.List;
-public class MediaSession2CallbackStub extends IMediaSession2Callback.Stub {
- private static final String TAG = "MS2CallbackStub";
+public class MediaController2Stub extends IMediaController2.Stub {
+ private static final String TAG = "MediaController2Stub";
private static final boolean DEBUG = true; // TODO(jaewan): Change
private final WeakReference<MediaController2Impl> mController;
- MediaSession2CallbackStub(MediaController2Impl controller) {
+ MediaController2Stub(MediaController2Impl controller) {
mController = new WeakReference<>(controller);
}
@@ -68,7 +68,7 @@
}
@Override
- public void onPlaybackStateChanged(Bundle state) throws RuntimeException {
+ public void onPlayerStateChanged(int state) {
final MediaController2Impl controller;
try {
controller = getController();
@@ -76,12 +76,59 @@
Log.w(TAG, "Don't fail silently here. Highly likely a bug");
return;
}
- controller.pushPlaybackStateChanges(
- PlaybackState2.fromBundle(controller.getContext(), state));
+ controller.pushPlayerStateChanges(state);
}
@Override
- public void onPlaylistChanged(List<Bundle> playlistBundle) throws RuntimeException {
+ public void onPositionChanged(long eventTimeMs, long positionMs) {
+ final MediaController2Impl controller;
+ try {
+ controller = getController();
+ } catch (IllegalStateException e) {
+ Log.w(TAG, "Don't fail silently here. Highly likely a bug");
+ return;
+ }
+ if (eventTimeMs < 0) {
+ Log.w(TAG, "onPositionChanged(): Ignoring negative eventTimeMs");
+ return;
+ }
+ if (positionMs < 0) {
+ Log.w(TAG, "onPositionChanged(): Ignoring negative positionMs");
+ return;
+ }
+ controller.pushPositionChanges(eventTimeMs, positionMs);
+ }
+
+ @Override
+ public void onPlaybackSpeedChanged(float speed) {
+ final MediaController2Impl controller;
+ try {
+ controller = getController();
+ } catch (IllegalStateException e) {
+ Log.w(TAG, "Don't fail silently here. Highly likely a bug");
+ return;
+ }
+ controller.pushPlaybackSpeedChanges(speed);
+ }
+
+ @Override
+ public void onBufferedPositionChanged(long bufferedPositionMs) {
+ final MediaController2Impl controller;
+ try {
+ controller = getController();
+ } catch (IllegalStateException e) {
+ Log.w(TAG, "Don't fail silently here. Highly likely a bug");
+ return;
+ }
+ if (bufferedPositionMs < 0) {
+ Log.w(TAG, "onBufferedPositionChanged(): Ignoring negative bufferedPositionMs");
+ return;
+ }
+ controller.pushBufferedPositionChanges(bufferedPositionMs);
+ }
+
+ @Override
+ public void onPlaylistChanged(List<Bundle> playlistBundle, Bundle metadataBundle) {
final MediaController2Impl controller;
try {
controller = getController();
@@ -90,7 +137,7 @@
return;
}
if (playlistBundle == null) {
- Log.w(TAG, "onPlaylistChanged(): Ignoring null playlist");
+ Log.w(TAG, "onPlaylistChanged(): Ignoring null playlist from " + controller);
return;
}
List<MediaItem2> playlist = new ArrayList<>();
@@ -102,7 +149,23 @@
playlist.add(item);
}
}
- controller.pushPlaylistChanges(playlist);
+ MediaMetadata2 metadata =
+ MediaMetadata2.fromBundle(controller.getContext(), metadataBundle);
+ controller.pushPlaylistChanges(playlist, metadata);
+ }
+
+ @Override
+ public void onPlaylistMetadataChanged(Bundle metadataBundle) throws RuntimeException {
+ final MediaController2Impl controller;
+ try {
+ controller = getController();
+ } catch (IllegalStateException e) {
+ Log.w(TAG, "Don't fail silently here. Highly likely a bug");
+ return;
+ }
+ MediaMetadata2 metadata =
+ MediaMetadata2.fromBundle(controller.getContext(), metadataBundle);
+ controller.pushPlaylistMetadataChanges(metadata);
}
@Override
@@ -146,8 +209,9 @@
@Override
public void onConnected(IMediaSession2 sessionBinder, Bundle commandGroup,
- Bundle playbackState, Bundle playbackInfo, Bundle playlistParams, List<Bundle>
- itemBundleList, PendingIntent sessionActivity) {
+ int playerState, long positionEventTimeMs, long positionMs, float playbackSpeed,
+ long bufferedPositionMs, Bundle playbackInfo, Bundle playlistParams,
+ List<Bundle> itemBundleList, PendingIntent sessionActivity) {
final MediaController2Impl controller = mController.get();
if (controller == null) {
if (DEBUG) {
@@ -168,7 +232,7 @@
}
controller.onConnectedNotLocked(sessionBinder,
CommandGroup.fromBundle(context, commandGroup),
- PlaybackState2.fromBundle(context, playbackState),
+ playerState, positionEventTimeMs, positionMs, playbackSpeed, bufferedPositionMs,
PlaybackInfoImpl.fromBundle(context, playbackInfo),
PlaylistParams.fromBundle(context, playlistParams),
itemList, sessionActivity);
diff --git a/packages/MediaComponents/src/com/android/media/MediaItem2Impl.java b/packages/MediaComponents/src/com/android/media/MediaItem2Impl.java
index 039ff8f..c95b43f 100644
--- a/packages/MediaComponents/src/com/android/media/MediaItem2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaItem2Impl.java
@@ -31,21 +31,31 @@
import android.os.Bundle;
import android.text.TextUtils;
+import java.util.UUID;
+
public class MediaItem2Impl implements MediaItem2Provider {
private static final String KEY_ID = "android.media.mediaitem2.id";
private static final String KEY_FLAGS = "android.media.mediaitem2.flags";
private static final String KEY_METADATA = "android.media.mediaitem2.metadata";
+ private static final String KEY_UUID = "android.media.mediaitem2.uuid";
private final Context mContext;
private final MediaItem2 mInstance;
private final String mId;
private final int mFlags;
+ private final UUID mUUID;
private MediaMetadata2 mMetadata;
private DataSourceDesc mDataSourceDesc;
// From the public API
public MediaItem2Impl(@NonNull Context context, @NonNull String mediaId,
@Nullable DataSourceDesc dsd, @Nullable MediaMetadata2 metadata, @Flags int flags) {
+ this(context, mediaId, dsd, metadata, flags, null);
+ }
+
+ private MediaItem2Impl(@NonNull Context context, @NonNull String mediaId,
+ @Nullable DataSourceDesc dsd, @Nullable MediaMetadata2 metadata, @Flags int flags,
+ @Nullable UUID uuid) {
if (mediaId == null) {
throw new IllegalArgumentException("mediaId shouldn't be null");
}
@@ -58,24 +68,18 @@
mDataSourceDesc = dsd;
mMetadata = metadata;
mFlags = flags;
+ mUUID = (uuid == null) ? UUID.randomUUID() : uuid;
mInstance = new MediaItem2(this);
}
- // Create anonymized version
- public MediaItem2Impl(Context context, String mediaId, MediaMetadata2 metadata,
- @Flags int flags) {
- if (mediaId == null) {
- throw new IllegalArgumentException("mediaId shouldn't be null");
+ @Override
+ public boolean equals_impl(Object obj) {
+ if (!(obj instanceof MediaItem2)) {
+ return false;
}
- if (metadata != null && !TextUtils.equals(mediaId, metadata.getMediaId())) {
- throw new IllegalArgumentException("metadata's id should be matched with the mediaid");
- }
- mContext = context;
- mId = mediaId;
- mMetadata = metadata;
- mFlags = flags;
- mInstance = new MediaItem2(this);
+ MediaItem2 other = (MediaItem2) obj;
+ return mUUID.equals(((MediaItem2Impl) other.getProvider()).mUUID);
}
/**
@@ -90,10 +94,37 @@
if (mMetadata != null) {
bundle.putBundle(KEY_METADATA, mMetadata.toBundle());
}
+ bundle.putString(KEY_UUID, mUUID.toString());
return bundle;
}
- public static MediaItem2 fromBundle(Context context, Bundle bundle) {
+ /**
+ * Create a MediaItem2 from the {@link Bundle}.
+ *
+ * @param context A context.
+ * @param bundle The bundle which was published by {@link MediaItem2#toBundle()}.
+ * @return The newly created MediaItem2
+ */
+ public static MediaItem2 fromBundle(@NonNull Context context, @NonNull Bundle bundle) {
+ if (bundle == null) {
+ return null;
+ }
+ final String uuidString = bundle.getString(KEY_UUID);
+ return fromBundle(context, bundle, UUID.fromString(uuidString));
+ }
+
+ /**
+ * Create a MediaItem2 from the {@link Bundle} with the specified {@link UUID}.
+ * If {@link UUID}
+ * can be null for creating new.
+ *
+ * @param context A context.
+ * @param bundle The bundle which was published by {@link MediaItem2#toBundle()}.
+ * @param uuid A {@link UUID} to override. Can be {@link null} for override.
+ * @return The newly created MediaItem2
+ */
+ static MediaItem2 fromBundle(@NonNull Context context, @NonNull Bundle bundle,
+ @Nullable UUID uuid) {
if (bundle == null) {
return null;
}
@@ -102,7 +133,7 @@
final MediaMetadata2 metadata = metadataBundle != null
? MediaMetadata2.fromBundle(context, metadataBundle) : null;
final int flags = bundle.getInt(KEY_FLAGS);
- return new MediaItem2Impl(context, id, metadata, flags).getInstance();
+ return new MediaItem2Impl(context, id, null, metadata, flags, uuid).getInstance();
}
private MediaItem2 getInstance() {
diff --git a/packages/MediaComponents/src/com/android/media/MediaMetadata2Impl.java b/packages/MediaComponents/src/com/android/media/MediaMetadata2Impl.java
index 333455d..286f5ee 100644
--- a/packages/MediaComponents/src/com/android/media/MediaMetadata2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaMetadata2Impl.java
@@ -228,7 +228,7 @@
}
public static MediaMetadata2 fromBundle(Context context, Bundle bundle) {
- return new MediaMetadata2Impl(context, bundle).getInstance();
+ return (bundle == null) ? null : new MediaMetadata2Impl(context, bundle).getInstance();
}
public static final class BuilderImpl implements MediaMetadata2Provider.BuilderProvider {
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java b/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
index 5c18515..b8c185a 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
@@ -52,7 +52,6 @@
import android.media.MediaSession2.PlaylistParams.ShuffleMode;
import android.media.MediaSession2.SessionCallback;
import android.media.MediaSessionService2;
-import android.media.PlaybackState2;
import android.media.SessionToken2;
import android.media.VolumeProvider2;
import android.media.session.MediaSessionManager;
@@ -68,7 +67,9 @@
import android.util.Log;
import java.lang.ref.WeakReference;
+import java.lang.reflect.Field;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executor;
@@ -248,11 +249,11 @@
oldAgent.unregisterPlaylistEventCallback(mPlaylistEventCallback);
}
}
- // TODO(jaewan): Notify controllers about the change in the media player base (b/74370608)
- // Note that notification will be done indirectly by telling player state,
- // position, buffered position, etc.
- mSessionStub.notifyPlaybackInfoChanged(info);
- notifyPlaybackStateChangedNotLocked(mInstance.getPlaybackState());
+
+ if (oldPlayer != null) {
+ mSessionStub.notifyPlaybackInfoChanged(info);
+ notifyPlayerUpdatedNotLocked(oldPlayer);
+ }
}
private PlaybackInfo createPlaybackInfo(VolumeProvider2 volumeProvider, AudioAttributes attrs) {
@@ -496,22 +497,27 @@
}
@Override
- public void setPlaylist_impl(List<MediaItem2> playlist) {
- if (playlist == null) {
- throw new IllegalArgumentException("playlist shouldn't be null");
+ public void setPlaylist_impl(List<MediaItem2> list, MediaMetadata2 metadata) {
+ if (list == null) {
+ throw new IllegalArgumentException("list shouldn't be null");
}
ensureCallingThread();
- // TODO: Uncomment or remove
- /*
- final MediaPlayerBase player = mPlayer;
- if (player != null) {
- // TODO implement and use SessionPlaylistAgent
- //player.setPlaylist(playlist);
- mSessionStub.notifyPlaylistChanged(playlist);
+ final MediaPlaylistAgent agent = mPlaylistAgent;
+ if (agent != null) {
+ agent.setPlaylist(list, metadata);
} else if (DEBUG) {
Log.d(TAG, "API calls after the close()", new IllegalStateException());
}
- */
+ }
+
+ @Override
+ public void updatePlaylistMetadata_impl(MediaMetadata2 metadata) {
+ final MediaPlaylistAgent agent = mPlaylistAgent;
+ if (agent != null) {
+ agent.updatePlaylistMetadata(metadata);
+ } else if (DEBUG) {
+ Log.d(TAG, "API calls after the close()", new IllegalStateException());
+ }
}
@Override
@@ -522,7 +528,12 @@
if (item == null) {
throw new IllegalArgumentException("item shouldn't be null");
}
- // TODO(jaewan): Implement
+ final MediaPlaylistAgent agent = mPlaylistAgent;
+ if (agent != null) {
+ agent.addPlaylistItem(index, item);
+ } else if (DEBUG) {
+ Log.d(TAG, "API calls after the close()", new IllegalStateException());
+ }
}
@Override
@@ -530,15 +541,12 @@
if (item == null) {
throw new IllegalArgumentException("item shouldn't be null");
}
- // TODO(jaewan): Implement
- }
-
- @Override
- public void editPlaylistItem_impl(MediaItem2 item) {
- if (item == null) {
- throw new IllegalArgumentException("item shouldn't be null");
+ final MediaPlaylistAgent agent = mPlaylistAgent;
+ if (agent != null) {
+ agent.removePlaylistItem(item);
+ } else if (DEBUG) {
+ Log.d(TAG, "API calls after the close()", new IllegalStateException());
}
- // TODO(jaewan): Implement
}
@Override
@@ -549,24 +557,33 @@
if (item == null) {
throw new IllegalArgumentException("item shouldn't be null");
}
- // TODO(jaewan): Implement
+ final MediaPlaylistAgent agent = mPlaylistAgent;
+ if (agent != null) {
+ agent.replacePlaylistItem(index, item);
+ } else if (DEBUG) {
+ Log.d(TAG, "API calls after the close()", new IllegalStateException());
+ }
}
@Override
public List<MediaItem2> getPlaylist_impl() {
- // TODO: Uncomment or remove
- /*
- final MediaPlayerBase player = mPlayer;
- if (player != null) {
- // TODO(jaewan): Is it safe to be called on any thread?
- // Otherwise MediaSession2 should cache parameter of setPlaylist.
- // TODO implement
- //return player.getPlaylist();
- return null;
+ final MediaPlaylistAgent agent = mPlaylistAgent;
+ if (agent != null) {
+ return agent.getPlaylist();
} else if (DEBUG) {
Log.d(TAG, "API calls after the close()", new IllegalStateException());
}
- */
+ return null;
+ }
+
+ @Override
+ public MediaMetadata2 getPlaylistMetadata_impl() {
+ final MediaPlaylistAgent agent = mPlaylistAgent;
+ if (agent != null) {
+ return agent.getPlaylistMetadata();
+ } else if (DEBUG) {
+ Log.d(TAG, "API calls after the close()", new IllegalStateException());
+ }
return null;
}
@@ -660,12 +677,6 @@
return;
}
mCallbacks.put(callback, executor);
- // TODO: Uncomment or remove
- /*
- // TODO(jaewan): Double check if we need this.
- final PlaybackState2 state = getInstance().getPlaybackState();
- executor.execute(() -> callback.onPlaybackStateChanged(state));
- */
}
@Override
@@ -678,25 +689,6 @@
}
@Override
- public PlaybackState2 getPlaybackState_impl() {
- ensureCallingThread();
- // TODO: Uncomment or remove
- /*
- final MediaPlayerBase player = mPlayer;
- if (player != null) {
- // TODO(jaewan): Is it safe to be called on any thread?
- // Otherwise MediaSession2 should cache the result from listener.
- // TODO implement
- //return player.getPlaybackState();
- return null;
- } else if (DEBUG) {
- Log.d(TAG, "API calls after the close()", new IllegalStateException());
- }
- */
- return null;
- }
-
- @Override
public void notifyError_impl(int errorCode, Bundle extras) {
// TODO(jaewan): Implement
}
@@ -725,9 +717,31 @@
}*/
}
- private void notifyPlaybackStateChangedNotLocked(final PlaybackState2 state) {
+ private void notifyPlaylistChangedOnExecutor(MediaPlaylistAgent playlistAgent,
+ List<MediaItem2> list, MediaMetadata2 metadata) {
+ if (playlistAgent != mPlaylistAgent) {
+ // Ignore calls from the old agent. Ignore.
+ return;
+ }
+ mCallback.onPlaylistChanged(mInstance, playlistAgent, list, metadata);
+ mSessionStub.notifyPlaylistChangedNotLocked(list, metadata);
+ }
+
+ private void notifyPlaylistMetadataChangedOnExecutor(MediaPlaylistAgent playlistAgent,
+ MediaMetadata2 metadata) {
+ if (playlistAgent != mPlaylistAgent) {
+ // Ignore calls from the old agent. Ignore.
+ return;
+ }
+ mCallback.onPlaylistMetadataChanged(mInstance, playlistAgent, metadata);
+ mSessionStub.notifyPlaylistMetadataChangedNotLocked(metadata);
+ }
+
+ private void notifyPlayerUpdatedNotLocked(MediaPlayerBase oldPlayer) {
ArrayMap<PlayerEventCallback, Executor> callbacks = new ArrayMap<>();
+ MediaPlayerBase player;
synchronized (mLock) {
+ player = mPlayer;
callbacks.putAll(mCallbacks);
}
// Notify to callbacks added directly to this session
@@ -738,7 +752,26 @@
//executor.execute(() -> callback.onPlaybackStateChanged(state));
}
// Notify to controllers as well.
- mSessionStub.notifyPlaybackStateChangedNotLocked(state);
+ final int state = player.getPlayerState();
+ if (state != oldPlayer.getPlayerState()) {
+ mSessionStub.notifyPlayerStateChangedNotLocked(state);
+ }
+
+ final long currentTimeMs = System.currentTimeMillis();
+ final long position = player.getCurrentPosition();
+ if (position != oldPlayer.getCurrentPosition()) {
+ mSessionStub.notifyPositionChangedNotLocked(currentTimeMs, position);
+ }
+
+ final float speed = player.getPlaybackSpeed();
+ if (speed != oldPlayer.getPlaybackSpeed()) {
+ mSessionStub.notifyPlaybackSpeedChangedNotLocked(speed);
+ }
+
+ final long bufferedPosition = player.getBufferedPosition();
+ if (bufferedPosition != oldPlayer.getBufferedPosition()) {
+ mSessionStub.notifyBufferedPositionChangedNotLocked(bufferedPosition);
+ }
}
private void notifyErrorNotLocked(String mediaId, int what, int extra) {
@@ -768,6 +801,10 @@
return mPlayer;
}
+ MediaPlaylistAgent getPlaylistAgent() {
+ return mPlaylistAgent;
+ }
+
Executor getCallbackExecutor() {
return mCallbackExecutor;
}
@@ -803,26 +840,64 @@
@Override
public void onCurrentDataSourceChanged(MediaPlayerBase mpb, DataSourceDesc dsd) {
- super.onCurrentDataSourceChanged(mpb, dsd);
- // TODO(jaewan): Handle this b/74370608
+ MediaSession2Impl session = getSession();
+ if (session == null) {
+ return;
+ }
+ session.getCallbackExecutor().execute(() -> {
+ // TODO (jaewan): Convert dsd to MediaItem (b/74506462)
+ // TODO (jaewan): Notify controllers through appropriate callback. (b/74505936)
+ session.getCallback().onCurrentMediaItemChanged(
+ session.getInstance(), mpb, null /* MediaItem */);
+ });
}
@Override
public void onMediaPrepared(MediaPlayerBase mpb, DataSourceDesc dsd) {
- super.onMediaPrepared(mpb, dsd);
- // TODO(jaewan): Handle this b/74370608
+ MediaSession2Impl session = getSession();
+ if (session == null) {
+ return;
+ }
+ session.getCallbackExecutor().execute(() -> {
+ // TODO (jaewan): Convert dsd to MediaItem (b/74506462)
+ // TODO (jaewan): Notify controllers through appropriate callback. (b/74505936)
+ session.getCallback().onMediaPrepared(
+ session.getInstance(), mpb, null /* MediaItem */);
+ });
}
@Override
public void onPlayerStateChanged(MediaPlayerBase mpb, int state) {
- super.onPlayerStateChanged(mpb, state);
- // TODO(jaewan): Handle this b/74370608
+ MediaSession2Impl session = getSession();
+ if (session == null) {
+ return;
+ }
+ session.getCallbackExecutor().execute(() -> {
+ session.getCallback().onPlayerStateChanged(session.getInstance(), mpb, state);
+ session.getSessionStub().notifyPlayerStateChangedNotLocked(state);
+ });
}
@Override
public void onBufferingStateChanged(MediaPlayerBase mpb, DataSourceDesc dsd, int state) {
- super.onBufferingStateChanged(mpb, dsd, state);
- // TODO(jaewan): Handle this b/74370608
+ MediaSession2Impl session = getSession();
+ if (session == null) {
+ return;
+ }
+ session.getCallbackExecutor().execute(() -> {
+ // TODO (jaewan): Convert dsd to MediaItem (b/74506462)
+ // TODO (jaewan): Notify controllers through appropriate callback. (b/74505936)
+ session.getCallback().onBufferingStateChanged(
+ session.getInstance(), mpb, null /* MediaItem */, state);
+ });
+ }
+
+ private MediaSession2Impl getSession() {
+ final MediaSession2Impl session = mSession.get();
+ if (session == null && DEBUG) {
+ Log.d(TAG, "Session is closed", new IllegalStateException());
+ }
+ return session;
}
}
@@ -836,15 +911,21 @@
@Override
public void onPlaylistChanged(MediaPlaylistAgent playlistAgent, List<MediaItem2> list,
MediaMetadata2 metadata) {
- super.onPlaylistChanged(playlistAgent, list, metadata);
- // TODO(jaewan): Handle this (b/74326040)
+ final MediaSession2Impl session = mSession.get();
+ if (session == null) {
+ return;
+ }
+ session.notifyPlaylistChangedOnExecutor(playlistAgent, list, metadata);
}
@Override
public void onPlaylistMetadataChanged(MediaPlaylistAgent playlistAgent,
MediaMetadata2 metadata) {
- super.onPlaylistMetadataChanged(playlistAgent, metadata);
- // TODO(jaewan): Handle this (b/74174649)
+ final MediaSession2Impl session = mSession.get();
+ if (session == null) {
+ return;
+ }
+ session.notifyPlaylistMetadataChangedOnExecutor(playlistAgent, metadata);
}
@Override
@@ -891,14 +972,17 @@
mExtras = extras;
}
+ @Override
public int getCommandCode_impl() {
return mCommandCode;
}
+ @Override
public @Nullable String getCustomCommand_impl() {
return mCustomCommand;
}
+ @Override
public @Nullable Bundle getExtras_impl() {
return mExtras;
}
@@ -906,6 +990,7 @@
/**
* @return a new Bundle instance from the Command
*/
+ @Override
public Bundle toBundle_impl() {
Bundle bundle = new Bundle();
bundle.putInt(KEY_COMMAND_CODE, mCommandCode);
@@ -959,6 +1044,16 @@
public static class CommandGroupImpl implements CommandGroupProvider {
private static final String KEY_COMMANDS =
"android.media.mediasession2.commandgroup.commands";
+
+ // Prefix for all command codes
+ private static final String PREFIX_COMMAND_CODE = "COMMAND_CODE_";
+
+ // Prefix for command codes that will be sent directly to the MediaPlayerBase
+ private static final String PREFIX_COMMAND_CODE_PLAYBACK = "COMMAND_CODE_PLAYBACK_";
+
+ // Prefix for command codes that will be sent directly to the MediaPlaylistAgent
+ private static final String PREFIX_COMMAND_CODE_PLAYLIST = "COMMAND_CODE_PLAYLIST_";
+
private List<Command> mCommands = new ArrayList<>();
private final Context mContext;
private final CommandGroup mInstance;
@@ -971,6 +1066,11 @@
}
}
+ public CommandGroupImpl(Context context) {
+ mContext = context;
+ mInstance = new CommandGroup(this);
+ }
+
@Override
public void addCommand_impl(Command command) {
if (command == null) {
@@ -981,8 +1081,30 @@
@Override
public void addAllPredefinedCommands_impl() {
- for (int i = 1; i <= MediaSession2.COMMAND_CODE_MAX; i++) {
- mCommands.add(new Command(mContext, i));
+ addCommandsWithPrefix(PREFIX_COMMAND_CODE);
+ }
+
+ public void addAllPlaybackCommands() {
+ addCommandsWithPrefix(PREFIX_COMMAND_CODE_PLAYBACK);
+ }
+
+ public void addAllPlaylistCommands() {
+ addCommandsWithPrefix(PREFIX_COMMAND_CODE_PLAYLIST);
+ }
+
+ private void addCommandsWithPrefix(String prefix) {
+ // TODO(jaewan): (Can be post-P): Don't use reflection for this purpose.
+ final Field[] fields = MediaSession2.class.getFields();
+ if (fields != null) {
+ for (int i = 0; i < fields.length; i++) {
+ if (fields[i].getName().startsWith(prefix)) {
+ try {
+ mCommands.add(new Command(mContext, fields[i].getInt(null)));
+ } catch (IllegalAccessException e) {
+ Log.w(TAG, "Unexpected " + fields[i] + " in MediaSession2");
+ }
+ }
+ }
}
}
@@ -1017,7 +1139,11 @@
@Override
public List<Command> getCommands_impl() {
- return mCommands;
+ return getCommands();
+ }
+
+ public List<Command> getCommands() {
+ return Collections.unmodifiableList(mCommands);
}
/**
@@ -1068,10 +1194,10 @@
private final int mUid;
private final String mPackageName;
private final boolean mIsTrusted;
- private final IMediaSession2Callback mControllerBinder;
+ private final IMediaController2 mControllerBinder;
public ControllerInfoImpl(Context context, ControllerInfo instance, int uid,
- int pid, String packageName, IMediaSession2Callback callback) {
+ int pid, String packageName, IMediaController2 callback) {
if (TextUtils.isEmpty(packageName)) {
throw new IllegalArgumentException("packageName shouldn't be empty");
}
@@ -1147,7 +1273,7 @@
return mControllerBinder.asBinder();
}
- public IMediaSession2Callback getControllerBinder() {
+ public IMediaController2 getControllerBinder() {
return mControllerBinder;
}
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java b/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
index a9c5224..8d9cf64 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
@@ -21,13 +21,14 @@
import android.media.MediaController2;
import android.media.MediaItem2;
import android.media.MediaLibraryService2.LibraryRoot;
+import android.media.MediaMetadata2;
+import android.media.MediaPlayerBase;
import android.media.MediaSession2;
import android.media.MediaSession2.Command;
import android.media.MediaSession2.CommandButton;
import android.media.MediaSession2.CommandGroup;
import android.media.MediaSession2.ControllerInfo;
import android.media.MediaSession2.PlaylistParams;
-import android.media.PlaybackState2;
import android.media.Rating2;
import android.media.VolumeProvider2;
import android.net.Uri;
@@ -37,12 +38,15 @@
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.support.annotation.GuardedBy;
+import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
+import android.util.SparseArray;
import com.android.media.MediaLibraryService2Impl.MediaLibrarySessionImpl;
import com.android.media.MediaSession2Impl.CommandButtonImpl;
+import com.android.media.MediaSession2Impl.CommandGroupImpl;
import com.android.media.MediaSession2Impl.ControllerInfoImpl;
import java.lang.ref.WeakReference;
@@ -61,6 +65,8 @@
private static final String TAG = "MediaSession2Stub";
private static final boolean DEBUG = true; // TODO(jaewan): Rename.
+ private static final SparseArray<Command> sCommandsForOnCommandRequest = new SparseArray<>();
+
private final Object mLock = new Object();
private final WeakReference<MediaSession2Impl> mSession;
@@ -75,6 +81,19 @@
public MediaSession2Stub(MediaSession2Impl session) {
mSession = new WeakReference<>(session);
+
+ synchronized (sCommandsForOnCommandRequest) {
+ if (sCommandsForOnCommandRequest.size() == 0) {
+ CommandGroupImpl group = new CommandGroupImpl(session.getContext());
+ group.addAllPlaybackCommands();
+ group.addAllPlaylistCommands();
+ List<Command> commands = group.getCommands();
+ for (int i = 0; i < commands.size(); i++) {
+ Command command = commands.get(i);
+ sCommandsForOnCommandRequest.append(command.getCommandCode(), command);
+ }
+ }
+ }
}
public void destroyNotLocked() {
@@ -85,7 +104,7 @@
mControllers.clear();
}
for (int i = 0; i < list.size(); i++) {
- IMediaSession2Callback controllerBinder =
+ IMediaController2 controllerBinder =
((ControllerInfoImpl) list.get(i).getProvider()).getControllerBinder();
try {
// Should be used without a lock hold to prevent potential deadlock.
@@ -113,7 +132,7 @@
}
// Get controller if the command from caller to session is able to be handled.
- private ControllerInfo getControllerIfAble(IMediaSession2Callback caller) {
+ private ControllerInfo getControllerIfAble(IMediaController2 caller) {
synchronized (mLock) {
final ControllerInfo controllerInfo = mControllers.get(caller.asBinder());
if (controllerInfo == null && DEBUG) {
@@ -124,7 +143,7 @@
}
// Get controller if the command from caller to session is able to be handled.
- private ControllerInfo getControllerIfAble(IMediaSession2Callback caller, int commandCode) {
+ private ControllerInfo getControllerIfAble(IMediaController2 caller, int commandCode) {
synchronized (mLock) {
final ControllerInfo controllerInfo = getControllerIfAble(caller);
if (controllerInfo == null) {
@@ -147,7 +166,7 @@
}
// Get controller if the command from caller to session is able to be handled.
- private ControllerInfo getControllerIfAble(IMediaSession2Callback caller, Command command) {
+ private ControllerInfo getControllerIfAble(IMediaController2 caller, Command command) {
synchronized (mLock) {
final ControllerInfo controllerInfo = getControllerIfAble(caller);
if (controllerInfo == null) {
@@ -170,7 +189,7 @@
}
// Return binder if the session is able to send a command to the controller.
- private IMediaSession2Callback getControllerBinderIfAble(ControllerInfo controller) {
+ private IMediaController2 getControllerBinderIfAble(ControllerInfo controller) {
if (getSession() == null) {
// getSession() already logged if session is closed.
return null;
@@ -190,7 +209,7 @@
}
// Return binder if the session is able to send a command to the controller.
- private IMediaSession2Callback getControllerBinderIfAble(ControllerInfo controller,
+ private IMediaController2 getControllerBinderIfAble(ControllerInfo controller,
int commandCode) {
synchronized (mLock) {
CommandGroup allowedCommands = mAllowedCommandGroupMap.get(controller);
@@ -208,11 +227,55 @@
}
}
+ private void onCommand(@NonNull IMediaController2 caller, int commandCode,
+ @NonNull SessionRunnable runnable) {
+ final MediaSession2Impl session = getSession();
+ final ControllerInfo controller = getControllerIfAble(caller, commandCode);
+ if (session == null || controller == null) {
+ return;
+ }
+ session.getCallbackExecutor().execute(() -> {
+ if (getControllerIfAble(caller, commandCode) == null) {
+ return;
+ }
+ Command command = sCommandsForOnCommandRequest.get(commandCode);
+ if (command != null) {
+ boolean accepted = session.getCallback().onCommandRequest(session.getInstance(),
+ controller, command);
+ if (!accepted) {
+ // Don't run rejected command.
+ if (DEBUG) {
+ Log.d(TAG, "Command (code=" + commandCode + ") from "
+ + controller + " was rejected by " + session);
+ }
+ return;
+ }
+ }
+ runnable.run(session, controller);
+ });
+ }
+
+ private void onBrowserCommand(@NonNull IMediaController2 caller,
+ @NonNull LibrarySessionRunnable runnable) {
+ final MediaLibrarySessionImpl session = getLibrarySession();
+ final ControllerInfo controller =
+ getControllerIfAble(caller, MediaSession2.COMMAND_CODE_BROWSER);
+ if (session == null || controller == null) {
+ return;
+ }
+ session.getCallbackExecutor().execute(() -> {
+ if (getControllerIfAble(caller, MediaSession2.COMMAND_CODE_BROWSER) == null) {
+ return;
+ }
+ runnable.run(session, controller);
+ });
+ }
+
//////////////////////////////////////////////////////////////////////////////////////////////
// AIDL methods for session overrides
//////////////////////////////////////////////////////////////////////////////////////////////
@Override
- public void connect(final IMediaSession2Callback caller, final String callingPackage)
+ public void connect(final IMediaController2 caller, final String callingPackage)
throws RuntimeException {
final MediaSession2Impl session = getSession();
if (session == null) {
@@ -256,11 +319,13 @@
// It's needed because we cannot call synchronous calls between session/controller.
// Note: We're doing this after the onConnectionChanged(), but there's no guarantee
// that events here are notified after the onConnected() because
- // IMediaSession2Callback is oneway (i.e. async call) and CallbackStub will
+ // IMediaController2 is oneway (i.e. async call) and Stub will
// use thread poll for incoming calls.
- // TODO(jaewan): Should we protect getting playback state?
- final PlaybackState2 state = session.getInstance().getPlaybackState();
- final Bundle playbackStateBundle = (state != null) ? state.toBundle() : null;
+ final int playerState = session.getInstance().getPlayerState();
+ final long positionEventTimeMs = System.currentTimeMillis();
+ final long positionMs = session.getInstance().getCurrentPosition();
+ final float playbackSpeed = session.getInstance().getPlaybackSpeed();
+ final long bufferedPositionMs = session.getInstance().getBufferedPosition();
final Bundle playbackInfoBundle = ((MediaController2Impl.PlaybackInfoImpl)
session.getPlaybackInfo().getProvider()).toBundle();
final PlaylistParams params = session.getInstance().getPlaylistParams();
@@ -292,9 +357,10 @@
return;
}
try {
- caller.onConnected(MediaSession2Stub.this,
- allowedCommands.toBundle(), playbackStateBundle, playbackInfoBundle,
- paramsBundle, playlistBundle, sessionActivity);
+ caller.onConnected(MediaSession2Stub.this, allowedCommands.toBundle(),
+ playerState, positionEventTimeMs, positionMs, playbackSpeed,
+ bufferedPositionMs, playbackInfoBundle, paramsBundle, playlistBundle,
+ sessionActivity);
} catch (RemoteException e) {
// Controller may be died prematurely.
// TODO(jaewan): Handle here.
@@ -317,43 +383,28 @@
}
@Override
- public void release(final IMediaSession2Callback caller) throws RemoteException {
+ public void release(final IMediaController2 caller) throws RemoteException {
+ ControllerInfo controller;
synchronized (mLock) {
- ControllerInfo controllerInfo = mControllers.remove(caller.asBinder());
+ controller = mControllers.remove(caller.asBinder());
if (DEBUG) {
- Log.d(TAG, "releasing " + controllerInfo);
+ Log.d(TAG, "releasing " + controller);
}
- mSubscriptions.remove(controllerInfo);
+ mSubscriptions.remove(controller);
}
- }
-
- @Override
- public void setVolumeTo(final IMediaSession2Callback caller, final int value, final int flags)
- throws RuntimeException {
final MediaSession2Impl session = getSession();
- final ControllerInfo controller = getControllerIfAble(
- caller, MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME);
if (session == null || controller == null) {
return;
}
session.getCallbackExecutor().execute(() -> {
- if (getControllerIfAble(caller, MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME) == null) {
- return;
- }
- // TODO(jaewan): Sanity check.
- Command command = new Command(
- session.getContext(), MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME);
- boolean accepted = session.getCallback().onCommandRequest(session.getInstance(),
- controller, command);
- if (!accepted) {
- // Don't run rejected command.
- if (DEBUG) {
- Log.d(TAG, "Command " + MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME + " from "
- + controller + " was rejected by " + session);
- }
- return;
- }
+ session.getCallback().onDisconnected(session.getInstance(), controller);
+ });
+ }
+ @Override
+ public void setVolumeTo(final IMediaController2 caller, final int value, final int flags)
+ throws RuntimeException {
+ onCommand(caller, MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME, (session, controller) -> {
VolumeProvider2 volumeProvider = session.getVolumeProvider();
if (volumeProvider == null) {
// TODO(jaewan): Set local stream volume
@@ -364,32 +415,9 @@
}
@Override
- public void adjustVolume(IMediaSession2Callback caller, int direction, int flags)
+ public void adjustVolume(IMediaController2 caller, int direction, int flags)
throws RuntimeException {
- final MediaSession2Impl session = getSession();
- final ControllerInfo controller = getControllerIfAble(
- caller, MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME);
- if (session == null || controller == null) {
- return;
- }
- session.getCallbackExecutor().execute(() -> {
- if (getControllerIfAble(caller, MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME) == null) {
- return;
- }
- // TODO(jaewan): Sanity check.
- Command command = new Command(
- session.getContext(), MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME);
- boolean accepted = session.getCallback().onCommandRequest(session.getInstance(),
- controller, command);
- if (!accepted) {
- // Don't run rejected command.
- if (DEBUG) {
- Log.d(TAG, "Command " + MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME + " from "
- + controller + " was rejected by " + session);
- }
- return;
- }
-
+ onCommand(caller, MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME, (session, controller) -> {
VolumeProvider2 volumeProvider = session.getVolumeProvider();
if (volumeProvider == null) {
// TODO(jaewan): Adjust local stream volume
@@ -400,30 +428,9 @@
}
@Override
- public void sendTransportControlCommand(IMediaSession2Callback caller,
+ public void sendTransportControlCommand(IMediaController2 caller,
int commandCode, Bundle args) throws RuntimeException {
- final MediaSession2Impl session = getSession();
- final ControllerInfo controller = getControllerIfAble(caller, commandCode);
- if (session == null || controller == null) {
- return;
- }
- session.getCallbackExecutor().execute(() -> {
- if (getControllerIfAble(caller, commandCode) == null) {
- return;
- }
- // TODO(jaewan): Sanity check.
- Command command = new Command(session.getContext(), commandCode);
- boolean accepted = session.getCallback().onCommandRequest(session.getInstance(),
- controller, command);
- if (!accepted) {
- // Don't run rejected command.
- if (DEBUG) {
- Log.d(TAG, "Command " + commandCode + " from "
- + controller + " was rejected by " + session);
- }
- return;
- }
-
+ onCommand(caller, commandCode, (session, controller) -> {
switch (commandCode) {
case MediaSession2.COMMAND_CODE_PLAYBACK_PLAY:
session.getInstance().play();
@@ -474,7 +481,7 @@
}
@Override
- public void sendCustomCommand(final IMediaSession2Callback caller, final Bundle commandBundle,
+ public void sendCustomCommand(final IMediaController2 caller, final Bundle commandBundle,
final Bundle args, final ResultReceiver receiver) {
final MediaSession2Impl session = getSession();
if (session == null) {
@@ -500,44 +507,23 @@
}
@Override
- public void prepareFromUri(final IMediaSession2Callback caller, final Uri uri,
+ public void prepareFromUri(final IMediaController2 caller, final Uri uri,
final Bundle extras) {
- final MediaSession2Impl session = getSession();
- final ControllerInfo controller = getControllerIfAble(
- caller, MediaSession2.COMMAND_CODE_PREPARE_FROM_URI);
- if (session == null || controller == null) {
- return;
- }
- if (uri == null) {
- Log.w(TAG, "prepareFromUri(): Ignoring null uri from " + controller);
- return;
- }
- session.getCallbackExecutor().execute(() -> {
- if (getControllerIfAble(
- caller, MediaSession2.COMMAND_CODE_PREPARE_FROM_URI) == null) {
+ onCommand(caller, MediaSession2.COMMAND_CODE_PREPARE_FROM_URI, (session, controller) -> {
+ if (uri == null) {
+ Log.w(TAG, "prepareFromUri(): Ignoring null uri from " + controller);
return;
}
- session.getCallback().onPrepareFromUri(session.getInstance(),
- controller, uri, extras);
+ session.getCallback().onPrepareFromUri(session.getInstance(), controller, uri, extras);
});
}
@Override
- public void prepareFromSearch(final IMediaSession2Callback caller, final String query,
+ public void prepareFromSearch(final IMediaController2 caller, final String query,
final Bundle extras) {
- final MediaSession2Impl session = getSession();
- final ControllerInfo controller = getControllerIfAble(
- caller, MediaSession2.COMMAND_CODE_PREPARE_FROM_SEARCH);
- if (session == null || controller == null) {
- return;
- }
- if (TextUtils.isEmpty(query)) {
- Log.w(TAG, "prepareFromSearch(): Ignoring empty query from " + controller);
- return;
- }
- session.getCallbackExecutor().execute(() -> {
- if (getControllerIfAble(
- caller, MediaSession2.COMMAND_CODE_PREPARE_FROM_SEARCH) == null) {
+ onCommand(caller, MediaSession2.COMMAND_CODE_PREPARE_FROM_SEARCH, (session, controller) -> {
+ if (TextUtils.isEmpty(query)) {
+ Log.w(TAG, "prepareFromSearch(): Ignoring empty query from " + controller);
return;
}
session.getCallback().onPrepareFromSearch(session.getInstance(),
@@ -546,21 +532,12 @@
}
@Override
- public void prepareFromMediaId(final IMediaSession2Callback caller, final String mediaId,
+ public void prepareFromMediaId(final IMediaController2 caller, final String mediaId,
final Bundle extras) {
- final MediaSession2Impl session = getSession();
- final ControllerInfo controller = getControllerIfAble(
- caller, MediaSession2.COMMAND_CODE_PREPARE_FROM_MEDIA_ID);
- if (session == null || controller == null) {
- return;
- }
- if (mediaId == null) {
- Log.w(TAG, "prepareFromMediaId(): Ignoring null mediaId from " + controller);
- return;
- }
- session.getCallbackExecutor().execute(() -> {
- if (getControllerIfAble(
- caller, MediaSession2.COMMAND_CODE_PREPARE_FROM_MEDIA_ID) == null) {
+ onCommand(caller, MediaSession2.COMMAND_CODE_PREPARE_FROM_MEDIA_ID,
+ (session, controller) -> {
+ if (mediaId == null) {
+ Log.w(TAG, "prepareFromMediaId(): Ignoring null mediaId from " + controller);
return;
}
session.getCallback().onPrepareFromMediaId(session.getInstance(),
@@ -569,21 +546,11 @@
}
@Override
- public void playFromUri(final IMediaSession2Callback caller, final Uri uri,
+ public void playFromUri(final IMediaController2 caller, final Uri uri,
final Bundle extras) {
- final MediaSession2Impl session = getSession();
- final ControllerInfo controller = getControllerIfAble(
- caller, MediaSession2.COMMAND_CODE_PLAY_FROM_URI);
- if (session == null || controller == null) {
- return;
- }
- if (uri == null) {
- Log.w(TAG, "playFromUri(): Ignoring null uri from " + controller);
- return;
- }
- session.getCallbackExecutor().execute(() -> {
- if (getControllerIfAble(
- caller, MediaSession2.COMMAND_CODE_PLAY_FROM_URI) == null) {
+ onCommand(caller, MediaSession2.COMMAND_CODE_PLAY_FROM_URI, (session, controller) -> {
+ if (uri == null) {
+ Log.w(TAG, "playFromUri(): Ignoring null uri from " + controller);
return;
}
session.getCallback().onPlayFromUri(session.getInstance(), controller, uri, extras);
@@ -591,21 +558,11 @@
}
@Override
- public void playFromSearch(final IMediaSession2Callback caller, final String query,
+ public void playFromSearch(final IMediaController2 caller, final String query,
final Bundle extras) {
- final MediaSession2Impl session = getSession();
- final ControllerInfo controller = getControllerIfAble(
- caller, MediaSession2.COMMAND_CODE_PLAY_FROM_SEARCH);
- if (session == null || controller == null) {
- return;
- }
- if (TextUtils.isEmpty(query)) {
- Log.w(TAG, "playFromSearch(): Ignoring empty query from " + controller);
- return;
- }
- session.getCallbackExecutor().execute(() -> {
- if (getControllerIfAble(
- caller, MediaSession2.COMMAND_CODE_PLAY_FROM_SEARCH) == null) {
+ onCommand(caller, MediaSession2.COMMAND_CODE_PLAY_FROM_SEARCH, (session, controller) -> {
+ if (TextUtils.isEmpty(query)) {
+ Log.w(TAG, "playFromSearch(): Ignoring empty query from " + controller);
return;
}
session.getCallback().onPlayFromSearch(session.getInstance(),
@@ -614,20 +571,11 @@
}
@Override
- public void playFromMediaId(final IMediaSession2Callback caller, final String mediaId,
+ public void playFromMediaId(final IMediaController2 caller, final String mediaId,
final Bundle extras) {
- final MediaSession2Impl session = getSession();
- final ControllerInfo controller = getControllerIfAble(
- caller, MediaSession2.COMMAND_CODE_PLAY_FROM_MEDIA_ID);
- if (session == null || controller == null) {
- return;
- }
- if (mediaId == null) {
- Log.w(TAG, "playFromMediaId(): Ignoring null mediaId from " + controller);
- return;
- }
- session.getCallbackExecutor().execute(() -> {
- if (session == null) {
+ onCommand(caller, MediaSession2.COMMAND_CODE_PLAY_FROM_MEDIA_ID, (session, controller) -> {
+ if (mediaId == null) {
+ Log.w(TAG, "playFromMediaId(): Ignoring null mediaId from " + controller);
return;
}
session.getCallback().onPlayFromMediaId(session.getInstance(),
@@ -636,47 +584,101 @@
}
@Override
- public void setRating(final IMediaSession2Callback caller, final String mediaId,
+ public void setRating(final IMediaController2 caller, final String mediaId,
final Bundle ratingBundle) {
- final MediaSession2Impl sessionImpl = getSession();
- final ControllerInfo controller = getControllerIfAble(caller);
- if (controller == null) {
- if (DEBUG) {
- Log.d(TAG, "Command from a controller that hasn't connected. Ignore");
+ // TODO(jaewan): Define COMMAND_CODE_SET_RATING
+ onCommand(caller, MediaSession2.COMMAND_CODE_SET_RATING, (session, controller) -> {
+ if (mediaId == null) {
+ Log.w(TAG, "setRating(): Ignoring null mediaId from " + controller);
+ return;
}
- return;
- }
- Rating2 rating = Rating2Impl.fromBundle(sessionImpl.getContext(), ratingBundle);
- if (rating == null) {
- Log.w(TAG, "setRating(): Ignore null rating");
- return;
- }
- sessionImpl.getCallbackExecutor().execute(() -> {
- final MediaSession2Impl session = mSession.get();
- if (session == null) {
+ if (ratingBundle == null) {
+ Log.w(TAG, "setRating(): Ignoring null ratingBundle from " + controller);
+ return;
+ }
+ Rating2 rating = Rating2Impl.fromBundle(session.getContext(), ratingBundle);
+ if (rating == null) {
+ if (ratingBundle == null) {
+ Log.w(TAG, "setRating(): Ignoring null rating from " + controller);
+ return;
+ }
return;
}
session.getCallback().onSetRating(session.getInstance(), controller, mediaId, rating);
});
}
+ @Override
+ public void setPlaylist(final IMediaController2 caller, final List<Bundle> playlist,
+ final Bundle metadata) {
+ onCommand(caller, MediaSession2.COMMAND_CODE_PLAYLIST_SET_LIST, (session, controller) -> {
+ if (playlist == null) {
+ Log.w(TAG, "setPlaylist(): Ignoring null playlist from " + controller);
+ return;
+ }
+ List<MediaItem2> list = new ArrayList<>();
+ for (int i = 0; i < playlist.size(); i++) {
+ // Recreates UUID in the playlist
+ MediaItem2 item = MediaItem2Impl.fromBundle(
+ session.getContext(), playlist.get(i), null);
+ if (item != null) {
+ list.add(item);
+ }
+ }
+ session.getInstance().setPlaylist(list,
+ MediaMetadata2.fromBundle(session.getContext(), metadata));
+ });
+ }
+
+ @Override
+ public void updatePlaylistMetadata(final IMediaController2 caller, final Bundle metadata) {
+ onCommand(caller, MediaSession2.COMMAND_CODE_PLAYLIST_SET_LIST_METADATA,
+ (session, controller) -> {
+ session.getInstance().updatePlaylistMetadata(
+ MediaMetadata2.fromBundle(session.getContext(), metadata));
+ });
+ }
+
+ @Override
+ public void addPlaylistItem(IMediaController2 caller, int index, Bundle mediaItem) {
+ onCommand(caller, MediaSession2.COMMAND_CODE_PLAYLIST_ADD_ITEM, (session, controller) -> {
+ // Resets the UUID from the incoming media id, so controller may reuse a media item
+ // multiple times for addPlaylistItem.
+ session.getInstance().addPlaylistItem(index,
+ MediaItem2Impl.fromBundle(session.getContext(), mediaItem, null));
+ });
+ }
+
+ @Override
+ public void removePlaylistItem(IMediaController2 caller, Bundle mediaItem) {
+ onCommand(caller, MediaSession2.COMMAND_CODE_PLAYLIST_REMOVE_ITEM,
+ (session, controller) -> {
+ MediaItem2 item = MediaItem2.fromBundle(session.getContext(), mediaItem);
+ List<MediaItem2> list = session.getInstance().getPlaylist();
+ // Trick to use the same reference for calls from the controller.
+ session.getInstance().removePlaylistItem(list.get(list.indexOf(item)));
+ });
+ }
+
+ @Override
+ public void replacePlaylistItem(IMediaController2 caller, int index, Bundle mediaItem) {
+ onCommand(caller, MediaSession2.COMMAND_CODE_PLAYLIST_REPLACE_ITEM,
+ (session, controller) -> {
+ // Resets the UUID from the incoming media id, so controller may reuse a media
+ // item multiple times for replacePlaylistItem.
+ session.getInstance().replacePlaylistItem(index,
+ MediaItem2.fromBundle(session.getContext(), mediaItem));
+ });
+ }
+
//////////////////////////////////////////////////////////////////////////////////////////////
// AIDL methods for LibrarySession overrides
//////////////////////////////////////////////////////////////////////////////////////////////
@Override
- public void getLibraryRoot(final IMediaSession2Callback caller, final Bundle rootHints)
+ public void getLibraryRoot(final IMediaController2 caller, final Bundle rootHints)
throws RuntimeException {
- final MediaLibrarySessionImpl session = getLibrarySession();
- final ControllerInfo controller = getControllerIfAble(
- caller, MediaSession2.COMMAND_CODE_BROWSER);
- if (session == null || controller == null) {
- return;
- }
- session.getCallbackExecutor().execute(() -> {
- if (getControllerIfAble(caller, MediaSession2.COMMAND_CODE_BROWSER) == null) {
- return;
- }
+ onBrowserCommand(caller, (session, controller) -> {
LibraryRoot root = session.getCallback().onGetLibraryRoot(session.getInstance(),
controller, rootHints);
try {
@@ -691,22 +693,13 @@
}
@Override
- public void getItem(final IMediaSession2Callback caller, final String mediaId)
+ public void getItem(final IMediaController2 caller, final String mediaId)
throws RuntimeException {
- if (mediaId == null) {
- if (DEBUG) {
- Log.d(TAG, "mediaId shouldn't be null");
- }
- return;
- }
- final MediaLibrarySessionImpl session = getLibrarySession();
- final ControllerInfo controller = getControllerIfAble(
- caller, MediaSession2.COMMAND_CODE_BROWSER);
- if (session == null || controller == null) {
- return;
- }
- session.getCallbackExecutor().execute(() -> {
- if (getControllerIfAble(caller, MediaSession2.COMMAND_CODE_BROWSER) == null) {
+ onBrowserCommand(caller, (session, controller) -> {
+ if (mediaId == null) {
+ if (DEBUG) {
+ Log.d(TAG, "mediaId shouldn't be null");
+ }
return;
}
MediaItem2 result = session.getCallback().onGetItem(session.getInstance(),
@@ -721,28 +714,19 @@
}
@Override
- public void getChildren(final IMediaSession2Callback caller, final String parentId,
+ public void getChildren(final IMediaController2 caller, final String parentId,
final int page, final int pageSize, final Bundle extras) throws RuntimeException {
- if (parentId == null) {
- if (DEBUG) {
- Log.d(TAG, "parentId shouldn't be null");
+ onBrowserCommand(caller, (session, controller) -> {
+ if (parentId == null) {
+ if (DEBUG) {
+ Log.d(TAG, "parentId shouldn't be null");
+ }
+ return;
}
- return;
- }
- if (page < 1 || pageSize < 1) {
- if (DEBUG) {
- Log.d(TAG, "Neither page nor pageSize should be less than 1");
- }
- return;
- }
- final MediaLibrarySessionImpl session = getLibrarySession();
- final ControllerInfo controller = getControllerIfAble(
- caller, MediaSession2.COMMAND_CODE_BROWSER);
- if (session == null || controller == null) {
- return;
- }
- session.getCallbackExecutor().execute(() -> {
- if (getControllerIfAble(caller, MediaSession2.COMMAND_CODE_BROWSER) == null) {
+ if (page < 1 || pageSize < 1) {
+ if (DEBUG) {
+ Log.d(TAG, "Neither page nor pageSize should be less than 1");
+ }
return;
}
List<MediaItem2> result = session.getCallback().onGetChildren(session.getInstance(),
@@ -769,19 +753,10 @@
}
@Override
- public void search(IMediaSession2Callback caller, String query, Bundle extras) {
- final MediaLibrarySessionImpl session = getLibrarySession();
- final ControllerInfo controller = getControllerIfAble(
- caller, MediaSession2.COMMAND_CODE_BROWSER);
- if (session == null || controller == null) {
- return;
- }
- if (TextUtils.isEmpty(query)) {
- Log.w(TAG, "search(): Ignoring empty query from " + controller);
- return;
- }
- session.getCallbackExecutor().execute(() -> {
- if (getControllerIfAble(caller, MediaSession2.COMMAND_CODE_BROWSER) == null) {
+ public void search(IMediaController2 caller, String query, Bundle extras) {
+ onBrowserCommand(caller, (session, controller) -> {
+ if (TextUtils.isEmpty(query)) {
+ Log.w(TAG, "search(): Ignoring empty query from " + controller);
return;
}
session.getCallback().onSearch(session.getInstance(), controller, query, extras);
@@ -789,25 +764,16 @@
}
@Override
- public void getSearchResult(final IMediaSession2Callback caller, final String query,
+ public void getSearchResult(final IMediaController2 caller, final String query,
final int page, final int pageSize, final Bundle extras) {
- final MediaLibrarySessionImpl session = getLibrarySession();
- final ControllerInfo controller = getControllerIfAble(
- caller, MediaSession2.COMMAND_CODE_BROWSER);
- if (session == null || controller == null) {
- return;
- }
- if (TextUtils.isEmpty(query)) {
- Log.w(TAG, "getSearchResult(): Ignoring empty query from " + controller);
- return;
- }
- if (page < 1 || pageSize < 1) {
- Log.w(TAG, "getSearchResult(): Ignoring negative page / pageSize."
- + " page=" + page + " pageSize=" + pageSize + " from " + controller);
- return;
- }
- session.getCallbackExecutor().execute(() -> {
- if (getControllerIfAble(caller, MediaSession2.COMMAND_CODE_BROWSER) == null) {
+ onBrowserCommand(caller, (session, controller) -> {
+ if (TextUtils.isEmpty(query)) {
+ Log.w(TAG, "getSearchResult(): Ignoring empty query from " + controller);
+ return;
+ }
+ if (page < 1 || pageSize < 1) {
+ Log.w(TAG, "getSearchResult(): Ignoring negative page / pageSize."
+ + " page=" + page + " pageSize=" + pageSize + " from " + controller);
return;
}
List<MediaItem2> result = session.getCallback().onGetSearchResult(session.getInstance(),
@@ -824,7 +790,6 @@
bundleList.add(item == null ? null : item.toBundle());
}
}
-
try {
caller.onGetSearchResultDone(query, page, pageSize, bundleList, extras);
} catch (RemoteException e) {
@@ -835,20 +800,11 @@
}
@Override
- public void subscribe(final IMediaSession2Callback caller, final String parentId,
+ public void subscribe(final IMediaController2 caller, final String parentId,
final Bundle option) {
- final MediaLibrarySessionImpl session = getLibrarySession();
- final ControllerInfo controller = getControllerIfAble(
- caller, MediaSession2.COMMAND_CODE_BROWSER);
- if (session == null || controller == null) {
- return;
- }
- if (parentId == null) {
- Log.w(TAG, "subscribe(): Ignoring null parentId from " + controller);
- return;
- }
- session.getCallbackExecutor().execute(() -> {
- if (getControllerIfAble(caller, MediaSession2.COMMAND_CODE_BROWSER) == null) {
+ onBrowserCommand(caller, (session, controller) -> {
+ if (parentId == null) {
+ Log.w(TAG, "subscribe(): Ignoring null parentId from " + controller);
return;
}
session.getCallback().onSubscribe(session.getInstance(),
@@ -865,19 +821,10 @@
}
@Override
- public void unsubscribe(final IMediaSession2Callback caller, final String parentId) {
- final MediaLibrarySessionImpl session = getLibrarySession();
- final ControllerInfo controller = getControllerIfAble(
- caller, MediaSession2.COMMAND_CODE_BROWSER);
- if (session == null || controller == null) {
- return;
- }
- if (parentId == null) {
- Log.w(TAG, "unsubscribe(): Ignoring null parentId from " + controller);
- return;
- }
- session.getCallbackExecutor().execute(() -> {
- if (getControllerIfAble(caller, MediaSession2.COMMAND_CODE_BROWSER) == null) {
+ public void unsubscribe(final IMediaController2 caller, final String parentId) {
+ onBrowserCommand(caller, (session, controller) -> {
+ if (parentId == null) {
+ Log.w(TAG, "unsubscribe(): Ignoring null parentId from " + controller);
return;
}
session.getCallback().onUnsubscribe(session.getInstance(), controller, parentId);
@@ -903,16 +850,63 @@
}
// Should be used without a lock to prevent potential deadlock.
- public void notifyPlaybackStateChangedNotLocked(PlaybackState2 state) {
+ public void notifyPlayerStateChangedNotLocked(int state) {
final List<ControllerInfo> list = getControllers();
for (int i = 0; i < list.size(); i++) {
- final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(list.get(i));
+ final IMediaController2 controllerBinder = getControllerBinderIfAble(list.get(i));
if (controllerBinder == null) {
return;
}
try {
- final Bundle bundle = state != null ? state.toBundle() : null;
- controllerBinder.onPlaybackStateChanged(bundle);
+ controllerBinder.onPlayerStateChanged(state);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Controller is gone", e);
+ // TODO(jaewan): What to do when the controller is gone?
+ }
+ }
+ }
+
+ public void notifyPositionChangedNotLocked(long eventTimeMs, long positionMs) {
+ final List<ControllerInfo> list = getControllers();
+ for (int i = 0; i < list.size(); i++) {
+ final IMediaController2 controllerBinder = getControllerBinderIfAble(list.get(i));
+ if (controllerBinder == null) {
+ return;
+ }
+ try {
+ controllerBinder.onPositionChanged(eventTimeMs, positionMs);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Controller is gone", e);
+ // TODO(jaewan): What to do when the controller is gone?
+ }
+ }
+ }
+
+ public void notifyPlaybackSpeedChangedNotLocked(float speed) {
+ final List<ControllerInfo> list = getControllers();
+ for (int i = 0; i < list.size(); i++) {
+ final IMediaController2 controllerBinder = getControllerBinderIfAble(list.get(i));
+ if (controllerBinder == null) {
+ return;
+ }
+ try {
+ controllerBinder.onPlaybackSpeedChanged(speed);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Controller is gone", e);
+ // TODO(jaewan): What to do when the controller is gone?
+ }
+ }
+ }
+
+ public void notifyBufferedPositionChangedNotLocked(long bufferedPositionMs) {
+ final List<ControllerInfo> list = getControllers();
+ for (int i = 0; i < list.size(); i++) {
+ final IMediaController2 controllerBinder = getControllerBinderIfAble(list.get(i));
+ if (controllerBinder == null) {
+ return;
+ }
+ try {
+ controllerBinder.onBufferedPositionChanged(bufferedPositionMs);
} catch (RemoteException e) {
Log.w(TAG, "Controller is gone", e);
// TODO(jaewan): What to do when the controller is gone?
@@ -921,7 +915,7 @@
}
public void notifyCustomLayoutNotLocked(ControllerInfo controller, List<CommandButton> layout) {
- final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(controller);
+ final IMediaController2 controllerBinder = getControllerBinderIfAble(controller);
if (controllerBinder == null) {
return;
}
@@ -940,23 +934,57 @@
}
}
- public void notifyPlaylistChanged(List<MediaItem2> playlist) {
- final List<Bundle> bundleList = new ArrayList<>();
- for (int i = 0; i < playlist.size(); i++) {
- if (playlist.get(i) != null) {
- Bundle bundle = playlist.get(i).toBundle();
- if (bundle != null) {
- bundleList.add(bundle);
+ public void notifyPlaylistChangedNotLocked(List<MediaItem2> playlist, MediaMetadata2 metadata) {
+ final List<Bundle> bundleList;
+ if (playlist != null) {
+ bundleList = new ArrayList<>();
+ for (int i = 0; i < playlist.size(); i++) {
+ if (playlist.get(i) != null) {
+ Bundle bundle = playlist.get(i).toBundle();
+ if (bundle != null) {
+ bundleList.add(bundle);
+ }
}
}
+ } else {
+ bundleList = null;
}
+ final Bundle metadataBundle = (metadata == null) ? null : metadata.toBundle();
final List<ControllerInfo> list = getControllers();
for (int i = 0; i < list.size(); i++) {
- final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(
+ final IMediaController2 controllerBinder = getControllerBinderIfAble(
list.get(i), MediaSession2.COMMAND_CODE_PLAYLIST_GET_LIST);
if (controllerBinder != null) {
try {
- controllerBinder.onPlaylistChanged(bundleList);
+ controllerBinder.onPlaylistChanged(bundleList, metadataBundle);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Controller is gone", e);
+ // TODO(jaewan): What to do when the controller is gone?
+ }
+ } else {
+ final IMediaController2 binder = getControllerBinderIfAble(
+ list.get(i), MediaSession2.COMMAND_CODE_PLAYLIST_GET_LIST_METADATA);
+ if (binder != null) {
+ try {
+ binder.onPlaylistMetadataChanged(metadataBundle);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Controller is gone", e);
+ // TODO(jaewan): What to do when the controller is gone?
+ }
+ }
+ }
+ }
+ }
+
+ public void notifyPlaylistMetadataChangedNotLocked(MediaMetadata2 metadata) {
+ final Bundle metadataBundle = (metadata == null) ? null : metadata.toBundle();
+ final List<ControllerInfo> list = getControllers();
+ for (int i = 0; i < list.size(); i++) {
+ final IMediaController2 controllerBinder = getControllerBinderIfAble(
+ list.get(i), MediaSession2.COMMAND_CODE_PLAYLIST_GET_LIST_METADATA);
+ if (controllerBinder != null) {
+ try {
+ controllerBinder.onPlaylistMetadataChanged(metadataBundle);
} catch (RemoteException e) {
Log.w(TAG, "Controller is gone", e);
// TODO(jaewan): What to do when the controller is gone?
@@ -968,7 +996,7 @@
public void notifyPlaylistParamsChanged(MediaSession2.PlaylistParams params) {
final List<ControllerInfo> list = getControllers();
for (int i = 0; i < list.size(); i++) {
- final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(list.get(i));
+ final IMediaController2 controllerBinder = getControllerBinderIfAble(list.get(i));
if (controllerBinder == null) {
return;
}
@@ -984,7 +1012,7 @@
public void notifyPlaybackInfoChanged(MediaController2.PlaybackInfo playbackInfo) {
final List<ControllerInfo> list = getControllers();
for (int i = 0; i < list.size(); i++) {
- final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(list.get(i));
+ final IMediaController2 controllerBinder = getControllerBinderIfAble(list.get(i));
if (controllerBinder == null) {
return;
}
@@ -1002,7 +1030,7 @@
synchronized (mLock) {
mAllowedCommandGroupMap.put(controller, commands);
}
- final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(controller);
+ final IMediaController2 controllerBinder = getControllerBinderIfAble(controller);
if (controllerBinder == null) {
return;
}
@@ -1038,7 +1066,7 @@
private void sendCustomCommandInternal(ControllerInfo controller, Command command, Bundle args,
ResultReceiver receiver) {
- final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(controller);
+ final IMediaController2 controllerBinder = getControllerBinderIfAble(controller);
if (controllerBinder == null) {
return;
}
@@ -1057,7 +1085,7 @@
public void notifySearchResultChanged(ControllerInfo controller, String query, int itemCount,
Bundle extras) {
- final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(controller);
+ final IMediaController2 controllerBinder = getControllerBinderIfAble(controller);
if (controllerBinder == null) {
return;
}
@@ -1091,7 +1119,7 @@
return;
}
}
- final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(controller);
+ final IMediaController2 controllerBinder = getControllerBinderIfAble(controller);
if (controller == null) {
return;
}
@@ -1101,4 +1129,18 @@
// TODO(jaewan): Handle controller removed?
}
}
+
+ //////////////////////////////////////////////////////////////////////////////////////////////
+ // Misc
+ //////////////////////////////////////////////////////////////////////////////////////////////
+
+ @FunctionalInterface
+ private interface SessionRunnable {
+ void run(final MediaSession2Impl session, final ControllerInfo controller);
+ }
+
+ @FunctionalInterface
+ private interface LibrarySessionRunnable {
+ void run(final MediaLibrarySessionImpl session, final ControllerInfo controller);
+ }
}
diff --git a/packages/MediaComponents/src/com/android/media/MediaSessionService2Impl.java b/packages/MediaComponents/src/com/android/media/MediaSessionService2Impl.java
index 6bd2b2a..a0123b5 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSessionService2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSessionService2Impl.java
@@ -27,10 +27,8 @@
import android.media.MediaSession2;
import android.media.MediaSessionService2;
import android.media.MediaSessionService2.MediaNotification;
-import android.media.PlaybackState2;
import android.media.SessionToken2;
import android.media.SessionToken2.TokenType;
-import android.media.session.PlaybackState;
import android.media.update.MediaSessionService2Provider;
import android.os.IBinder;
import android.support.annotation.GuardedBy;
@@ -109,13 +107,13 @@
return null;
}
- private void updateNotification(PlaybackState2 state) {
+ private void updateNotification(int playerState) {
MediaNotification mediaNotification = mInstance.onUpdateNotification();
if (mediaNotification == null) {
return;
}
- switch((int) state.getState()) {
- case PlaybackState.STATE_PLAYING:
+ switch(playerState) {
+ case MediaPlayerBase.PLAYER_STATE_PLAYING:
if (!mIsRunningForeground) {
mIsRunningForeground = true;
mInstance.startForegroundService(mStartSelfIntent);
@@ -124,7 +122,8 @@
return;
}
break;
- case PlaybackState.STATE_STOPPED:
+ case MediaPlayerBase.PLAYER_STATE_IDLE:
+ case MediaPlayerBase.PLAYER_STATE_ERROR:
if (mIsRunningForeground) {
mIsRunningForeground = false;
mInstance.stopForeground(true);
@@ -142,15 +141,6 @@
// TODO: Implement this
return;
}
- // TODO: Uncomment or remove
- //public void onPlaybackStateChanged(PlaybackState2 state) {
- // if (state == null) {
- // Log.w(TAG, "Ignoring null playback state");
- // return;
- // }
- // MediaSession2Impl impl = (MediaSession2Impl) mSession.getProvider();
- // updateNotification(impl.getInstance().getPlaybackState());
- //}
}
public static class MediaNotificationImpl implements MediaNotificationProvider {
diff --git a/packages/MediaComponents/src/com/android/media/PlaybackState2Impl.java b/packages/MediaComponents/src/com/android/media/PlaybackState2Impl.java
deleted file mode 100644
index ee8d6d7..0000000
--- a/packages/MediaComponents/src/com/android/media/PlaybackState2Impl.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright 2018 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.media;
-
-import android.content.Context;
-import android.media.PlaybackState2;
-import android.media.update.PlaybackState2Provider;
-import android.os.Bundle;
-
-public final class PlaybackState2Impl implements PlaybackState2Provider {
- /**
- * Keys used for converting a PlaybackState2 to a bundle object and vice versa.
- */
- private static final String KEY_STATE = "android.media.playbackstate2.state";
- private static final String KEY_POSITION = "android.media.playbackstate2.position";
- private static final String KEY_BUFFERED_POSITION =
- "android.media.playbackstate2.buffered_position";
- private static final String KEY_SPEED = "android.media.playbackstate2.speed";
- private static final String KEY_UPDATE_TIME = "android.media.playbackstate2.update_time";
- private static final String KEY_ACTIVE_ITEM_ID = "android.media.playbackstate2.active_item_id";
-
- private final Context mContext;
- private final PlaybackState2 mInstance;
- private final int mState;
- private final long mPosition;
- private final long mUpdateTime;
- private final float mSpeed;
- private final long mBufferedPosition;
- private final long mActiveItemId;
-
- public PlaybackState2Impl(Context context, PlaybackState2 instance, int state, long position,
- long updateTime, float speed, long bufferedPosition, long activeItemId) {
- mContext = context;
- mInstance = instance;
- mState = state;
- mPosition = position;
- mSpeed = speed;
- mUpdateTime = updateTime;
- mBufferedPosition = bufferedPosition;
- mActiveItemId = activeItemId;
- }
-
- @Override
- public String toString_impl() {
- StringBuilder bob = new StringBuilder("PlaybackState {");
- bob.append("state=").append(mState);
- bob.append(", position=").append(mPosition);
- bob.append(", buffered position=").append(mBufferedPosition);
- bob.append(", speed=").append(mSpeed);
- bob.append(", updated=").append(mUpdateTime);
- bob.append(", active item id=").append(mActiveItemId);
- bob.append("}");
- return bob.toString();
- }
-
- @Override
- public int getState_impl() {
- return mState;
- }
-
- @Override
- public long getPosition_impl() {
- return mPosition;
- }
-
- @Override
- public long getBufferedPosition_impl() {
- return mBufferedPosition;
- }
-
- @Override
- public float getPlaybackSpeed_impl() {
- return mSpeed;
- }
-
- @Override
- public long getLastPositionUpdateTime_impl() {
- return mUpdateTime;
- }
-
- @Override
- public long getCurrentPlaylistItemIndex_impl() {
- return mActiveItemId;
- }
-
- @Override
- public Bundle toBundle_impl() {
- Bundle bundle = new Bundle();
- bundle.putInt(KEY_STATE, mState);
- bundle.putLong(KEY_POSITION, mPosition);
- bundle.putLong(KEY_UPDATE_TIME, mUpdateTime);
- bundle.putFloat(KEY_SPEED, mSpeed);
- bundle.putLong(KEY_BUFFERED_POSITION, mBufferedPosition);
- bundle.putLong(KEY_ACTIVE_ITEM_ID, mActiveItemId);
- return bundle;
- }
-
- public static PlaybackState2 fromBundle(Context context, Bundle bundle) {
- if (bundle == null) {
- return null;
- }
- if (!bundle.containsKey(KEY_STATE)
- || !bundle.containsKey(KEY_POSITION)
- || !bundle.containsKey(KEY_UPDATE_TIME)
- || !bundle.containsKey(KEY_SPEED)
- || !bundle.containsKey(KEY_BUFFERED_POSITION)
- || !bundle.containsKey(KEY_ACTIVE_ITEM_ID)) {
- return null;
- }
- return new PlaybackState2(context,
- bundle.getInt(KEY_STATE),
- bundle.getLong(KEY_POSITION),
- bundle.getLong(KEY_UPDATE_TIME),
- bundle.getFloat(KEY_SPEED),
- bundle.getLong(KEY_BUFFERED_POSITION),
- bundle.getLong(KEY_ACTIVE_ITEM_ID));
- }
-}
\ No newline at end of file
diff --git a/packages/MediaComponents/src/com/android/media/update/ApiFactory.java b/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
index 07b8788..7f225de 100644
--- a/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
+++ b/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
@@ -20,7 +20,6 @@
import android.content.Context;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
-import android.media.DataSourceDesc;
import android.media.MediaBrowser2;
import android.media.MediaBrowser2.BrowserCallback;
import android.media.MediaController2;
@@ -31,7 +30,6 @@
import android.media.MediaLibraryService2.MediaLibrarySession;
import android.media.MediaLibraryService2.MediaLibrarySession.MediaLibrarySessionCallback;
import android.media.MediaMetadata2;
-import android.media.MediaPlayerBase;
import android.media.MediaPlaylistAgent;
import android.media.MediaSession2;
import android.media.MediaSession2.Command;
@@ -41,7 +39,6 @@
import android.media.MediaSession2.SessionCallback;
import android.media.MediaSessionService2;
import android.media.MediaSessionService2.MediaNotification;
-import android.media.PlaybackState2;
import android.media.Rating2;
import android.media.SessionToken2;
import android.media.VolumeProvider2;
@@ -58,7 +55,6 @@
import android.media.update.MediaSession2Provider.PlaylistParamsProvider;
import android.media.update.MediaSessionService2Provider;
import android.media.update.MediaSessionService2Provider.MediaNotificationProvider;
-import android.media.update.PlaybackState2Provider;
import android.media.update.SessionToken2Provider;
import android.media.update.StaticProvider;
import android.media.update.VideoView2Provider;
@@ -71,7 +67,7 @@
import android.widget.MediaControlView2;
import android.widget.VideoView2;
-import com.android.media.IMediaSession2Callback;
+import com.android.media.IMediaController2;
import com.android.media.MediaBrowser2Impl;
import com.android.media.MediaController2Impl;
import com.android.media.MediaItem2Impl;
@@ -82,7 +78,6 @@
import com.android.media.MediaSession2Impl;
import com.android.media.MediaSession2Impl.PlaylistParamsImpl;
import com.android.media.MediaSessionService2Impl;
-import com.android.media.PlaybackState2Impl;
import com.android.media.Rating2Impl;
import com.android.media.SessionToken2Impl;
import com.android.media.VolumeProvider2Impl;
@@ -142,7 +137,7 @@
Context context, ControllerInfo instance, int uid, int pid, String packageName,
IInterface callback) {
return new MediaSession2Impl.ControllerInfoImpl(context,
- instance, uid, pid, packageName, (IMediaSession2Callback) callback);
+ instance, uid, pid, packageName, (IMediaController2) callback);
}
@Override
@@ -293,19 +288,6 @@
}
@Override
- public PlaybackState2Provider createPlaybackState2(Context context, PlaybackState2 instance,
- int state, long position, long updateTime, float speed, long bufferedPosition,
- long activeItemId) {
- return new PlaybackState2Impl(context, instance, state, position, updateTime, speed,
- bufferedPosition, activeItemId);
- }
-
- @Override
- public PlaybackState2 fromBundle_PlaybackState2(Context context, Bundle bundle) {
- return PlaybackState2Impl.fromBundle(context, bundle);
- }
-
- @Override
public MediaPlaylistAgentProvider createMediaPlaylistAgent(Context context,
MediaPlaylistAgent instance) {
return new MediaPlaylistAgentImpl(context, instance);
diff --git a/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java b/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
index 69febc2..16707c6 100644
--- a/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
+++ b/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
@@ -52,6 +52,7 @@
import com.android.support.mediarouter.media.MediaRouter;
import com.android.support.mediarouter.media.MediaRouteSelector;
+import java.util.Arrays;
import java.util.ArrayList;
import java.util.Formatter;
import java.util.List;
@@ -69,19 +70,37 @@
static final String KEY_VIDEO_TRACK_COUNT = "VideoTrackCount";
static final String KEY_AUDIO_TRACK_COUNT = "AudioTrackCount";
static final String KEY_SUBTITLE_TRACK_COUNT = "SubtitleTrackCount";
+ static final String KEY_PLAYBACK_SPEED = "PlaybackSpeed";
+ static final String KEY_SELECTED_AUDIO_INDEX = "SelectedAudioIndex";
+ static final String KEY_SELECTED_SUBTITLE_INDEX = "SelectedSubtitleIndex";
static final String EVENT_UPDATE_TRACK_STATUS = "UpdateTrackStatus";
// TODO: Remove this once integrating with MediaSession2 & MediaMetadata2
static final String KEY_STATE_IS_ADVERTISEMENT = "MediaTypeAdvertisement";
static final String EVENT_UPDATE_MEDIA_TYPE_STATUS = "UpdateMediaTypeStatus";
- // String for receiving command to show subtitle from MediaSession.
+ // String for sending command to show subtitle to MediaSession.
static final String COMMAND_SHOW_SUBTITLE = "showSubtitle";
- // String for receiving command to hide subtitle from MediaSession.
+ // String for sending command to hide subtitle to MediaSession.
static final String COMMAND_HIDE_SUBTITLE = "hideSubtitle";
// TODO: remove once the implementation is revised
public static final String COMMAND_SET_FULLSCREEN = "setFullscreen";
+ // String for sending command to select audio track to MediaSession.
+ static final String COMMAND_SELECT_AUDIO_TRACK = "SelectTrack";
+ // String for sending command to set playback speed to MediaSession.
+ static final String COMMAND_SET_PLAYBACK_SPEED = "SetPlaybackSpeed";
+ // String for sending command to mute audio to MediaSession.
+ static final String COMMAND_MUTE= "Mute";
+ // String for sending command to unmute audio to MediaSession.
+ static final String COMMAND_UNMUTE = "Unmute";
+ private static final int SETTINGS_MODE_AUDIO_TRACK = 0;
+ private static final int SETTINGS_MODE_PLAYBACK_SPEED = 1;
+ private static final int SETTINGS_MODE_HELP = 2;
+ private static final int SETTINGS_MODE_SUBTITLE_TRACK = 3;
+ private static final int SETTINGS_MODE_VIDEO_QUALITY = 4;
+ private static final int SETTINGS_MODE_MAIN = 5;
+ private static final int PLAYBACK_SPEED_1x_INDEX = 3;
private static final int MAX_PROGRESS = 1000;
private static final int DEFAULT_PROGRESS_UPDATE_TIME_MS = 1000;
private static final int REWIND_TIME_MS = 10000;
@@ -100,6 +119,7 @@
private TextView mTitleView;
private TextView mAdSkipView, mAdRemainingView;
private View mAdExternalLink;
+ private View mTitleBar;
private View mRoot;
private int mDuration;
private int mPrevState;
@@ -107,6 +127,13 @@
private int mVideoTrackCount;
private int mAudioTrackCount;
private int mSubtitleTrackCount;
+ private int mSettingsMode;
+ private int mSelectedSubtitleTrackIndex;
+ private int mSelectedAudioTrackIndex;
+ private int mSelectedVideoQualityIndex;
+ private int mSelectedSpeedIndex;
+ private int mSettingsItemHeight;
+ private int mSettingsWindowMargin;
private long mPlaybackActions;
private boolean mDragging;
private boolean mIsFullScreen;
@@ -115,11 +142,13 @@
private boolean mSubtitleIsEnabled;
private boolean mSeekAvailable;
private boolean mIsAdvertisement;
+ private boolean mIsMute;
private ImageButton mPlayPauseButton;
private ImageButton mFfwdButton;
private ImageButton mRewButton;
private ImageButton mNextButton;
private ImageButton mPrevButton;
+ private ImageButton mBackButton;
private ViewGroup mBasicControls;
private ImageButton mSubtitleButton;
@@ -130,12 +159,13 @@
private ViewGroup mCustomButtons;
private ImageButton mOverflowButtonLeft;
private ImageButton mMuteButton;
- private ImageButton mAspectRationButton;
+ private ImageButton mVideoQualityButton;
private ImageButton mSettingsButton;
private ListView mSettingsListView;
private PopupWindow mSettingsWindow;
private SettingsAdapter mSettingsAdapter;
+ private SubSettingsAdapter mSubSettingsAdapter;
private List<String> mSettingsMainTextsList;
private List<String> mSettingsSubTextsList;
@@ -143,7 +173,8 @@
private List<String> mSubtitleDescriptionsList;
private List<String> mAudioTrackList;
private List<String> mVideoQualityList;
- private List<String> mPlaybackSpeedTextIdsList;
+ private List<String> mPlaybackSpeedTextList;
+ private List<Float> mPlaybackSpeedList;
private CharSequence mPlayDescription;
private CharSequence mPauseDescription;
@@ -166,7 +197,6 @@
mResources = ApiHelper.getLibResources();
// Inflate MediaControlView2 from XML
mRoot = makeControllerView();
- mRoot.addOnLayoutChangeListener(mTitleBarLayoutChangeListener);
mInstance.addView(mRoot);
}
@@ -217,15 +247,11 @@
}
break;
case MediaControlView2.BUTTON_NEXT:
- // TODO: this button is not visible unless its listener is manually set. Should this
- // function still be provided?
if (mNextButton != null) {
mNextButton.setVisibility(visibility);
}
break;
case MediaControlView2.BUTTON_PREV:
- // TODO: this button is not visible unless its listener is manually set. Should this
- // function still be provided?
if (mPrevButton != null) {
mPrevButton.setVisibility(visibility);
}
@@ -250,11 +276,6 @@
mMuteButton.setVisibility(visibility);
}
break;
- case MediaControlView2.BUTTON_ASPECT_RATIO:
- if (mAspectRationButton != null) {
- mAspectRationButton.setVisibility(visibility);
- }
- break;
case MediaControlView2.BUTTON_SETTINGS:
if (mSettingsButton != null) {
mSettingsButton.setVisibility(visibility);
@@ -363,7 +384,8 @@
}
mPlaybackState = mController.getPlaybackState();
if (mPlaybackState != null) {
- return (int) (mPlaybackState.getBufferedPosition() * 100) / mDuration;
+ long bufferedPos = mPlaybackState.getBufferedPosition();
+ return (bufferedPos == -1) ? -1 : (int) (bufferedPos * 100 / mDuration);
}
return 0;
}
@@ -415,20 +437,14 @@
if (mPlayPauseButton != null) {
mPlayPauseButton.requestFocus();
mPlayPauseButton.setOnClickListener(mPlayPauseListener);
- mPlayPauseButton.setColorFilter(R.color.gray);
- mPlayPauseButton.setEnabled(false);
}
mFfwdButton = v.findViewById(R.id.ffwd);
if (mFfwdButton != null) {
mFfwdButton.setOnClickListener(mFfwdListener);
- mFfwdButton.setColorFilter(R.color.gray);
- mFfwdButton.setEnabled(false);
}
mRewButton = v.findViewById(R.id.rew);
if (mRewButton != null) {
mRewButton.setOnClickListener(mRewListener);
- mRewButton.setColorFilter(R.color.gray);
- mRewButton.setEnabled(false);
}
mNextButton = v.findViewById(R.id.next);
if (mNextButton != null) {
@@ -438,13 +454,15 @@
if (mPrevButton != null) {
mPrevButton.setOnClickListener(mPrevListener);
}
+ mBackButton = v.findViewById(R.id.back);
+ if (mBackButton != null) {
+ mBackButton.setOnClickListener(mBackListener);
+ }
mBasicControls = v.findViewById(R.id.basic_controls);
mSubtitleButton = v.findViewById(R.id.subtitle);
if (mSubtitleButton != null) {
mSubtitleButton.setOnClickListener(mSubtitleListener);
- mSubtitleButton.setColorFilter(R.color.gray);
- mSubtitleButton.setEnabled(false);
}
mFullScreenButton = v.findViewById(R.id.fullscreen);
if (mFullScreenButton != null) {
@@ -464,21 +482,33 @@
mOverflowButtonLeft.setOnClickListener(mOverflowLeftListener);
}
mMuteButton = v.findViewById(R.id.mute);
- mAspectRationButton = v.findViewById(R.id.aspect_ratio);
+ if (mMuteButton != null) {
+ mMuteButton.setOnClickListener(mMuteButtonListener);
+ }
mSettingsButton = v.findViewById(R.id.settings);
if (mSettingsButton != null) {
mSettingsButton.setOnClickListener(mSettingsButtonListener);
}
+ mVideoQualityButton = v.findViewById(R.id.video_quality);
+ if (mVideoQualityButton != null) {
+ mVideoQualityButton.setOnClickListener(mVideoQualityListener);
+ }
mProgress = v.findViewById(R.id.mediacontroller_progress);
if (mProgress != null) {
if (mProgress instanceof SeekBar) {
SeekBar seeker = (SeekBar) mProgress;
seeker.setOnSeekBarChangeListener(mSeekListener);
+ seeker.setProgressDrawable(mResources.getDrawable(R.drawable.custom_progress));
+ seeker.setThumb(mResources.getDrawable(R.drawable.custom_progress_thumb));
}
mProgress.setMax(MAX_PROGRESS);
}
+ mTitleBar = v.findViewById(R.id.title_bar);
+ if (mTitleBar != null) {
+ mTitleBar.addOnLayoutChangeListener(mTitleBarLayoutChangeListener);
+ }
mTitleView = v.findViewById(R.id.title_text);
mEndTime = v.findViewById(R.id.time);
@@ -494,10 +524,16 @@
mSettingsListView = (ListView) ApiHelper.inflateLibLayout(mInstance.getContext(),
R.layout.settings_list);
mSettingsAdapter = new SettingsAdapter(mSettingsMainTextsList, mSettingsSubTextsList,
- mSettingsIconIdsList, false);
+ mSettingsIconIdsList);
+ mSubSettingsAdapter = new SubSettingsAdapter(null, 0);
mSettingsListView.setAdapter(mSettingsAdapter);
mSettingsListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
mSettingsListView.setOnItemClickListener(mSettingsItemClickListener);
+
+ mSettingsItemHeight = mResources.getDimensionPixelSize(
+ R.dimen.MediaControlView2_settings_height);
+ mSettingsWindowMargin = (-1) * mResources.getDimensionPixelSize(
+ R.dimen.MediaControlView2_settings_offset);
int width = mResources.getDimensionPixelSize(R.dimen.MediaControlView2_settings_width);
mSettingsWindow = new PopupWindow(mSettingsListView, width,
ViewGroup.LayoutParams.WRAP_CONTENT, true);
@@ -574,7 +610,13 @@
}
if (mProgress != null && currentPosition != mDuration) {
mProgress.setProgress(positionOnProgressBar);
- mProgress.setSecondaryProgress(getBufferPercentage() * 10);
+ // If the media is a local file, there is no need to set a buffer, so set secondary
+ // progress to maximum.
+ if (getBufferPercentage() < 0) {
+ mProgress.setSecondaryProgress(MAX_PROGRESS);
+ } else {
+ mProgress.setSecondaryProgress(getBufferPercentage() * 10);
+ }
}
if (mEndTime != null) {
@@ -744,20 +786,30 @@
}
};
+ private final View.OnClickListener mBackListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // TODO: implement
+ }
+ };
+
private final View.OnClickListener mSubtitleListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
- if (!mSubtitleIsEnabled) {
- mSubtitleButton.setImageDrawable(
- mResources.getDrawable(R.drawable.ic_media_subtitle_enabled, null));
- mController.sendCommand(MediaControlView2Impl.COMMAND_SHOW_SUBTITLE, null, null);
- mSubtitleIsEnabled = true;
- } else {
- mSubtitleButton.setImageDrawable(
- mResources.getDrawable(R.drawable.ic_media_subtitle_disabled, null));
- mController.sendCommand(MediaControlView2Impl.COMMAND_HIDE_SUBTITLE, null, null);
- mSubtitleIsEnabled = false;
- }
+ mSettingsMode = SETTINGS_MODE_SUBTITLE_TRACK;
+ mSubSettingsAdapter.setTexts(mSubtitleDescriptionsList);
+ mSubSettingsAdapter.setCheckPosition(mSelectedSubtitleTrackIndex);
+ displaySettingsWindow(mSubSettingsAdapter);
+ }
+ };
+
+ private final View.OnClickListener mVideoQualityListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mSettingsMode = SETTINGS_MODE_VIDEO_QUALITY;
+ mSubSettingsAdapter.setTexts(mVideoQualityList);
+ mSubSettingsAdapter.setCheckPosition(mSelectedVideoQualityIndex);
+ displaySettingsWindow(mSubSettingsAdapter);
}
};
@@ -797,20 +849,29 @@
}
};
+ private final View.OnClickListener mMuteButtonListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (!mIsMute) {
+ mMuteButton.setImageDrawable(
+ mResources.getDrawable(R.drawable.ic_mute, null));
+ mIsMute = true;
+ mController.sendCommand(COMMAND_MUTE, null, null);
+ } else {
+ mMuteButton.setImageDrawable(
+ mResources.getDrawable(R.drawable.ic_unmute, null));
+ mIsMute = false;
+ mController.sendCommand(COMMAND_UNMUTE, null, null);
+ }
+ }
+ };
+
private final View.OnClickListener mSettingsButtonListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
- mSettingsAdapter = new SettingsAdapter(mSettingsMainTextsList,
- mSettingsSubTextsList, mSettingsIconIdsList, false);
- mSettingsListView.setAdapter(mSettingsAdapter);
- int itemHeight = mResources.getDimensionPixelSize(
- R.dimen.MediaControlView2_settings_height);
- int totalHeight = mSettingsAdapter.getCount() * itemHeight;
- int margin = (-1) * mResources.getDimensionPixelSize(
- R.dimen.MediaControlView2_settings_offset);
- mSettingsWindow.dismiss();
- mSettingsWindow.showAsDropDown(mInstance, margin, margin - totalHeight,
- Gravity.BOTTOM | Gravity.RIGHT);
+ mSettingsMode = SETTINGS_MODE_MAIN;
+ mSettingsAdapter.setSubTexts(mSettingsSubTextsList);
+ displaySettingsWindow(mSettingsAdapter);
}
};
@@ -818,56 +879,78 @@
= new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- switch (position) {
- // change to identifiers
- case 0:
- // TODO: add additional subtitle track details
- mSubtitleDescriptionsList = new ArrayList<String>();
- mSubtitleDescriptionsList.add(mResources.getString(
- R.string.MediaControlView2_subtitle_off_text));
- for (int i = 0; i < mSubtitleTrackCount; i++) {
- String track = mResources.getString(
- R.string.MediaControlView2_subtitle_track_number_text, i + 1);
- mSubtitleDescriptionsList.add(track);
+ switch (mSettingsMode) {
+ case SETTINGS_MODE_MAIN:
+ if (position == SETTINGS_MODE_AUDIO_TRACK) {
+ mSubSettingsAdapter.setTexts(mAudioTrackList);
+ mSubSettingsAdapter.setCheckPosition(mSelectedAudioTrackIndex);
+ mSettingsMode = SETTINGS_MODE_AUDIO_TRACK;
+ } else if (position == SETTINGS_MODE_PLAYBACK_SPEED) {
+ mSubSettingsAdapter.setTexts(mPlaybackSpeedTextList);
+ mSubSettingsAdapter.setCheckPosition(mSelectedSpeedIndex);
+ mSettingsMode = SETTINGS_MODE_PLAYBACK_SPEED;
+ } else if (position == SETTINGS_MODE_HELP) {
+ // TODO: implement this.
+ mSettingsWindow.dismiss();
+ return;
}
- mSettingsAdapter = new SettingsAdapter(mSubtitleDescriptionsList, null,
- null, true);
+ displaySettingsWindow(mSubSettingsAdapter);
break;
- case 1:
- // TODO: add additional audio track details
- mAudioTrackList = new ArrayList<String>();
- mAudioTrackList.add(mResources.getString(
- R.string.MediaControlView2_audio_track_none_text));
- for (int i = 0; i < mAudioTrackCount; i++) {
- String track = mResources.getString(
- R.string.MediaControlView2_audio_track_number_text, i + 1);
- mAudioTrackList.add(track);
+ case SETTINGS_MODE_AUDIO_TRACK:
+ if (position != mSelectedAudioTrackIndex) {
+ mSelectedAudioTrackIndex = position;
+ if (mAudioTrackCount > 0) {
+ Bundle extra = new Bundle();
+ extra.putInt(KEY_SELECTED_AUDIO_INDEX, position);
+ mController.sendCommand(COMMAND_SELECT_AUDIO_TRACK, extra, null);
+ }
+ mSettingsSubTextsList.set(SETTINGS_MODE_AUDIO_TRACK,
+ mSubSettingsAdapter.getMainText(position));
}
- mSettingsAdapter = new SettingsAdapter(mAudioTrackList, null,
- null, true);
+ mSettingsWindow.dismiss();
break;
- case 2:
- // TODO: add support for multiple quality video tracks
- mSettingsAdapter = new SettingsAdapter(mVideoQualityList, null,
- null, true);
+ case SETTINGS_MODE_PLAYBACK_SPEED:
+ if (position != mSelectedSpeedIndex) {
+ mSelectedSpeedIndex = position;
+ Bundle extra = new Bundle();
+ extra.putFloat(KEY_PLAYBACK_SPEED, mPlaybackSpeedList.get(position));
+ mController.sendCommand(COMMAND_SET_PLAYBACK_SPEED, extra, null);
+ mSettingsSubTextsList.set(SETTINGS_MODE_PLAYBACK_SPEED,
+ mSubSettingsAdapter.getMainText(position));
+ }
+ mSettingsWindow.dismiss();
break;
- case 3:
- // TODO: implement code to reflect change in speed.
- mSettingsAdapter = new SettingsAdapter(mPlaybackSpeedTextIdsList, null,
- null, true);
+ case SETTINGS_MODE_HELP:
+ // TODO: implement this.
break;
- default:
- return;
+ case SETTINGS_MODE_SUBTITLE_TRACK:
+ if (position != mSelectedSubtitleTrackIndex) {
+ mSelectedSubtitleTrackIndex = position;
+ if (position > 0) {
+ Bundle extra = new Bundle();
+ extra.putInt(KEY_SELECTED_SUBTITLE_INDEX, position - 1);
+ mController.sendCommand(
+ MediaControlView2Impl.COMMAND_SHOW_SUBTITLE, extra, null);
+ mSubtitleButton.setImageDrawable(
+ mResources.getDrawable(R.drawable.ic_subtitle_on, null));
+ mSubtitleIsEnabled = true;
+ } else {
+ mController.sendCommand(
+ MediaControlView2Impl.COMMAND_HIDE_SUBTITLE, null, null);
+ mSubtitleButton.setImageDrawable(
+ mResources.getDrawable(R.drawable.ic_subtitle_off, null));
+
+ mSubtitleIsEnabled = false;
+ }
+ }
+ mSettingsWindow.dismiss();
+ break;
+ case SETTINGS_MODE_VIDEO_QUALITY:
+ // TODO: add support for video quality
+ mSelectedVideoQualityIndex = position;
+ mSettingsWindow.dismiss();
+ break;
}
- mSettingsListView.setAdapter(mSettingsAdapter);
- int itemHeight = mResources.getDimensionPixelSize(
- R.dimen.MediaControlView2_settings_height);
- int totalHeight = mSettingsAdapter.getCount() * itemHeight;
- int margin = (-1) * mResources.getDimensionPixelSize(
- R.dimen.MediaControlView2_settings_offset);
- mSettingsWindow.dismiss();
- mSettingsWindow.showAsDropDown(mInstance, margin, margin - totalHeight,
- Gravity.BOTTOM | Gravity.RIGHT);
}
};
@@ -953,80 +1036,56 @@
}
private void initializeSettingsLists() {
- if (mSettingsMainTextsList == null) {
- mSettingsMainTextsList = new ArrayList<String>();
- mSettingsMainTextsList.add(
- mResources.getString(R.string.MediaControlView2_subtitle_text));
- mSettingsMainTextsList.add(
- mResources.getString(R.string.MediaControlView2_audio_track_text));
- mSettingsMainTextsList.add(
- mResources.getString(R.string.MediaControlView2_video_quality_text));
- mSettingsMainTextsList.add(
- mResources.getString(R.string.MediaControlView2_playback_speed_text));
- mSettingsMainTextsList.add(
- mResources.getString(R.string.MediaControlView2_help_text));
- }
+ mSettingsMainTextsList = new ArrayList<String>();
+ mSettingsMainTextsList.add(
+ mResources.getString(R.string.MediaControlView2_audio_track_text));
+ mSettingsMainTextsList.add(
+ mResources.getString(R.string.MediaControlView2_playback_speed_text));
+ mSettingsMainTextsList.add(
+ mResources.getString(R.string.MediaControlView2_help_text));
- // TODO: Update the following code to be dynamic.
- if (mSettingsSubTextsList == null) {
- mSettingsSubTextsList = new ArrayList<String>();
- mSettingsSubTextsList.add(
- mResources.getString(R.string.MediaControlView2_subtitle_off_text));
- mSettingsSubTextsList.add(
- mResources.getString(R.string.MediaControlView2_audio_track_none_text));
- mSettingsSubTextsList.add(
- mResources.getString(R.string.MediaControlView2_video_quality_auto_text));
- mSettingsSubTextsList.add(
- mResources.getString(R.string.MediaControlView2_playback_speed_1x_text));
- mSettingsSubTextsList.add(RESOURCE_EMPTY);
- }
+ mSettingsSubTextsList = new ArrayList<String>();
+ mSettingsSubTextsList.add(
+ mResources.getString(R.string.MediaControlView2_audio_track_none_text));
+ mSettingsSubTextsList.add(
+ mResources.getStringArray(
+ R.array.MediaControlView2_playback_speeds)[PLAYBACK_SPEED_1x_INDEX]);
+ mSettingsSubTextsList.add(RESOURCE_EMPTY);
- if (mSettingsIconIdsList == null) {
- mSettingsIconIdsList = new ArrayList<Integer>();
- mSettingsIconIdsList.add(R.drawable.ic_closed_caption_off);
- mSettingsIconIdsList.add(R.drawable.ic_audiotrack);
- mSettingsIconIdsList.add(R.drawable.ic_high_quality);
- mSettingsIconIdsList.add(R.drawable.ic_play_circle_filled);
- mSettingsIconIdsList.add(R.drawable.ic_help);
- }
+ mSettingsIconIdsList = new ArrayList<Integer>();
+ mSettingsIconIdsList.add(R.drawable.ic_audiotrack);
+ mSettingsIconIdsList.add(R.drawable.ic_play_circle_filled);
+ mSettingsIconIdsList.add(R.drawable.ic_help);
- if (mSubtitleDescriptionsList == null) {
- mSubtitleDescriptionsList = new ArrayList<String>();
- mSubtitleDescriptionsList.add(
- mResources.getString(R.string.MediaControlView2_subtitle_off_text));
- }
+ mAudioTrackList = new ArrayList<String>();
+ mAudioTrackList.add(
+ mResources.getString(R.string.MediaControlView2_audio_track_none_text));
- if (mAudioTrackList == null) {
- mAudioTrackList = new ArrayList<String>();
- mAudioTrackList.add(
- mResources.getString(R.string.MediaControlView2_audio_track_none_text));
- }
+ mVideoQualityList = new ArrayList<String>();
+ mVideoQualityList.add(
+ mResources.getString(R.string.MediaControlView2_video_quality_auto_text));
- if (mVideoQualityList == null) {
- mVideoQualityList = new ArrayList<String>();
- mVideoQualityList.add(
- mResources.getString(R.string.MediaControlView2_video_quality_auto_text));
- }
+ mPlaybackSpeedTextList = new ArrayList<String>(Arrays.asList(
+ mResources.getStringArray(R.array.MediaControlView2_playback_speeds)));
+ // Select the "1x" speed as the default value.
+ mSelectedSpeedIndex = PLAYBACK_SPEED_1x_INDEX;
- if (mPlaybackSpeedTextIdsList == null) {
- mPlaybackSpeedTextIdsList = new ArrayList<String>();
- mPlaybackSpeedTextIdsList.add(
- mResources.getString(R.string.MediaControlView2_playback_speed_0_25x_text));
- mPlaybackSpeedTextIdsList.add(
- mResources.getString(R.string.MediaControlView2_playback_speed_0_5x_text));
- mPlaybackSpeedTextIdsList.add(
- mResources.getString(R.string.MediaControlView2_playback_speed_0_75x_text));
- mPlaybackSpeedTextIdsList.add(
- mResources.getString(R.string.MediaControlView2_playback_speed_1x_text));
- mPlaybackSpeedTextIdsList.add(
- mResources.getString(R.string.MediaControlView2_playback_speed_1_25x_text));
- mPlaybackSpeedTextIdsList.add(
- mResources.getString(R.string.MediaControlView2_playback_speed_1_5x_text));
- mPlaybackSpeedTextIdsList.add(
- mResources.getString(R.string.MediaControlView2_playback_speed_2x_text));
+ mPlaybackSpeedList = new ArrayList<Float>();
+ int[] speeds = mResources.getIntArray(R.array.speed_multiplied_by_100);
+ for (int i = 0; i < speeds.length; i++) {
+ float speed = (float) speeds[i] / 100.0f;
+ mPlaybackSpeedList.add(speed);
}
}
+ private void displaySettingsWindow(BaseAdapter adapter) {
+ mSettingsListView.setAdapter(adapter);
+ int totalHeight = adapter.getCount() * mSettingsItemHeight;
+ mSettingsWindow.dismiss();
+ mSettingsWindow.showAsDropDown(mInstance, mSettingsWindowMargin,
+ mSettingsWindowMargin - totalHeight, Gravity.BOTTOM | Gravity.RIGHT);
+ }
+
private class MediaControllerCallback extends MediaController.Callback {
@Override
public void onPlaybackStateChanged(PlaybackState state) {
@@ -1065,16 +1124,13 @@
if (mPlaybackActions != mPlaybackState.getActions()) {
long newActions = mPlaybackState.getActions();
if ((newActions & PlaybackState.ACTION_PAUSE) != 0) {
- mPlayPauseButton.clearColorFilter();
- mPlayPauseButton.setEnabled(true);
+ mPlayPauseButton.setVisibility(View.VISIBLE);
}
if ((newActions & PlaybackState.ACTION_REWIND) != 0) {
- mRewButton.clearColorFilter();
- mRewButton.setEnabled(true);
+ mRewButton.setVisibility(View.VISIBLE);
}
if ((newActions & PlaybackState.ACTION_FAST_FORWARD) != 0) {
- mFfwdButton.clearColorFilter();
- mFfwdButton.setEnabled(true);
+ mFfwdButton.setVisibility(View.VISIBLE);
}
if ((newActions & PlaybackState.ACTION_SEEK_TO) != 0) {
mSeekAvailable = true;
@@ -1122,16 +1178,42 @@
switch (event) {
case EVENT_UPDATE_TRACK_STATUS:
mVideoTrackCount = extras.getInt(KEY_VIDEO_TRACK_COUNT);
+ // If there is one or more audio tracks, and this information has not been
+ // reflected into the Settings window yet, automatically check the first track.
+ // Otherwise, the Audio Track selection will be defaulted to "None".
mAudioTrackCount = extras.getInt(KEY_AUDIO_TRACK_COUNT);
- int newSubtitleTrackCount = extras.getInt(KEY_SUBTITLE_TRACK_COUNT);
- if (newSubtitleTrackCount > 0) {
- mSubtitleButton.clearColorFilter();
- mSubtitleButton.setEnabled(true);
+ mAudioTrackList = new ArrayList<String>();
+ if (mAudioTrackCount > 0) {
+ // TODO: add more text about track info.
+ for (int i = 0; i < mAudioTrackCount; i++) {
+ String track = mResources.getString(
+ R.string.MediaControlView2_audio_track_number_text, i + 1);
+ mAudioTrackList.add(track);
+ }
+ // Change sub text inside the Settings window.
+ mSettingsSubTextsList.set(SETTINGS_MODE_AUDIO_TRACK,
+ mAudioTrackList.get(0));
} else {
- mSubtitleButton.setColorFilter(R.color.gray);
+ mAudioTrackList.add(mResources.getString(
+ R.string.MediaControlView2_audio_track_none_text));
+ }
+
+ mSubtitleTrackCount = extras.getInt(KEY_SUBTITLE_TRACK_COUNT);
+ mSubtitleDescriptionsList = new ArrayList<String>();
+ if (mSubtitleTrackCount > 0) {
+ mSubtitleButton.setVisibility(View.VISIBLE);
+ mSubtitleButton.setEnabled(true);
+ mSubtitleDescriptionsList.add(mResources.getString(
+ R.string.MediaControlView2_subtitle_off_text));
+ for (int i = 0; i < mSubtitleTrackCount; i++) {
+ String track = mResources.getString(
+ R.string.MediaControlView2_subtitle_track_number_text, i + 1);
+ mSubtitleDescriptionsList.add(track);
+ }
+ } else {
+ mSubtitleButton.setVisibility(View.GONE);
mSubtitleButton.setEnabled(false);
}
- mSubtitleTrackCount = newSubtitleTrackCount;
break;
case EVENT_UPDATE_MEDIA_TYPE_STATUS:
boolean newStatus = extras.getBoolean(KEY_STATE_IS_ADVERTISEMENT);
@@ -1145,17 +1227,29 @@
}
private class SettingsAdapter extends BaseAdapter {
- List<Integer> mIconIds;
- List<String> mMainTexts;
- List<String> mSubTexts;
- boolean mIsCheckable;
+ private List<Integer> mIconIds;
+ private List<String> mMainTexts;
+ private List<String> mSubTexts;
public SettingsAdapter(List<String> mainTexts, @Nullable List<String> subTexts,
- @Nullable List<Integer> iconIds, boolean isCheckable) {
+ @Nullable List<Integer> iconIds) {
mMainTexts = mainTexts;
mSubTexts = subTexts;
mIconIds = iconIds;
- mIsCheckable = isCheckable;
+ }
+
+ public void updateSubTexts(List<String> subTexts) {
+ mSubTexts = subTexts;
+ notifyDataSetChanged();
+ }
+
+ public String getMainText(int position) {
+ if (mMainTexts != null) {
+ if (position < mMainTexts.size()) {
+ return mMainTexts.get(position);
+ }
+ }
+ return RESOURCE_EMPTY;
}
@Override
@@ -1184,7 +1278,6 @@
TextView mainTextView = (TextView) row.findViewById(R.id.main_text);
TextView subTextView = (TextView) row.findViewById(R.id.sub_text);
ImageView iconView = (ImageView) row.findViewById(R.id.icon);
- ImageView checkView = (ImageView) row.findViewById(R.id.check);
// Set main text
mainTextView.setText(mMainTexts.get(position));
@@ -1206,13 +1299,72 @@
// Otherwise, set main icon.
iconView.setImageDrawable(mResources.getDrawable(mIconIds.get(position), null));
}
+ return row;
+ }
- // Set check icon
- // TODO: make the following code dynamic
- if (!mIsCheckable) {
- checkView.setVisibility(View.GONE);
+ public void setSubTexts(List<String> subTexts) {
+ mSubTexts = subTexts;
+ }
+ }
+
+ // TODO: extend this class from SettingsAdapter
+ private class SubSettingsAdapter extends BaseAdapter {
+ private List<String> mTexts;
+ private int mCheckPosition;
+
+ public SubSettingsAdapter(List<String> texts, int checkPosition) {
+ mTexts = texts;
+ mCheckPosition = checkPosition;
+ }
+
+ public String getMainText(int position) {
+ if (mTexts != null) {
+ if (position < mTexts.size()) {
+ return mTexts.get(position);
+ }
+ }
+ return RESOURCE_EMPTY;
+ }
+
+ @Override
+ public int getCount() {
+ return (mTexts == null) ? 0 : mTexts.size();
+ }
+
+ @Override
+ public long getItemId(int position) {
+ // Auto-generated method stub--does not have any purpose here
+ // TODO: implement this.
+ return 0;
+ }
+
+ @Override
+ public Object getItem(int position) {
+ // Auto-generated method stub--does not have any purpose here
+ // TODO: implement this.
+ return null;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup container) {
+ View row = ApiHelper.inflateLibLayout(mInstance.getContext(),
+ R.layout.sub_settings_list_item);
+ TextView textView = (TextView) row.findViewById(R.id.text);
+ ImageView checkView = (ImageView) row.findViewById(R.id.check);
+
+ textView.setText(mTexts.get(position));
+ if (position != mCheckPosition) {
+ checkView.setVisibility(View.INVISIBLE);
}
return row;
}
+
+ public void setTexts(List<String> texts) {
+ mTexts = texts;
+ }
+
+ public void setCheckPosition(int checkPosition) {
+ mCheckPosition = checkPosition;
+ }
}
}
diff --git a/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java b/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
index 0bb659d..264f632 100644
--- a/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
+++ b/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
@@ -86,6 +86,7 @@
private static final int STATE_PLAYBACK_COMPLETED = 5;
private static final int INVALID_TRACK_INDEX = -1;
+ private static final float INVALID_SPEED = 0f;
private AccessibilityManager mAccessibilityManager;
private AudioManager mAudioManager;
@@ -137,6 +138,9 @@
// TODO: Remove mFallbackSpeed when integration with MediaPlayer2's new setPlaybackParams().
// Refer: https://docs.google.com/document/d/1nzAfns6i2hJ3RkaUre3QMT6wsDedJ5ONLiA_OOBFFX8/edit
private float mFallbackSpeed; // keep the original speed before 'pause' is called.
+ private float mVolumeLevelFloat;
+ private int mVolumeLevel;
+
private long mShowControllerIntervalMs;
private MediaRouter mMediaRouter;
@@ -651,7 +655,7 @@
};
mMediaPlayer.setMediaPlayer2EventCallback(executor, mMediaPlayer2Callback);
- mCurrentBufferPercentage = 0;
+ mCurrentBufferPercentage = -1;
mMediaPlayer.setDataSource(dsd);
mMediaPlayer.setAudioAttributes(mAudioAttributes);
// we don't set the target state here either, but preserve the
@@ -753,8 +757,12 @@
&& mCurrentState != STATE_PREPARING) {
// TODO: this should be replaced with MediaPlayer2.getBufferedPosition() once it is
// implemented.
- mStateBuilder.setBufferedPosition(
- (long) (mCurrentBufferPercentage / 100.0) * mMediaPlayer.getDuration());
+ if (mCurrentBufferPercentage == -1) {
+ mStateBuilder.setBufferedPosition(-1);
+ } else {
+ mStateBuilder.setBufferedPosition(
+ (long) (mCurrentBufferPercentage / 100.0 * mMediaPlayer.getDuration()));
+ }
}
// Set PlaybackState for MediaSession
@@ -854,8 +862,6 @@
}
if (select) {
if (mSubtitleTrackIndices.size() > 0) {
- // TODO: make this selection dynamic
- mSelectedSubtitleTrackIndex = mSubtitleTrackIndices.get(0);
mMediaPlayer.selectTrack(mSelectedSubtitleTrackIndex);
mSubtitleView.setVisibility(View.VISIBLE);
}
@@ -881,9 +887,17 @@
mAudioTrackIndices.add(i);
} else if (trackType == MediaPlayer2.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE
|| trackType == MediaPlayer2.TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT) {
- mSubtitleTrackIndices.add(i);
+ mSubtitleTrackIndices.add(i);
}
}
+ // Select first tracks as default
+ if (mVideoTrackIndices.size() > 0) {
+ mSelectedVideoTrackIndex = 0;
+ }
+ if (mAudioTrackIndices.size() > 0) {
+ mSelectedAudioTrackIndex = 0;
+ }
+
Bundle data = new Bundle();
data.putInt(MediaControlView2Impl.KEY_VIDEO_TRACK_COUNT, mVideoTrackIndices.size());
data.putInt(MediaControlView2Impl.KEY_AUDIO_TRACK_COUNT, mAudioTrackIndices.size());
@@ -1048,10 +1062,19 @@
} else {
switch (command) {
case MediaControlView2Impl.COMMAND_SHOW_SUBTITLE:
- mInstance.setSubtitleEnabled(true);
+ int subtitleIndex = args.getInt(
+ MediaControlView2Impl.KEY_SELECTED_SUBTITLE_INDEX,
+ INVALID_TRACK_INDEX);
+ if (subtitleIndex != INVALID_TRACK_INDEX) {
+ int subtitleTrackIndex = mSubtitleTrackIndices.get(subtitleIndex);
+ if (subtitleTrackIndex != mSelectedSubtitleTrackIndex) {
+ mSelectedSubtitleTrackIndex = subtitleTrackIndex;
+ selectOrDeselectSubtitle(true);
+ }
+ }
break;
case MediaControlView2Impl.COMMAND_HIDE_SUBTITLE:
- mInstance.setSubtitleEnabled(false);
+ selectOrDeselectSubtitle(false);
break;
case MediaControlView2Impl.COMMAND_SET_FULLSCREEN:
if (mFullScreenRequestListener != null) {
@@ -1060,6 +1083,32 @@
args.getBoolean(MediaControlView2Impl.ARGUMENT_KEY_FULLSCREEN));
}
break;
+ case MediaControlView2Impl.COMMAND_SELECT_AUDIO_TRACK:
+ int audioIndex = args.getInt(MediaControlView2Impl.KEY_SELECTED_AUDIO_INDEX,
+ INVALID_TRACK_INDEX);
+ if (audioIndex != INVALID_TRACK_INDEX) {
+ int audioTrackIndex = mAudioTrackIndices.get(audioIndex);
+ if (audioTrackIndex != mSelectedAudioTrackIndex) {
+ mSelectedAudioTrackIndex = audioTrackIndex;
+ mMediaPlayer.selectTrack(mSelectedAudioTrackIndex);
+ }
+ }
+ break;
+ case MediaControlView2Impl.COMMAND_SET_PLAYBACK_SPEED:
+ float speed = args.getFloat(
+ MediaControlView2Impl.KEY_PLAYBACK_SPEED, INVALID_SPEED);
+ if (speed != INVALID_SPEED && speed != mSpeed) {
+ mInstance.setSpeed(speed);
+ mSpeed = speed;
+ }
+ break;
+ case MediaControlView2Impl.COMMAND_MUTE:
+ mVolumeLevel = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+ mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 0, 0);
+ break;
+ case MediaControlView2Impl.COMMAND_UNMUTE:
+ mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, mVolumeLevel, 0);
+ break;
}
}
showController();
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index 229e08e..8033382 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -3051,6 +3051,7 @@
// check recording permission for visualizer
if ((memcmp(&desc.type, SL_IID_VISUALIZATION, sizeof(effect_uuid_t)) == 0) &&
+ // TODO: Do we need to start/stop op - i.e. is there recording being performed?
!recordingAllowed(opPackageName, pid, IPCThreadState::self()->getCallingUid())) {
lStatus = PERMISSION_DENIED;
goto Exit;
diff --git a/services/audioflinger/ServiceUtilities.cpp b/services/audioflinger/ServiceUtilities.cpp
index f08698e..aa267ea 100644
--- a/services/audioflinger/ServiceUtilities.cpp
+++ b/services/audioflinger/ServiceUtilities.cpp
@@ -30,6 +30,8 @@
namespace android {
+static const String16 sAndroidPermissionRecordAudio("android.permission.RECORD_AUDIO");
+
// Not valid until initialized by AudioFlinger constructor. It would have to be
// re-initialized if the process containing AudioFlinger service forks (which it doesn't).
// This is often used to validate binder interface calls within audioserver
@@ -48,26 +50,11 @@
}
}
-bool recordingAllowed(const String16& opPackageName, pid_t pid, uid_t uid) {
- // we're always OK.
- if (getpid_cached == IPCThreadState::self()->getCallingPid()) return true;
-
- static const String16 sRecordAudio("android.permission.RECORD_AUDIO");
-
- // We specify a pid and uid here as mediaserver (aka MediaRecorder or StageFrightRecorder)
- // may open a record track on behalf of a client. Note that pid may be a tid.
- // IMPORTANT: Don't use PermissionCache - a runtime permission and may change.
- const bool ok = checkPermission(sRecordAudio, pid, uid);
- if (!ok) {
- ALOGE("Request requires android.permission.RECORD_AUDIO");
- return false;
+static String16 resolveCallingPackage(PermissionController& permissionController,
+ const String16& opPackageName, uid_t uid) {
+ if (opPackageName.size() > 0) {
+ return opPackageName;
}
-
- // To permit command-line native tests
- if (uid == AID_ROOT) return true;
-
- String16 checkedOpPackageName = opPackageName;
-
// In some cases the calling code has no access to the package it runs under.
// For example, code using the wilhelm framework's OpenSL-ES APIs. In this
// case we will get the packages for the calling UID and pick the first one
@@ -75,40 +62,89 @@
// as for legacy apps we will toggle the app op for all packages in the UID.
// The caveat is that the operation may be attributed to the wrong package and
// stats based on app ops may be slightly off.
- if (checkedOpPackageName.size() <= 0) {
- sp<IServiceManager> sm = defaultServiceManager();
- sp<IBinder> binder = sm->getService(String16("permission"));
- if (binder == 0) {
- ALOGE("Cannot get permission service");
- return false;
- }
+ Vector<String16> packages;
+ permissionController.getPackagesForUid(uid, packages);
+ if (packages.isEmpty()) {
+ ALOGE("No packages for uid %d", uid);
+ return opPackageName; // empty string
+ }
+ return packages[0];
+}
- sp<IPermissionController> permCtrl = interface_cast<IPermissionController>(binder);
- Vector<String16> packages;
+static inline bool isAudioServerOrRoot(uid_t uid) {
+ // AID_ROOT is OK for command-line tests. Native unforked audioserver always OK.
+ return uid == AID_ROOT || uid == AID_AUDIOSERVER ;
+}
- permCtrl->getPackagesForUid(uid, packages);
+static bool checkRecordingInternal(const String16& opPackageName, pid_t pid,
+ uid_t uid, bool start) {
+ // Okay to not track in app ops as audio server is us and if
+ // device is rooted security model is considered compromised.
+ if (isAudioServerOrRoot(uid)) return true;
- if (packages.isEmpty()) {
- ALOGE("No packages for calling UID");
- return false;
- }
- checkedOpPackageName = packages[0];
+ // We specify a pid and uid here as mediaserver (aka MediaRecorder or StageFrightRecorder)
+ // may open a record track on behalf of a client. Note that pid may be a tid.
+ // IMPORTANT: DON'T USE PermissionCache - RUNTIME PERMISSIONS CHANGE.
+ PermissionController permissionController;
+ const bool ok = permissionController.checkPermission(sAndroidPermissionRecordAudio, pid, uid);
+ if (!ok) {
+ ALOGE("Request requires %s", String8(sAndroidPermissionRecordAudio).c_str());
+ return false;
+ }
+
+ String16 resolvedOpPackageName = resolveCallingPackage(
+ permissionController, opPackageName, uid);
+ if (resolvedOpPackageName.size() == 0) {
+ return false;
}
AppOpsManager appOps;
- if (appOps.noteOp(AppOpsManager::OP_RECORD_AUDIO, uid, checkedOpPackageName)
- != AppOpsManager::MODE_ALLOWED) {
- ALOGE("Request denied by app op OP_RECORD_AUDIO");
- return false;
+ const int32_t op = appOps.permissionToOpCode(sAndroidPermissionRecordAudio);
+ if (start) {
+ if (appOps.startOpNoThrow(op, uid, resolvedOpPackageName, /*startIfModeDefault*/ false)
+ != AppOpsManager::MODE_ALLOWED) {
+ ALOGE("Request denied by app op: %d", op);
+ return false;
+ }
+ } else {
+ if (appOps.noteOp(op, uid, resolvedOpPackageName) != AppOpsManager::MODE_ALLOWED) {
+ ALOGE("Request denied by app op: %d", op);
+ return false;
+ }
}
return true;
}
+bool recordingAllowed(const String16& opPackageName, pid_t pid, uid_t uid) {
+ return checkRecordingInternal(opPackageName, pid, uid, /*start*/ false);
+}
+
+bool startRecording(const String16& opPackageName, pid_t pid, uid_t uid) {
+ return checkRecordingInternal(opPackageName, pid, uid, /*start*/ true);
+}
+
+void finishRecording(const String16& opPackageName, uid_t uid) {
+ // Okay to not track in app ops as audio server is us and if
+ // device is rooted security model is considered compromised.
+ if (isAudioServerOrRoot(uid)) return;
+
+ PermissionController permissionController;
+ String16 resolvedOpPackageName = resolveCallingPackage(
+ permissionController, opPackageName, uid);
+ if (resolvedOpPackageName.size() == 0) {
+ return;
+ }
+
+ AppOpsManager appOps;
+ const int32_t op = appOps.permissionToOpCode(sAndroidPermissionRecordAudio);
+ appOps.finishOp(op, uid, resolvedOpPackageName);
+}
+
bool captureAudioOutputAllowed(pid_t pid, uid_t uid) {
if (getpid_cached == IPCThreadState::self()->getCallingPid()) return true;
static const String16 sCaptureAudioOutput("android.permission.CAPTURE_AUDIO_OUTPUT");
- bool ok = checkPermission(sCaptureAudioOutput, pid, uid);
+ bool ok = PermissionCache::checkPermission(sCaptureAudioOutput, pid, uid);
if (!ok) ALOGE("Request requires android.permission.CAPTURE_AUDIO_OUTPUT");
return ok;
}
@@ -155,7 +191,7 @@
bool modifyPhoneStateAllowed(pid_t pid, uid_t uid) {
static const String16 sModifyPhoneState("android.permission.MODIFY_PHONE_STATE");
- bool ok = checkPermission(sModifyPhoneState, pid, uid);
+ bool ok = PermissionCache::checkPermission(sModifyPhoneState, pid, uid);
if (!ok) ALOGE("Request requires android.permission.MODIFY_PHONE_STATE");
return ok;
}
diff --git a/services/audioflinger/ServiceUtilities.h b/services/audioflinger/ServiceUtilities.h
index 83533dd..f45ada1 100644
--- a/services/audioflinger/ServiceUtilities.h
+++ b/services/audioflinger/ServiceUtilities.h
@@ -16,11 +16,15 @@
#include <unistd.h>
+#include <binder/PermissionController.h>
+
namespace android {
extern pid_t getpid_cached;
bool isTrustedCallingUid(uid_t uid);
bool recordingAllowed(const String16& opPackageName, pid_t pid, uid_t uid);
+bool startRecording(const String16& opPackageName, pid_t pid, uid_t uid);
+void finishRecording(const String16& opPackageName, uid_t uid);
bool captureAudioOutputAllowed(pid_t pid, uid_t uid);
bool captureHotwordAllowed(pid_t pid, uid_t uid);
bool settingsAllowed();
diff --git a/services/audiopolicy/common/include/Volume.h b/services/audiopolicy/common/include/Volume.h
index 1239fe0..4862684 100644
--- a/services/audiopolicy/common/include/Volume.h
+++ b/services/audiopolicy/common/include/Volume.h
@@ -125,6 +125,7 @@
case AUDIO_DEVICE_OUT_BLUETOOTH_A2DP:
case AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES:
case AUDIO_DEVICE_OUT_USB_HEADSET:
+ case AUDIO_DEVICE_OUT_HEARING_AID:
return DEVICE_CATEGORY_HEADSET;
case AUDIO_DEVICE_OUT_LINE:
case AUDIO_DEVICE_OUT_AUX_DIGITAL:
diff --git a/services/audiopolicy/common/managerdefinitions/include/AudioOutputDescriptor.h b/services/audiopolicy/common/managerdefinitions/include/AudioOutputDescriptor.h
index 78a184b..5e5d38b 100644
--- a/services/audiopolicy/common/managerdefinitions/include/AudioOutputDescriptor.h
+++ b/services/audiopolicy/common/managerdefinitions/include/AudioOutputDescriptor.h
@@ -203,6 +203,16 @@
*/
audio_io_handle_t getA2dpOutput() const;
+ /**
+ * returns true if primary HAL supports A2DP Offload
+ */
+ bool isA2dpOffloadedOnPrimary() const;
+
+ /**
+ * returns true if A2DP is supported (either via hardware offload or software encoding)
+ */
+ bool isA2dpSupported() const;
+
sp<SwAudioOutputDescriptor> getOutputFromId(audio_port_handle_t id) const;
sp<SwAudioOutputDescriptor> getPrimaryOutput() const;
diff --git a/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp b/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp
index caaa0f7..294a2a6 100644
--- a/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp
+++ b/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp
@@ -645,6 +645,29 @@
return 0;
}
+bool SwAudioOutputCollection::isA2dpOffloadedOnPrimary() const
+{
+ sp<SwAudioOutputDescriptor> primaryOutput = getPrimaryOutput();
+
+ if ((primaryOutput != NULL) && (primaryOutput->mProfile != NULL)
+ && (primaryOutput->mProfile->mModule != NULL)) {
+ sp<HwModule> primaryHwModule = primaryOutput->mProfile->mModule;
+ Vector <sp<IOProfile>> primaryHwModuleOutputProfiles =
+ primaryHwModule->getOutputProfiles();
+ for (size_t i = 0; i < primaryHwModuleOutputProfiles.size(); i++) {
+ if (primaryHwModuleOutputProfiles[i]->supportDevice(AUDIO_DEVICE_OUT_ALL_A2DP)) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+bool SwAudioOutputCollection::isA2dpSupported() const
+{
+ return (isA2dpOffloadedOnPrimary() || (getA2dpOutput() != 0));
+}
+
sp<SwAudioOutputDescriptor> SwAudioOutputCollection::getPrimaryOutput() const
{
for (size_t i = 0; i < size(); i++) {
diff --git a/services/audiopolicy/config/audio_policy_configuration.xml b/services/audiopolicy/config/audio_policy_configuration.xml
index 73efe8e..a75f1cb 100644
--- a/services/audiopolicy/config/audio_policy_configuration.xml
+++ b/services/audiopolicy/config/audio_policy_configuration.xml
@@ -182,6 +182,9 @@
<!-- Remote Submix Audio HAL -->
<xi:include href="r_submix_audio_policy_configuration.xml"/>
+ <!-- Hearing aid Audio HAL -->
+ <xi:include href="hearing_aid_audio_policy_configuration.xml"/>
+
</modules>
<!-- End of Modules section -->
diff --git a/services/audiopolicy/config/hearing_aid_audio_policy_configuration.xml b/services/audiopolicy/config/hearing_aid_audio_policy_configuration.xml
new file mode 100644
index 0000000..3c48e88
--- /dev/null
+++ b/services/audiopolicy/config/hearing_aid_audio_policy_configuration.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Hearing aid Audio HAL Audio Policy Configuration file -->
+<module name="hearing_aid" halVersion="2.0">
+ <mixPorts>
+ <mixPort name="hearing aid output" role="source" flags="AUDIO_OUTPUT_FLAG_PRIMARY">
+ <profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
+ samplingRates="24000,16000"
+ channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
+ </mixPort>
+ </mixPorts>
+ <devicePorts>
+ <devicePort tagName="BT Hearing Aid Out" type="AUDIO_DEVICE_OUT_HEARING_AID" role="sink"/>
+ </devicePorts>
+ <routes>
+ <route type="mix" sink="BT Hearing Aid Out" sources="hearing aid output"/>
+ </routes>
+</module>
diff --git a/services/audiopolicy/enginedefault/src/Engine.cpp b/services/audiopolicy/enginedefault/src/Engine.cpp
index 5ec0475..977a396 100644
--- a/services/audiopolicy/enginedefault/src/Engine.cpp
+++ b/services/audiopolicy/enginedefault/src/Engine.cpp
@@ -306,8 +306,14 @@
sp<AudioOutputDescriptor> primaryOutput = outputs.getPrimaryOutput();
audio_devices_t availPrimaryInputDevices =
availableInputDevices.getDevicesFromHwModule(primaryOutput->getModuleHandle());
+
+ // TODO: getPrimaryOutput return only devices from first module in
+ // audio_policy_configuration.xml, hearing aid is not there, but it's
+ // a primary device
+ // FIXME: this is not the right way of solving this problem
audio_devices_t availPrimaryOutputDevices =
- primaryOutput->supportedDevices() & availableOutputDevices.types();
+ (primaryOutput->supportedDevices() | AUDIO_DEVICE_OUT_HEARING_AID) &
+ availableOutputDevices.types();
if (((availableInputDevices.types() &
AUDIO_DEVICE_IN_TELEPHONY_RX & ~AUDIO_DEVICE_BIT_IN) == 0) ||
@@ -332,10 +338,12 @@
// FALL THROUGH
default: // FORCE_NONE
+ device = availableOutputDevicesType & AUDIO_DEVICE_OUT_HEARING_AID;
+ if (device) break;
// when not in a phone call, phone strategy should route STREAM_VOICE_CALL to A2DP
if (!isInCall() &&
(mForceUse[AUDIO_POLICY_FORCE_FOR_MEDIA] != AUDIO_POLICY_FORCE_NO_BT_A2DP) &&
- (outputs.getA2dpOutput() != 0)) {
+ outputs.isA2dpSupported()) {
device = availableOutputDevicesType & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP;
if (device) break;
device = availableOutputDevicesType & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES;
@@ -369,7 +377,7 @@
// A2DP speaker when forcing to speaker output
if (!isInCall() &&
(mForceUse[AUDIO_POLICY_FORCE_FOR_MEDIA] != AUDIO_POLICY_FORCE_NO_BT_A2DP) &&
- (outputs.getA2dpOutput() != 0)) {
+ outputs.isA2dpSupported()) {
device = availableOutputDevicesType & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER;
if (device) break;
}
@@ -481,9 +489,12 @@
outputDeviceTypesToIgnore);
break;
}
+ if (device2 == AUDIO_DEVICE_NONE) {
+ device2 = availableOutputDevicesType & AUDIO_DEVICE_OUT_HEARING_AID;
+ }
if ((device2 == AUDIO_DEVICE_NONE) &&
(mForceUse[AUDIO_POLICY_FORCE_FOR_MEDIA] != AUDIO_POLICY_FORCE_NO_BT_A2DP) &&
- (outputs.getA2dpOutput() != 0)) {
+ outputs.isA2dpSupported()) {
device2 = availableOutputDevicesType & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP;
if (device2 == AUDIO_DEVICE_NONE) {
device2 = availableOutputDevicesType & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES;
diff --git a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
index 42b199a..74ca902 100644
--- a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
+++ b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
@@ -26,6 +26,7 @@
#define AUDIO_POLICY_XML_CONFIG_FILE_PATH_MAX_LENGTH 128
#define AUDIO_POLICY_XML_CONFIG_FILE_NAME "audio_policy_configuration.xml"
+#define AUDIO_POLICY_A2DP_OFFLOAD_XML_CONFIG_FILE_NAME "audio_policy_a2dp_offload_configuration.xml"
#include <inttypes.h>
#include <math.h>
@@ -3517,11 +3518,14 @@
for (int i = 0; i < kConfigLocationListSize; i++) {
PolicySerializer serializer;
+ bool use_a2dp_offload_config =
+ property_get_bool("persist.bluetooth.a2dp_offload.enable", false);
snprintf(audioPolicyXmlConfigFile,
sizeof(audioPolicyXmlConfigFile),
"%s/%s",
kConfigLocationList[i],
- AUDIO_POLICY_XML_CONFIG_FILE_NAME);
+ use_a2dp_offload_config ? AUDIO_POLICY_A2DP_OFFLOAD_XML_CONFIG_FILE_NAME :
+ AUDIO_POLICY_XML_CONFIG_FILE_NAME);
ret = serializer.deserialize(audioPolicyXmlConfigFile, config);
if (ret == NO_ERROR) {
break;
@@ -4381,7 +4385,7 @@
void AudioPolicyManager::checkA2dpSuspend()
{
audio_io_handle_t a2dpOutput = mOutputs.getA2dpOutput();
- if (a2dpOutput == 0) {
+ if (a2dpOutput == 0 || mOutputs.isA2dpOffloadedOnPrimary()) {
mA2dpSuspended = false;
return;
}
diff --git a/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp b/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp
index 306de3f..8f0c846 100644
--- a/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp
+++ b/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp
@@ -412,7 +412,7 @@
}
// check calling permissions
- if (!recordingAllowed(client->opPackageName, client->pid, client->uid)) {
+ if (!startRecording(client->opPackageName, client->pid, client->uid)) {
ALOGE("%s permission denied: recording not allowed for uid %d pid %d",
__func__, client->uid, client->pid);
return PERMISSION_DENIED;
@@ -439,6 +439,8 @@
if (concurrency & AudioPolicyInterface::API_INPUT_CONCURRENCY_CAPTURE) {
//TODO: check concurrent capture permission
}
+ } else {
+ finishRecording(client->opPackageName, client->uid);
}
return status;
@@ -457,6 +459,9 @@
}
sp<AudioRecordClient> client = mAudioRecordClients.valueAt(index);
+ // finish the recording app op
+ finishRecording(client->opPackageName, client->uid);
+
return mAudioPolicyManager->stopInput(client->input, client->session);
}
diff --git a/services/camera/libcameraservice/api2/CameraDeviceClient.cpp b/services/camera/libcameraservice/api2/CameraDeviceClient.cpp
index 9ab2d88..c49de8e 100644
--- a/services/camera/libcameraservice/api2/CameraDeviceClient.cpp
+++ b/services/camera/libcameraservice/api2/CameraDeviceClient.cpp
@@ -1002,10 +1002,6 @@
case HAL_PIXEL_FORMAT_BLOB:
case HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED:
case HAL_PIXEL_FORMAT_YCbCr_420_888:
- case HAL_PIXEL_FORMAT_YCbCr_422_888:
- case HAL_PIXEL_FORMAT_YCbCr_444_888:
- case HAL_PIXEL_FORMAT_FLEX_RGB_888:
- case HAL_PIXEL_FORMAT_FLEX_RGBA_8888:
case HAL_PIXEL_FORMAT_YCbCr_422_SP:
case HAL_PIXEL_FORMAT_YCrCb_420_SP:
case HAL_PIXEL_FORMAT_YCbCr_422_I:
diff --git a/services/mediacodec/seccomp_policy/mediacodec-x86.policy b/services/mediacodec/seccomp_policy/mediacodec-x86.policy
index 1553ec7..bbbe552 100644
--- a/services/mediacodec/seccomp_policy/mediacodec-x86.policy
+++ b/services/mediacodec/seccomp_policy/mediacodec-x86.policy
@@ -16,12 +16,14 @@
mprotect: 1
prctl: 1
openat: 1
+open: 1
getuid32: 1
writev: 1
ioctl: 1
close: 1
mmap2: 1
fstat64: 1
+stat64: 1
madvise: 1
fstatat64: 1
futex: 1