Merge "aaudio: call validate() from readFromParcel()" 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/mp4/ItemTable.cpp b/media/extractors/mp4/ItemTable.cpp
index cef5f4a..c082fc1 100644
--- a/media/extractors/mp4/ItemTable.cpp
+++ b/media/extractors/mp4/ItemTable.cpp
@@ -982,26 +982,24 @@
bool InfeBox::parseNullTerminatedString(
off64_t *offset, size_t *size, String8 *out) {
- char tmp[256];
- size_t len = 0;
+ char tmp;
+ Vector<char> buf;
+ buf.setCapacity(256);
off64_t newOffset = *offset;
off64_t stopOffset = *offset + *size;
while (newOffset < stopOffset) {
- if (!source()->readAt(newOffset++, &tmp[len], 1)) {
+ if (!source()->readAt(newOffset++, &tmp, 1)) {
return false;
}
- if (tmp[len] == 0) {
- out->append(tmp, len);
+ buf.push_back(tmp);
+ if (tmp == 0) {
+ out->setTo(buf.array());
*offset = newOffset;
*size = stopOffset - newOffset;
return true;
}
- if (++len >= sizeof(tmp)) {
- out->append(tmp, len);
- len = 0;
- }
}
return false;
}
@@ -1060,9 +1058,12 @@
return ERROR_MALFORMED;
}
- String8 content_encoding;
- if (!parseNullTerminatedString(&offset, &size, &content_encoding)) {
- return ERROR_MALFORMED;
+ // content_encoding is optional; can be omitted if would be empty
+ if (size > 0) {
+ String8 content_encoding;
+ if (!parseNullTerminatedString(&offset, &size, &content_encoding)) {
+ return ERROR_MALFORMED;
+ }
}
} else if (item_type == FOURCC('u', 'r', 'i', ' ')) {
String8 item_uri_type;
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/libaudiohal/4.0/Android.bp b/media/libaudiohal/4.0/Android.bp
new file mode 100644
index 0000000..3d104ab
--- /dev/null
+++ b/media/libaudiohal/4.0/Android.bp
@@ -0,0 +1,57 @@
+cc_library_shared {
+ name: "libaudiohal@4.0",
+
+ srcs: [
+ "DeviceHalLocal.cpp",
+ "DevicesFactoryHalHybrid.cpp",
+ "DevicesFactoryHalLocal.cpp",
+ "StreamHalLocal.cpp",
+
+ "ConversionHelperHidl.cpp",
+ "DeviceHalHidl.cpp",
+ "DevicesFactoryHalHidl.cpp",
+ "EffectBufferHalHidl.cpp",
+ "EffectHalHidl.cpp",
+ "EffectsFactoryHalHidl.cpp",
+ "StreamHalHidl.cpp",
+ ],
+
+ export_include_dirs: ["include"],
+
+ cflags: [
+ "-Wall",
+ "-Wextra",
+ "-Werror",
+ ],
+ shared_libs: [
+ "libaudiohal_deathhandler",
+ "libaudioutils",
+ "libcutils",
+ "liblog",
+ "libutils",
+ "libhardware",
+ "libbase",
+ "libfmq",
+ "libhwbinder",
+ "libhidlbase",
+ "libhidlmemory",
+ "libhidltransport",
+ "android.hardware.audio@4.0",
+ "android.hardware.audio.common-util",
+ "android.hardware.audio.common@4.0",
+ "android.hardware.audio.common@4.0-util",
+ "android.hardware.audio.effect@4.0",
+ "android.hidl.allocator@1.0",
+ "android.hidl.memory@1.0",
+ "libmedia_helper",
+ "libmediautils",
+ ],
+ header_libs: [
+ "android.hardware.audio.common.util@all-versions",
+ "libaudiohal_headers"
+ ],
+
+ export_shared_lib_headers: [
+ "libfmq",
+ ],
+}
diff --git a/media/libaudiohal/4.0/ConversionHelperHidl.cpp b/media/libaudiohal/4.0/ConversionHelperHidl.cpp
new file mode 100644
index 0000000..a3cc28f
--- /dev/null
+++ b/media/libaudiohal/4.0/ConversionHelperHidl.cpp
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <string.h>
+
+#define LOG_TAG "HalHidl"
+#include <media/AudioParameter.h>
+#include <utils/Log.h>
+
+#include "ConversionHelperHidl.h"
+
+using ::android::hardware::audio::V4_0::Result;
+
+namespace android {
+namespace V4_0 {
+
+// static
+status_t ConversionHelperHidl::keysFromHal(const String8& keys, hidl_vec<hidl_string> *hidlKeys) {
+ AudioParameter halKeys(keys);
+ if (halKeys.size() == 0) return BAD_VALUE;
+ hidlKeys->resize(halKeys.size());
+ //FIXME: keyStreamSupportedChannels and keyStreamSupportedSamplingRates come with a
+ // "keyFormat=<value>" pair. We need to transform it into a single key string so that it is
+ // carried over to the legacy HAL via HIDL.
+ String8 value;
+ bool keepFormatValue = halKeys.size() == 2 &&
+ (halKeys.get(String8(AudioParameter::keyStreamSupportedChannels), value) == NO_ERROR ||
+ halKeys.get(String8(AudioParameter::keyStreamSupportedSamplingRates), value) == NO_ERROR);
+
+ for (size_t i = 0; i < halKeys.size(); ++i) {
+ String8 key;
+ status_t status = halKeys.getAt(i, key);
+ if (status != OK) return status;
+ if (keepFormatValue && key == AudioParameter::keyFormat) {
+ AudioParameter formatParam;
+ halKeys.getAt(i, key, value);
+ formatParam.add(key, value);
+ key = formatParam.toString();
+ }
+ (*hidlKeys)[i] = key.string();
+ }
+ return OK;
+}
+
+// static
+status_t ConversionHelperHidl::parametersFromHal(
+ const String8& kvPairs, hidl_vec<ParameterValue> *hidlParams) {
+ AudioParameter params(kvPairs);
+ if (params.size() == 0) return BAD_VALUE;
+ hidlParams->resize(params.size());
+ for (size_t i = 0; i < params.size(); ++i) {
+ String8 key, value;
+ status_t status = params.getAt(i, key, value);
+ if (status != OK) return status;
+ (*hidlParams)[i].key = key.string();
+ (*hidlParams)[i].value = value.string();
+ }
+ return OK;
+}
+
+// static
+void ConversionHelperHidl::parametersToHal(
+ const hidl_vec<ParameterValue>& parameters, String8 *values) {
+ AudioParameter params;
+ for (size_t i = 0; i < parameters.size(); ++i) {
+ params.add(String8(parameters[i].key.c_str()), String8(parameters[i].value.c_str()));
+ }
+ values->setTo(params.toString());
+}
+
+ConversionHelperHidl::ConversionHelperHidl(const char* className)
+ : mClassName(className) {
+}
+
+// static
+status_t ConversionHelperHidl::analyzeResult(const Result& result) {
+ switch (result) {
+ case Result::OK: return OK;
+ case Result::INVALID_ARGUMENTS: return BAD_VALUE;
+ case Result::INVALID_STATE: return NOT_ENOUGH_DATA;
+ case Result::NOT_INITIALIZED: return NO_INIT;
+ case Result::NOT_SUPPORTED: return INVALID_OPERATION;
+ default: return NO_INIT;
+ }
+}
+
+void ConversionHelperHidl::emitError(const char* funcName, const char* description) {
+ ALOGE("%s %p %s: %s (from rpc)", mClassName, this, funcName, description);
+}
+
+} // namespace V4_0
+} // namespace android
diff --git a/media/libaudiohal/4.0/ConversionHelperHidl.h b/media/libaudiohal/4.0/ConversionHelperHidl.h
new file mode 100644
index 0000000..ddc8569
--- /dev/null
+++ b/media/libaudiohal/4.0/ConversionHelperHidl.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2016 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 ANDROID_HARDWARE_CONVERSION_HELPER_HIDL_4_0_H
+#define ANDROID_HARDWARE_CONVERSION_HELPER_HIDL_4_0_H
+
+#include <android/hardware/audio/4.0/types.h>
+#include <hidl/HidlSupport.h>
+#include <utils/String8.h>
+
+using ::android::hardware::audio::V4_0::ParameterValue;
+using ::android::hardware::Return;
+using ::android::hardware::hidl_string;
+using ::android::hardware::hidl_vec;
+
+namespace android {
+namespace V4_0 {
+
+class ConversionHelperHidl {
+ protected:
+ static status_t keysFromHal(const String8& keys, hidl_vec<hidl_string> *hidlKeys);
+ static status_t parametersFromHal(const String8& kvPairs, hidl_vec<ParameterValue> *hidlParams);
+ static void parametersToHal(const hidl_vec<ParameterValue>& parameters, String8 *values);
+
+ ConversionHelperHidl(const char* className);
+
+ template<typename R, typename T>
+ status_t processReturn(const char* funcName, const Return<R>& ret, T *retval) {
+ if (ret.isOk()) {
+ // This way it also works for enum class to unscoped enum conversion.
+ *retval = static_cast<T>(static_cast<R>(ret));
+ return OK;
+ }
+ return processReturn(funcName, ret);
+ }
+
+ template<typename T>
+ status_t processReturn(const char* funcName, const Return<T>& ret) {
+ if (!ret.isOk()) {
+ emitError(funcName, ret.description().c_str());
+ }
+ return ret.isOk() ? OK : FAILED_TRANSACTION;
+ }
+
+ status_t processReturn(const char* funcName, const Return<hardware::audio::V4_0::Result>& ret) {
+ if (!ret.isOk()) {
+ emitError(funcName, ret.description().c_str());
+ }
+ return ret.isOk() ? analyzeResult(ret) : FAILED_TRANSACTION;
+ }
+
+ template<typename T>
+ status_t processReturn(
+ const char* funcName, const Return<T>& ret, hardware::audio::V4_0::Result retval) {
+ if (!ret.isOk()) {
+ emitError(funcName, ret.description().c_str());
+ }
+ return ret.isOk() ? analyzeResult(retval) : FAILED_TRANSACTION;
+ }
+
+ private:
+ const char* mClassName;
+
+ static status_t analyzeResult(const hardware::audio::V4_0::Result& result);
+
+ void emitError(const char* funcName, const char* description);
+};
+
+} // namespace V4_0
+} // namespace android
+
+#endif // ANDROID_HARDWARE_CONVERSION_HELPER_HIDL_4_0_H
diff --git a/media/libaudiohal/4.0/DeviceHalHidl.cpp b/media/libaudiohal/4.0/DeviceHalHidl.cpp
new file mode 100644
index 0000000..8da1051
--- /dev/null
+++ b/media/libaudiohal/4.0/DeviceHalHidl.cpp
@@ -0,0 +1,372 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+
+#define LOG_TAG "DeviceHalHidl"
+//#define LOG_NDEBUG 0
+
+#include <android/hardware/audio/4.0/IPrimaryDevice.h>
+#include <cutils/native_handle.h>
+#include <hwbinder/IPCThreadState.h>
+#include <utils/Log.h>
+
+#include <common/all-versions/VersionUtils.h>
+
+#include "DeviceHalHidl.h"
+#include "HidlUtils.h"
+#include "StreamHalHidl.h"
+#include "VersionUtils.h"
+
+using ::android::hardware::audio::common::V4_0::AudioConfig;
+using ::android::hardware::audio::common::V4_0::AudioDevice;
+using ::android::hardware::audio::common::V4_0::AudioInputFlag;
+using ::android::hardware::audio::common::V4_0::AudioOutputFlag;
+using ::android::hardware::audio::common::V4_0::AudioPatchHandle;
+using ::android::hardware::audio::common::V4_0::AudioPort;
+using ::android::hardware::audio::common::V4_0::AudioPortConfig;
+using ::android::hardware::audio::common::V4_0::AudioMode;
+using ::android::hardware::audio::common::V4_0::AudioSource;
+using ::android::hardware::audio::common::V4_0::HidlUtils;
+using ::android::hardware::audio::common::utils::mkEnumConverter;
+using ::android::hardware::audio::V4_0::DeviceAddress;
+using ::android::hardware::audio::V4_0::IPrimaryDevice;
+using ::android::hardware::audio::V4_0::ParameterValue;
+using ::android::hardware::audio::V4_0::Result;
+using ::android::hardware::audio::V4_0::SinkMetadata;
+using ::android::hardware::hidl_string;
+using ::android::hardware::hidl_vec;
+
+namespace android {
+namespace V4_0 {
+
+namespace {
+
+status_t deviceAddressFromHal(
+ audio_devices_t device, const char* halAddress, DeviceAddress* address) {
+ address->device = AudioDevice(device);
+
+ if (address == nullptr || strnlen(halAddress, AUDIO_DEVICE_MAX_ADDRESS_LEN) == 0) {
+ return OK;
+ }
+ const bool isInput = (device & AUDIO_DEVICE_BIT_IN) != 0;
+ if (isInput) device &= ~AUDIO_DEVICE_BIT_IN;
+ if ((!isInput && (device & AUDIO_DEVICE_OUT_ALL_A2DP) != 0)
+ || (isInput && (device & AUDIO_DEVICE_IN_BLUETOOTH_A2DP) != 0)) {
+ int status = sscanf(halAddress,
+ "%hhX:%hhX:%hhX:%hhX:%hhX:%hhX",
+ &address->address.mac[0], &address->address.mac[1], &address->address.mac[2],
+ &address->address.mac[3], &address->address.mac[4], &address->address.mac[5]);
+ return status == 6 ? OK : BAD_VALUE;
+ } else if ((!isInput && (device & AUDIO_DEVICE_OUT_IP) != 0)
+ || (isInput && (device & AUDIO_DEVICE_IN_IP) != 0)) {
+ int status = sscanf(halAddress,
+ "%hhu.%hhu.%hhu.%hhu",
+ &address->address.ipv4[0], &address->address.ipv4[1],
+ &address->address.ipv4[2], &address->address.ipv4[3]);
+ return status == 4 ? OK : BAD_VALUE;
+ } else if ((!isInput && (device & AUDIO_DEVICE_OUT_ALL_USB)) != 0
+ || (isInput && (device & AUDIO_DEVICE_IN_ALL_USB)) != 0) {
+ int status = sscanf(halAddress,
+ "card=%d;device=%d",
+ &address->address.alsa.card, &address->address.alsa.device);
+ return status == 2 ? OK : BAD_VALUE;
+ } else if ((!isInput && (device & AUDIO_DEVICE_OUT_BUS) != 0)
+ || (isInput && (device & AUDIO_DEVICE_IN_BUS) != 0)) {
+ if (halAddress != NULL) {
+ address->busAddress = halAddress;
+ return OK;
+ }
+ return BAD_VALUE;
+ } else if ((!isInput && (device & AUDIO_DEVICE_OUT_REMOTE_SUBMIX)) != 0
+ || (isInput && (device & AUDIO_DEVICE_IN_REMOTE_SUBMIX) != 0)) {
+ if (halAddress != NULL) {
+ address->rSubmixAddress = halAddress;
+ return OK;
+ }
+ return BAD_VALUE;
+ }
+ return OK;
+}
+
+} // namespace
+
+DeviceHalHidl::DeviceHalHidl(const sp<IDevice>& device)
+ : ConversionHelperHidl("Device"), mDevice(device),
+ mPrimaryDevice(IPrimaryDevice::castFrom(device)) {
+}
+
+DeviceHalHidl::~DeviceHalHidl() {
+ if (mDevice != 0) {
+ mDevice.clear();
+ hardware::IPCThreadState::self()->flushCommands();
+ }
+}
+
+status_t DeviceHalHidl::getSupportedDevices(uint32_t*) {
+ // Obsolete.
+ return INVALID_OPERATION;
+}
+
+status_t DeviceHalHidl::initCheck() {
+ if (mDevice == 0) return NO_INIT;
+ return processReturn("initCheck", mDevice->initCheck());
+}
+
+status_t DeviceHalHidl::setVoiceVolume(float volume) {
+ if (mDevice == 0) return NO_INIT;
+ if (mPrimaryDevice == 0) return INVALID_OPERATION;
+ return processReturn("setVoiceVolume", mPrimaryDevice->setVoiceVolume(volume));
+}
+
+status_t DeviceHalHidl::setMasterVolume(float volume) {
+ if (mDevice == 0) return NO_INIT;
+ if (mPrimaryDevice == 0) return INVALID_OPERATION;
+ return processReturn("setMasterVolume", mPrimaryDevice->setMasterVolume(volume));
+}
+
+status_t DeviceHalHidl::getMasterVolume(float *volume) {
+ if (mDevice == 0) return NO_INIT;
+ if (mPrimaryDevice == 0) return INVALID_OPERATION;
+ Result retval;
+ Return<void> ret = mPrimaryDevice->getMasterVolume(
+ [&](Result r, float v) {
+ retval = r;
+ if (retval == Result::OK) {
+ *volume = v;
+ }
+ });
+ return processReturn("getMasterVolume", ret, retval);
+}
+
+status_t DeviceHalHidl::setMode(audio_mode_t mode) {
+ if (mDevice == 0) return NO_INIT;
+ if (mPrimaryDevice == 0) return INVALID_OPERATION;
+ return processReturn("setMode", mPrimaryDevice->setMode(AudioMode(mode)));
+}
+
+status_t DeviceHalHidl::setMicMute(bool state) {
+ if (mDevice == 0) return NO_INIT;
+ return processReturn("setMicMute", mDevice->setMicMute(state));
+}
+
+status_t DeviceHalHidl::getMicMute(bool *state) {
+ if (mDevice == 0) return NO_INIT;
+ Result retval;
+ Return<void> ret = mDevice->getMicMute(
+ [&](Result r, bool mute) {
+ retval = r;
+ if (retval == Result::OK) {
+ *state = mute;
+ }
+ });
+ return processReturn("getMicMute", ret, retval);
+}
+
+status_t DeviceHalHidl::setMasterMute(bool state) {
+ if (mDevice == 0) return NO_INIT;
+ return processReturn("setMasterMute", mDevice->setMasterMute(state));
+}
+
+status_t DeviceHalHidl::getMasterMute(bool *state) {
+ if (mDevice == 0) return NO_INIT;
+ Result retval;
+ Return<void> ret = mDevice->getMasterMute(
+ [&](Result r, bool mute) {
+ retval = r;
+ if (retval == Result::OK) {
+ *state = mute;
+ }
+ });
+ return processReturn("getMasterMute", ret, retval);
+}
+
+status_t DeviceHalHidl::setParameters(const String8& kvPairs) {
+ if (mDevice == 0) return NO_INIT;
+ hidl_vec<ParameterValue> hidlParams;
+ status_t status = parametersFromHal(kvPairs, &hidlParams);
+ if (status != OK) return status;
+ // TODO: change the API so that context and kvPairs are separated
+ return processReturn("setParameters",
+ utils::setParameters(mDevice, {} /* context */, hidlParams));
+}
+
+status_t DeviceHalHidl::getParameters(const String8& keys, String8 *values) {
+ values->clear();
+ if (mDevice == 0) return NO_INIT;
+ hidl_vec<hidl_string> hidlKeys;
+ status_t status = keysFromHal(keys, &hidlKeys);
+ if (status != OK) return status;
+ Result retval;
+ Return<void> ret = utils::getParameters(mDevice,
+ {} /* context */,
+ hidlKeys,
+ [&](Result r, const hidl_vec<ParameterValue>& parameters) {
+ retval = r;
+ if (retval == Result::OK) {
+ parametersToHal(parameters, values);
+ }
+ });
+ return processReturn("getParameters", ret, retval);
+}
+
+status_t DeviceHalHidl::getInputBufferSize(
+ const struct audio_config *config, size_t *size) {
+ if (mDevice == 0) return NO_INIT;
+ AudioConfig hidlConfig;
+ HidlUtils::audioConfigFromHal(*config, &hidlConfig);
+ Result retval;
+ Return<void> ret = mDevice->getInputBufferSize(
+ hidlConfig,
+ [&](Result r, uint64_t bufferSize) {
+ retval = r;
+ if (retval == Result::OK) {
+ *size = static_cast<size_t>(bufferSize);
+ }
+ });
+ return processReturn("getInputBufferSize", ret, retval);
+}
+
+status_t DeviceHalHidl::openOutputStream(
+ audio_io_handle_t handle,
+ audio_devices_t devices,
+ audio_output_flags_t flags,
+ struct audio_config *config,
+ const char *address,
+ sp<StreamOutHalInterface> *outStream) {
+ if (mDevice == 0) return NO_INIT;
+ DeviceAddress hidlDevice;
+ status_t status = deviceAddressFromHal(devices, address, &hidlDevice);
+ if (status != OK) return status;
+ AudioConfig hidlConfig;
+ HidlUtils::audioConfigFromHal(*config, &hidlConfig);
+ Result retval = Result::NOT_INITIALIZED;
+ Return<void> ret = mDevice->openOutputStream(
+ handle,
+ hidlDevice,
+ hidlConfig,
+ mkEnumConverter<AudioOutputFlag>(flags),
+ {} /* metadata */,
+ [&](Result r, const sp<IStreamOut>& result, const AudioConfig& suggestedConfig) {
+ retval = r;
+ if (retval == Result::OK) {
+ *outStream = new StreamOutHalHidl(result);
+ }
+ HidlUtils::audioConfigToHal(suggestedConfig, config);
+ });
+ return processReturn("openOutputStream", ret, retval);
+}
+
+status_t DeviceHalHidl::openInputStream(
+ audio_io_handle_t handle,
+ audio_devices_t devices,
+ struct audio_config *config,
+ audio_input_flags_t flags,
+ const char *address,
+ audio_source_t source,
+ sp<StreamInHalInterface> *inStream) {
+ if (mDevice == 0) return NO_INIT;
+ DeviceAddress hidlDevice;
+ status_t status = deviceAddressFromHal(devices, address, &hidlDevice);
+ if (status != OK) return status;
+ AudioConfig hidlConfig;
+ HidlUtils::audioConfigFromHal(*config, &hidlConfig);
+ Result retval = Result::NOT_INITIALIZED;
+ // TODO: correctly propagate the tracks sources and volume
+ // for now, only send the main source at 1dbfs
+ SinkMetadata metadata = {{{AudioSource(source), 1}}};
+ Return<void> ret = mDevice->openInputStream(
+ handle,
+ hidlDevice,
+ hidlConfig,
+ flags,
+ metadata,
+ [&](Result r, const sp<IStreamIn>& result, const AudioConfig& suggestedConfig) {
+ retval = r;
+ if (retval == Result::OK) {
+ *inStream = new StreamInHalHidl(result);
+ }
+ HidlUtils::audioConfigToHal(suggestedConfig, config);
+ });
+ return processReturn("openInputStream", ret, retval);
+}
+
+status_t DeviceHalHidl::supportsAudioPatches(bool *supportsPatches) {
+ if (mDevice == 0) return NO_INIT;
+ return processReturn("supportsAudioPatches", mDevice->supportsAudioPatches(), supportsPatches);
+}
+
+status_t DeviceHalHidl::createAudioPatch(
+ unsigned int num_sources,
+ const struct audio_port_config *sources,
+ unsigned int num_sinks,
+ const struct audio_port_config *sinks,
+ audio_patch_handle_t *patch) {
+ if (mDevice == 0) return NO_INIT;
+ hidl_vec<AudioPortConfig> hidlSources, hidlSinks;
+ HidlUtils::audioPortConfigsFromHal(num_sources, sources, &hidlSources);
+ HidlUtils::audioPortConfigsFromHal(num_sinks, sinks, &hidlSinks);
+ Result retval;
+ Return<void> ret = mDevice->createAudioPatch(
+ hidlSources, hidlSinks,
+ [&](Result r, AudioPatchHandle hidlPatch) {
+ retval = r;
+ if (retval == Result::OK) {
+ *patch = static_cast<audio_patch_handle_t>(hidlPatch);
+ }
+ });
+ return processReturn("createAudioPatch", ret, retval);
+}
+
+status_t DeviceHalHidl::releaseAudioPatch(audio_patch_handle_t patch) {
+ if (mDevice == 0) return NO_INIT;
+ return processReturn("releaseAudioPatch", mDevice->releaseAudioPatch(patch));
+}
+
+status_t DeviceHalHidl::getAudioPort(struct audio_port *port) {
+ if (mDevice == 0) return NO_INIT;
+ AudioPort hidlPort;
+ HidlUtils::audioPortFromHal(*port, &hidlPort);
+ Result retval;
+ Return<void> ret = mDevice->getAudioPort(
+ hidlPort,
+ [&](Result r, const AudioPort& p) {
+ retval = r;
+ if (retval == Result::OK) {
+ HidlUtils::audioPortToHal(p, port);
+ }
+ });
+ return processReturn("getAudioPort", ret, retval);
+}
+
+status_t DeviceHalHidl::setAudioPortConfig(const struct audio_port_config *config) {
+ if (mDevice == 0) return NO_INIT;
+ AudioPortConfig hidlConfig;
+ HidlUtils::audioPortConfigFromHal(*config, &hidlConfig);
+ return processReturn("setAudioPortConfig", mDevice->setAudioPortConfig(hidlConfig));
+}
+
+status_t DeviceHalHidl::dump(int fd) {
+ if (mDevice == 0) return NO_INIT;
+ native_handle_t* hidlHandle = native_handle_create(1, 0);
+ hidlHandle->data[0] = fd;
+ Return<void> ret = mDevice->debug(hidlHandle, {} /* options */);
+ native_handle_delete(hidlHandle);
+ return processReturn("dump", ret);
+}
+
+} // namespace V4_0
+} // namespace android
diff --git a/media/libaudiohal/4.0/DeviceHalHidl.h b/media/libaudiohal/4.0/DeviceHalHidl.h
new file mode 100644
index 0000000..f460add
--- /dev/null
+++ b/media/libaudiohal/4.0/DeviceHalHidl.h
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2016 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 ANDROID_HARDWARE_DEVICE_HAL_HIDL_4_0_H
+#define ANDROID_HARDWARE_DEVICE_HAL_HIDL_4_0_H
+
+#include <android/hardware/audio/4.0/IDevice.h>
+#include <android/hardware/audio/4.0/IPrimaryDevice.h>
+#include <media/audiohal/DeviceHalInterface.h>
+
+#include "ConversionHelperHidl.h"
+
+using ::android::hardware::audio::V4_0::IDevice;
+using ::android::hardware::audio::V4_0::IPrimaryDevice;
+using ::android::hardware::Return;
+
+namespace android {
+namespace V4_0 {
+
+class DeviceHalHidl : public DeviceHalInterface, public ConversionHelperHidl
+{
+ public:
+ // Sets the value of 'devices' to a bitmask of 1 or more values of audio_devices_t.
+ virtual status_t getSupportedDevices(uint32_t *devices);
+
+ // Check to see if the audio hardware interface has been initialized.
+ virtual status_t initCheck();
+
+ // Set the audio volume of a voice call. Range is between 0.0 and 1.0.
+ virtual status_t setVoiceVolume(float volume);
+
+ // Set the audio volume for all audio activities other than voice call.
+ virtual status_t setMasterVolume(float volume);
+
+ // Get the current master volume value for the HAL.
+ virtual status_t getMasterVolume(float *volume);
+
+ // Called when the audio mode changes.
+ virtual status_t setMode(audio_mode_t mode);
+
+ // Muting control.
+ virtual status_t setMicMute(bool state);
+ virtual status_t getMicMute(bool *state);
+ virtual status_t setMasterMute(bool state);
+ virtual status_t getMasterMute(bool *state);
+
+ // Set global audio parameters.
+ virtual status_t setParameters(const String8& kvPairs);
+
+ // Get global audio parameters.
+ virtual status_t getParameters(const String8& keys, String8 *values);
+
+ // Returns audio input buffer size according to parameters passed.
+ virtual status_t getInputBufferSize(const struct audio_config *config,
+ size_t *size);
+
+ // Creates and opens the audio hardware output stream. The stream is closed
+ // by releasing all references to the returned object.
+ virtual status_t openOutputStream(
+ audio_io_handle_t handle,
+ audio_devices_t devices,
+ audio_output_flags_t flags,
+ struct audio_config *config,
+ const char *address,
+ sp<StreamOutHalInterface> *outStream);
+
+ // Creates and opens the audio hardware input stream. The stream is closed
+ // by releasing all references to the returned object.
+ virtual status_t openInputStream(
+ audio_io_handle_t handle,
+ audio_devices_t devices,
+ struct audio_config *config,
+ audio_input_flags_t flags,
+ const char *address,
+ audio_source_t source,
+ sp<StreamInHalInterface> *inStream);
+
+ // Returns whether createAudioPatch and releaseAudioPatch operations are supported.
+ virtual status_t supportsAudioPatches(bool *supportsPatches);
+
+ // Creates an audio patch between several source and sink ports.
+ virtual status_t createAudioPatch(
+ unsigned int num_sources,
+ const struct audio_port_config *sources,
+ unsigned int num_sinks,
+ const struct audio_port_config *sinks,
+ audio_patch_handle_t *patch);
+
+ // Releases an audio patch.
+ virtual status_t releaseAudioPatch(audio_patch_handle_t patch);
+
+ // Fills the list of supported attributes for a given audio port.
+ virtual status_t getAudioPort(struct audio_port *port);
+
+ // Set audio port configuration.
+ virtual status_t setAudioPortConfig(const struct audio_port_config *config);
+
+ virtual status_t dump(int fd);
+
+ private:
+ friend class DevicesFactoryHalHidl;
+ sp<IDevice> mDevice;
+ sp<IPrimaryDevice> mPrimaryDevice; // Null if it's not a primary device.
+
+ // Can not be constructed directly by clients.
+ explicit DeviceHalHidl(const sp<IDevice>& device);
+
+ // The destructor automatically closes the device.
+ virtual ~DeviceHalHidl();
+};
+
+} // namespace V4_0
+} // namespace android
+
+#endif // ANDROID_HARDWARE_DEVICE_HAL_HIDL_4_0_H
diff --git a/media/libaudiohal/4.0/DeviceHalLocal.cpp b/media/libaudiohal/4.0/DeviceHalLocal.cpp
new file mode 100644
index 0000000..e64eee1
--- /dev/null
+++ b/media/libaudiohal/4.0/DeviceHalLocal.cpp
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2016 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_TAG "DeviceHalLocal"
+//#define LOG_NDEBUG 0
+
+#include <utils/Log.h>
+
+#include "DeviceHalLocal.h"
+#include "StreamHalLocal.h"
+
+namespace android {
+namespace V4_0 {
+
+DeviceHalLocal::DeviceHalLocal(audio_hw_device_t *dev)
+ : mDev(dev) {
+}
+
+DeviceHalLocal::~DeviceHalLocal() {
+ int status = audio_hw_device_close(mDev);
+ ALOGW_IF(status, "Error closing audio hw device %p: %s", mDev, strerror(-status));
+ mDev = 0;
+}
+
+status_t DeviceHalLocal::getSupportedDevices(uint32_t *devices) {
+ if (mDev->get_supported_devices == NULL) return INVALID_OPERATION;
+ *devices = mDev->get_supported_devices(mDev);
+ return OK;
+}
+
+status_t DeviceHalLocal::initCheck() {
+ return mDev->init_check(mDev);
+}
+
+status_t DeviceHalLocal::setVoiceVolume(float volume) {
+ return mDev->set_voice_volume(mDev, volume);
+}
+
+status_t DeviceHalLocal::setMasterVolume(float volume) {
+ if (mDev->set_master_volume == NULL) return INVALID_OPERATION;
+ return mDev->set_master_volume(mDev, volume);
+}
+
+status_t DeviceHalLocal::getMasterVolume(float *volume) {
+ if (mDev->get_master_volume == NULL) return INVALID_OPERATION;
+ return mDev->get_master_volume(mDev, volume);
+}
+
+status_t DeviceHalLocal::setMode(audio_mode_t mode) {
+ return mDev->set_mode(mDev, mode);
+}
+
+status_t DeviceHalLocal::setMicMute(bool state) {
+ return mDev->set_mic_mute(mDev, state);
+}
+
+status_t DeviceHalLocal::getMicMute(bool *state) {
+ return mDev->get_mic_mute(mDev, state);
+}
+
+status_t DeviceHalLocal::setMasterMute(bool state) {
+ if (mDev->set_master_mute == NULL) return INVALID_OPERATION;
+ return mDev->set_master_mute(mDev, state);
+}
+
+status_t DeviceHalLocal::getMasterMute(bool *state) {
+ if (mDev->get_master_mute == NULL) return INVALID_OPERATION;
+ return mDev->get_master_mute(mDev, state);
+}
+
+status_t DeviceHalLocal::setParameters(const String8& kvPairs) {
+ return mDev->set_parameters(mDev, kvPairs.string());
+}
+
+status_t DeviceHalLocal::getParameters(const String8& keys, String8 *values) {
+ char *halValues = mDev->get_parameters(mDev, keys.string());
+ if (halValues != NULL) {
+ values->setTo(halValues);
+ free(halValues);
+ } else {
+ values->clear();
+ }
+ return OK;
+}
+
+status_t DeviceHalLocal::getInputBufferSize(
+ const struct audio_config *config, size_t *size) {
+ *size = mDev->get_input_buffer_size(mDev, config);
+ return OK;
+}
+
+status_t DeviceHalLocal::openOutputStream(
+ audio_io_handle_t handle,
+ audio_devices_t devices,
+ audio_output_flags_t flags,
+ struct audio_config *config,
+ const char *address,
+ sp<StreamOutHalInterface> *outStream) {
+ audio_stream_out_t *halStream;
+ ALOGV("open_output_stream handle: %d devices: %x flags: %#x"
+ "srate: %d format %#x channels %x address %s",
+ handle, devices, flags,
+ config->sample_rate, config->format, config->channel_mask,
+ address);
+ int openResut = mDev->open_output_stream(
+ mDev, handle, devices, flags, config, &halStream, address);
+ if (openResut == OK) {
+ *outStream = new StreamOutHalLocal(halStream, this);
+ }
+ ALOGV("open_output_stream status %d stream %p", openResut, halStream);
+ return openResut;
+}
+
+status_t DeviceHalLocal::openInputStream(
+ audio_io_handle_t handle,
+ audio_devices_t devices,
+ struct audio_config *config,
+ audio_input_flags_t flags,
+ const char *address,
+ audio_source_t source,
+ sp<StreamInHalInterface> *inStream) {
+ audio_stream_in_t *halStream;
+ ALOGV("open_input_stream handle: %d devices: %x flags: %#x "
+ "srate: %d format %#x channels %x address %s source %d",
+ handle, devices, flags,
+ config->sample_rate, config->format, config->channel_mask,
+ address, source);
+ int openResult = mDev->open_input_stream(
+ mDev, handle, devices, config, &halStream, flags, address, source);
+ if (openResult == OK) {
+ *inStream = new StreamInHalLocal(halStream, this);
+ }
+ ALOGV("open_input_stream status %d stream %p", openResult, inStream);
+ return openResult;
+}
+
+status_t DeviceHalLocal::supportsAudioPatches(bool *supportsPatches) {
+ *supportsPatches = version() >= AUDIO_DEVICE_API_VERSION_3_0;
+ return OK;
+}
+
+status_t DeviceHalLocal::createAudioPatch(
+ unsigned int num_sources,
+ const struct audio_port_config *sources,
+ unsigned int num_sinks,
+ const struct audio_port_config *sinks,
+ audio_patch_handle_t *patch) {
+ if (version() >= AUDIO_DEVICE_API_VERSION_3_0) {
+ return mDev->create_audio_patch(
+ mDev, num_sources, sources, num_sinks, sinks, patch);
+ } else {
+ return INVALID_OPERATION;
+ }
+}
+
+status_t DeviceHalLocal::releaseAudioPatch(audio_patch_handle_t patch) {
+ if (version() >= AUDIO_DEVICE_API_VERSION_3_0) {
+ return mDev->release_audio_patch(mDev, patch);
+ } else {
+ return INVALID_OPERATION;
+ }
+}
+
+status_t DeviceHalLocal::getAudioPort(struct audio_port *port) {
+ return mDev->get_audio_port(mDev, port);
+}
+
+status_t DeviceHalLocal::setAudioPortConfig(const struct audio_port_config *config) {
+ if (version() >= AUDIO_DEVICE_API_VERSION_3_0)
+ return mDev->set_audio_port_config(mDev, config);
+ else
+ return INVALID_OPERATION;
+}
+
+status_t DeviceHalLocal::dump(int fd) {
+ return mDev->dump(mDev, fd);
+}
+
+void DeviceHalLocal::closeOutputStream(struct audio_stream_out *stream_out) {
+ mDev->close_output_stream(mDev, stream_out);
+}
+
+void DeviceHalLocal::closeInputStream(struct audio_stream_in *stream_in) {
+ mDev->close_input_stream(mDev, stream_in);
+}
+
+} // namespace V4_0
+} // namespace android
diff --git a/media/libaudiohal/4.0/DeviceHalLocal.h b/media/libaudiohal/4.0/DeviceHalLocal.h
new file mode 100644
index 0000000..daafdc7
--- /dev/null
+++ b/media/libaudiohal/4.0/DeviceHalLocal.h
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2016 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 ANDROID_HARDWARE_DEVICE_HAL_LOCAL_4_0_H
+#define ANDROID_HARDWARE_DEVICE_HAL_LOCAL_4_0_H
+
+#include <hardware/audio.h>
+#include <media/audiohal/DeviceHalInterface.h>
+
+namespace android {
+namespace V4_0 {
+
+class DeviceHalLocal : public DeviceHalInterface
+{
+ public:
+ // Sets the value of 'devices' to a bitmask of 1 or more values of audio_devices_t.
+ virtual status_t getSupportedDevices(uint32_t *devices);
+
+ // Check to see if the audio hardware interface has been initialized.
+ virtual status_t initCheck();
+
+ // Set the audio volume of a voice call. Range is between 0.0 and 1.0.
+ virtual status_t setVoiceVolume(float volume);
+
+ // Set the audio volume for all audio activities other than voice call.
+ virtual status_t setMasterVolume(float volume);
+
+ // Get the current master volume value for the HAL.
+ virtual status_t getMasterVolume(float *volume);
+
+ // Called when the audio mode changes.
+ virtual status_t setMode(audio_mode_t mode);
+
+ // Muting control.
+ virtual status_t setMicMute(bool state);
+ virtual status_t getMicMute(bool *state);
+ virtual status_t setMasterMute(bool state);
+ virtual status_t getMasterMute(bool *state);
+
+ // Set global audio parameters.
+ virtual status_t setParameters(const String8& kvPairs);
+
+ // Get global audio parameters.
+ virtual status_t getParameters(const String8& keys, String8 *values);
+
+ // Returns audio input buffer size according to parameters passed.
+ virtual status_t getInputBufferSize(const struct audio_config *config,
+ size_t *size);
+
+ // Creates and opens the audio hardware output stream. The stream is closed
+ // by releasing all references to the returned object.
+ virtual status_t openOutputStream(
+ audio_io_handle_t handle,
+ audio_devices_t devices,
+ audio_output_flags_t flags,
+ struct audio_config *config,
+ const char *address,
+ sp<StreamOutHalInterface> *outStream);
+
+ // Creates and opens the audio hardware input stream. The stream is closed
+ // by releasing all references to the returned object.
+ virtual status_t openInputStream(
+ audio_io_handle_t handle,
+ audio_devices_t devices,
+ struct audio_config *config,
+ audio_input_flags_t flags,
+ const char *address,
+ audio_source_t source,
+ sp<StreamInHalInterface> *inStream);
+
+ // Returns whether createAudioPatch and releaseAudioPatch operations are supported.
+ virtual status_t supportsAudioPatches(bool *supportsPatches);
+
+ // Creates an audio patch between several source and sink ports.
+ virtual status_t createAudioPatch(
+ unsigned int num_sources,
+ const struct audio_port_config *sources,
+ unsigned int num_sinks,
+ const struct audio_port_config *sinks,
+ audio_patch_handle_t *patch);
+
+ // Releases an audio patch.
+ virtual status_t releaseAudioPatch(audio_patch_handle_t patch);
+
+ // Fills the list of supported attributes for a given audio port.
+ virtual status_t getAudioPort(struct audio_port *port);
+
+ // Set audio port configuration.
+ virtual status_t setAudioPortConfig(const struct audio_port_config *config);
+
+ virtual status_t dump(int fd);
+
+ void closeOutputStream(struct audio_stream_out *stream_out);
+ void closeInputStream(struct audio_stream_in *stream_in);
+
+ private:
+ audio_hw_device_t *mDev;
+
+ friend class DevicesFactoryHalLocal;
+
+ // Can not be constructed directly by clients.
+ explicit DeviceHalLocal(audio_hw_device_t *dev);
+
+ // The destructor automatically closes the device.
+ virtual ~DeviceHalLocal();
+
+ uint32_t version() const { return mDev->common.version; }
+};
+
+} // namespace V4_0
+} // namespace android
+
+#endif // ANDROID_HARDWARE_DEVICE_HAL_LOCAL_4_0_H
diff --git a/media/libaudiohal/4.0/DevicesFactoryHalHidl.cpp b/media/libaudiohal/4.0/DevicesFactoryHalHidl.cpp
new file mode 100644
index 0000000..c83194e
--- /dev/null
+++ b/media/libaudiohal/4.0/DevicesFactoryHalHidl.cpp
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <string.h>
+
+#define LOG_TAG "DevicesFactoryHalHidl"
+//#define LOG_NDEBUG 0
+
+#include <android/hardware/audio/4.0/IDevice.h>
+#include <media/audiohal/hidl/HalDeathHandler.h>
+#include <utils/Log.h>
+
+#include "ConversionHelperHidl.h"
+#include "DeviceHalHidl.h"
+#include "DevicesFactoryHalHidl.h"
+
+using ::android::hardware::audio::V4_0::IDevice;
+using ::android::hardware::audio::V4_0::Result;
+using ::android::hardware::Return;
+
+namespace android {
+namespace V4_0 {
+
+DevicesFactoryHalHidl::DevicesFactoryHalHidl() {
+ mDevicesFactory = IDevicesFactory::getService();
+ if (mDevicesFactory != 0) {
+ // It is assumed that DevicesFactory is owned by AudioFlinger
+ // and thus have the same lifespan.
+ mDevicesFactory->linkToDeath(HalDeathHandler::getInstance(), 0 /*cookie*/);
+ } else {
+ ALOGE("Failed to obtain IDevicesFactory service, terminating process.");
+ exit(1);
+ }
+ // The MSD factory is optional
+ mDevicesFactoryMsd = IDevicesFactory::getService(AUDIO_HAL_SERVICE_NAME_MSD);
+ // TODO: Register death handler, and add 'restart' directive to audioserver.rc
+}
+
+DevicesFactoryHalHidl::~DevicesFactoryHalHidl() {
+}
+
+status_t DevicesFactoryHalHidl::openDevice(const char *name, sp<DeviceHalInterface> *device) {
+ if (mDevicesFactory == 0) return NO_INIT;
+ Result retval = Result::NOT_INITIALIZED;
+ Return<void> ret = mDevicesFactory->openDevice(
+ name,
+ [&](Result r, const sp<IDevice>& result) {
+ retval = r;
+ if (retval == Result::OK) {
+ *device = new DeviceHalHidl(result);
+ }
+ });
+ if (ret.isOk()) {
+ if (retval == Result::OK) return OK;
+ else if (retval == Result::INVALID_ARGUMENTS) return BAD_VALUE;
+ else return NO_INIT;
+ }
+ return FAILED_TRANSACTION;
+}
+
+} // namespace V4_0
+} // namespace android
diff --git a/media/libaudiohal/4.0/DevicesFactoryHalHidl.h b/media/libaudiohal/4.0/DevicesFactoryHalHidl.h
new file mode 100644
index 0000000..114889b
--- /dev/null
+++ b/media/libaudiohal/4.0/DevicesFactoryHalHidl.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2016 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 ANDROID_HARDWARE_DEVICES_FACTORY_HAL_HIDL_4_0_H
+#define ANDROID_HARDWARE_DEVICES_FACTORY_HAL_HIDL_4_0_H
+
+#include <android/hardware/audio/4.0/IDevicesFactory.h>
+#include <media/audiohal/DevicesFactoryHalInterface.h>
+#include <utils/Errors.h>
+#include <utils/RefBase.h>
+
+#include "DeviceHalHidl.h"
+
+using ::android::hardware::audio::V4_0::IDevicesFactory;
+
+namespace android {
+namespace V4_0 {
+
+class DevicesFactoryHalHidl : public DevicesFactoryHalInterface
+{
+ public:
+ // Opens a device with the specified name. To close the device, it is
+ // necessary to release references to the returned object.
+ virtual status_t openDevice(const char *name, sp<DeviceHalInterface> *device);
+
+ private:
+ friend class DevicesFactoryHalHybrid;
+
+ sp<IDevicesFactory> mDevicesFactory;
+ sp<IDevicesFactory> mDevicesFactoryMsd;
+
+ // Can not be constructed directly by clients.
+ DevicesFactoryHalHidl();
+
+ virtual ~DevicesFactoryHalHidl();
+};
+
+} // namespace V4_0
+} // namespace android
+
+#endif // ANDROID_HARDWARE_DEVICES_FACTORY_HAL_HIDL_4_0_H
diff --git a/media/libaudiohal/4.0/DevicesFactoryHalHybrid.cpp b/media/libaudiohal/4.0/DevicesFactoryHalHybrid.cpp
new file mode 100644
index 0000000..7ff1ec7d
--- /dev/null
+++ b/media/libaudiohal/4.0/DevicesFactoryHalHybrid.cpp
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2017 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_TAG "DevicesFactoryHalHybrid"
+//#define LOG_NDEBUG 0
+
+#include <libaudiohal/4.0/DevicesFactoryHalHybrid.h>
+#include "DevicesFactoryHalLocal.h"
+#include "DevicesFactoryHalHidl.h"
+
+namespace android {
+namespace V4_0 {
+
+DevicesFactoryHalHybrid::DevicesFactoryHalHybrid()
+ : mLocalFactory(new DevicesFactoryHalLocal()),
+ mHidlFactory(new DevicesFactoryHalHidl()) {
+}
+
+DevicesFactoryHalHybrid::~DevicesFactoryHalHybrid() {
+}
+
+status_t DevicesFactoryHalHybrid::openDevice(const char *name, sp<DeviceHalInterface> *device) {
+ 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);
+}
+
+} // namespace V4_0
+} // namespace android
diff --git a/media/libaudiohal/4.0/DevicesFactoryHalLocal.cpp b/media/libaudiohal/4.0/DevicesFactoryHalLocal.cpp
new file mode 100644
index 0000000..e54edd4
--- /dev/null
+++ b/media/libaudiohal/4.0/DevicesFactoryHalLocal.cpp
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2016 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_TAG "DevicesFactoryHalLocal"
+//#define LOG_NDEBUG 0
+
+#include <string.h>
+
+#include <hardware/audio.h>
+#include <utils/Log.h>
+
+#include "DeviceHalLocal.h"
+#include "DevicesFactoryHalLocal.h"
+
+namespace android {
+namespace V4_0 {
+
+static status_t load_audio_interface(const char *if_name, audio_hw_device_t **dev)
+{
+ const hw_module_t *mod;
+ int rc;
+
+ rc = hw_get_module_by_class(AUDIO_HARDWARE_MODULE_ID, if_name, &mod);
+ if (rc) {
+ ALOGE("%s couldn't load audio hw module %s.%s (%s)", __func__,
+ AUDIO_HARDWARE_MODULE_ID, if_name, strerror(-rc));
+ goto out;
+ }
+ rc = audio_hw_device_open(mod, dev);
+ if (rc) {
+ ALOGE("%s couldn't open audio hw device in %s.%s (%s)", __func__,
+ AUDIO_HARDWARE_MODULE_ID, if_name, strerror(-rc));
+ goto out;
+ }
+ if ((*dev)->common.version < AUDIO_DEVICE_API_VERSION_MIN) {
+ ALOGE("%s wrong audio hw device version %04x", __func__, (*dev)->common.version);
+ rc = BAD_VALUE;
+ audio_hw_device_close(*dev);
+ goto out;
+ }
+ return OK;
+
+out:
+ *dev = NULL;
+ return rc;
+}
+
+status_t DevicesFactoryHalLocal::openDevice(const char *name, sp<DeviceHalInterface> *device) {
+ audio_hw_device_t *dev;
+ status_t rc = load_audio_interface(name, &dev);
+ if (rc == OK) {
+ *device = new DeviceHalLocal(dev);
+ }
+ return rc;
+}
+
+} // namespace V4_0
+} // namespace android
diff --git a/media/libaudiohal/4.0/DevicesFactoryHalLocal.h b/media/libaudiohal/4.0/DevicesFactoryHalLocal.h
new file mode 100644
index 0000000..bc1c521
--- /dev/null
+++ b/media/libaudiohal/4.0/DevicesFactoryHalLocal.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2016 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 ANDROID_HARDWARE_DEVICES_FACTORY_HAL_LOCAL_4_0_H
+#define ANDROID_HARDWARE_DEVICES_FACTORY_HAL_LOCAL_4_0_H
+
+#include <media/audiohal/DevicesFactoryHalInterface.h>
+#include <utils/Errors.h>
+#include <utils/RefBase.h>
+
+#include "DeviceHalLocal.h"
+
+namespace android {
+namespace V4_0 {
+
+class DevicesFactoryHalLocal : public DevicesFactoryHalInterface
+{
+ public:
+ // Opens a device with the specified name. To close the device, it is
+ // necessary to release references to the returned object.
+ virtual status_t openDevice(const char *name, sp<DeviceHalInterface> *device);
+
+ private:
+ friend class DevicesFactoryHalHybrid;
+
+ // Can not be constructed directly by clients.
+ DevicesFactoryHalLocal() {}
+
+ virtual ~DevicesFactoryHalLocal() {}
+};
+
+} // namespace V4_0
+} // namespace android
+
+#endif // ANDROID_HARDWARE_DEVICES_FACTORY_HAL_LOCAL_4_0_H
diff --git a/media/libaudiohal/4.0/EffectBufferHalHidl.cpp b/media/libaudiohal/4.0/EffectBufferHalHidl.cpp
new file mode 100644
index 0000000..957c89f
--- /dev/null
+++ b/media/libaudiohal/4.0/EffectBufferHalHidl.cpp
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <atomic>
+
+#define LOG_TAG "EffectBufferHalHidl"
+//#define LOG_NDEBUG 0
+
+#include <android/hidl/allocator/1.0/IAllocator.h>
+#include <hidlmemory/mapping.h>
+#include <utils/Log.h>
+
+#include "ConversionHelperHidl.h"
+#include "EffectBufferHalHidl.h"
+
+using ::android::hardware::Return;
+using ::android::hidl::allocator::V1_0::IAllocator;
+
+namespace android {
+namespace V4_0 {
+
+// static
+uint64_t EffectBufferHalHidl::makeUniqueId() {
+ static std::atomic<uint64_t> counter{1};
+ return counter++;
+}
+
+status_t EffectBufferHalHidl::allocate(
+ size_t size, sp<EffectBufferHalInterface>* buffer) {
+ return mirror(nullptr, size, buffer);
+}
+
+status_t EffectBufferHalHidl::mirror(
+ void* external, size_t size, sp<EffectBufferHalInterface>* buffer) {
+ sp<EffectBufferHalInterface> tempBuffer = new EffectBufferHalHidl(size);
+ status_t result = static_cast<EffectBufferHalHidl*>(tempBuffer.get())->init();
+ if (result == OK) {
+ tempBuffer->setExternalData(external);
+ *buffer = tempBuffer;
+ }
+ return result;
+}
+
+EffectBufferHalHidl::EffectBufferHalHidl(size_t size)
+ : mBufferSize(size), mFrameCountChanged(false),
+ mExternalData(nullptr), mAudioBuffer{0, {nullptr}} {
+ mHidlBuffer.id = makeUniqueId();
+ mHidlBuffer.frameCount = 0;
+}
+
+EffectBufferHalHidl::~EffectBufferHalHidl() {
+}
+
+status_t EffectBufferHalHidl::init() {
+ sp<IAllocator> ashmem = IAllocator::getService("ashmem");
+ if (ashmem == 0) {
+ ALOGE("Failed to retrieve ashmem allocator service");
+ return NO_INIT;
+ }
+ status_t retval = NO_MEMORY;
+ Return<void> result = ashmem->allocate(
+ mBufferSize,
+ [&](bool success, const hidl_memory& memory) {
+ if (success) {
+ mHidlBuffer.data = memory;
+ retval = OK;
+ }
+ });
+ if (result.isOk() && retval == OK) {
+ mMemory = hardware::mapMemory(mHidlBuffer.data);
+ if (mMemory != 0) {
+ mMemory->update();
+ mAudioBuffer.raw = static_cast<void*>(mMemory->getPointer());
+ memset(mAudioBuffer.raw, 0, mMemory->getSize());
+ mMemory->commit();
+ } else {
+ ALOGE("Failed to map allocated ashmem");
+ retval = NO_MEMORY;
+ }
+ } else {
+ ALOGE("Failed to allocate %d bytes from ashmem", (int)mBufferSize);
+ }
+ return result.isOk() ? retval : FAILED_TRANSACTION;
+}
+
+audio_buffer_t* EffectBufferHalHidl::audioBuffer() {
+ return &mAudioBuffer;
+}
+
+void* EffectBufferHalHidl::externalData() const {
+ return mExternalData;
+}
+
+void EffectBufferHalHidl::setFrameCount(size_t frameCount) {
+ mHidlBuffer.frameCount = frameCount;
+ mAudioBuffer.frameCount = frameCount;
+ mFrameCountChanged = true;
+}
+
+bool EffectBufferHalHidl::checkFrameCountChange() {
+ bool result = mFrameCountChanged;
+ mFrameCountChanged = false;
+ return result;
+}
+
+void EffectBufferHalHidl::setExternalData(void* external) {
+ mExternalData = external;
+}
+
+void EffectBufferHalHidl::update() {
+ update(mBufferSize);
+}
+
+void EffectBufferHalHidl::commit() {
+ commit(mBufferSize);
+}
+
+void EffectBufferHalHidl::update(size_t size) {
+ if (mExternalData == nullptr) return;
+ mMemory->update();
+ if (size > mBufferSize) size = mBufferSize;
+ memcpy(mAudioBuffer.raw, mExternalData, size);
+ mMemory->commit();
+}
+
+void EffectBufferHalHidl::commit(size_t size) {
+ if (mExternalData == nullptr) return;
+ if (size > mBufferSize) size = mBufferSize;
+ memcpy(mExternalData, mAudioBuffer.raw, size);
+}
+
+} // namespace V4_0
+} // namespace android
diff --git a/media/libaudiohal/4.0/EffectBufferHalHidl.h b/media/libaudiohal/4.0/EffectBufferHalHidl.h
new file mode 100644
index 0000000..6d578c6
--- /dev/null
+++ b/media/libaudiohal/4.0/EffectBufferHalHidl.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2017 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 ANDROID_HARDWARE_EFFECT_BUFFER_HAL_HIDL_4_0_H
+#define ANDROID_HARDWARE_EFFECT_BUFFER_HAL_HIDL_4_0_H
+
+#include <android/hardware/audio/effect/4.0/types.h>
+#include <android/hidl/memory/1.0/IMemory.h>
+#include <hidl/HidlSupport.h>
+#include <media/audiohal/EffectBufferHalInterface.h>
+#include <system/audio_effect.h>
+
+using android::hardware::audio::effect::V4_0::AudioBuffer;
+using android::hardware::hidl_memory;
+using android::hidl::memory::V1_0::IMemory;
+
+namespace android {
+namespace V4_0 {
+
+class EffectBufferHalHidl : public EffectBufferHalInterface
+{
+ public:
+ static status_t allocate(size_t size, sp<EffectBufferHalInterface>* buffer);
+ static status_t mirror(void* external, size_t size, sp<EffectBufferHalInterface>* buffer);
+
+ virtual audio_buffer_t* audioBuffer();
+ virtual void* externalData() const;
+
+ virtual size_t getSize() const override { return mBufferSize; }
+
+ virtual void setExternalData(void* external);
+ virtual void setFrameCount(size_t frameCount);
+ virtual bool checkFrameCountChange();
+
+ virtual void update();
+ virtual void commit();
+ virtual void update(size_t size);
+ virtual void commit(size_t size);
+
+ const AudioBuffer& hidlBuffer() const { return mHidlBuffer; }
+
+ private:
+ friend class EffectBufferHalInterface;
+
+ static uint64_t makeUniqueId();
+
+ const size_t mBufferSize;
+ bool mFrameCountChanged;
+ void* mExternalData;
+ AudioBuffer mHidlBuffer;
+ sp<IMemory> mMemory;
+ audio_buffer_t mAudioBuffer;
+
+ // Can not be constructed directly by clients.
+ explicit EffectBufferHalHidl(size_t size);
+
+ virtual ~EffectBufferHalHidl();
+
+ status_t init();
+};
+
+} // namespace V4_0
+} // namespace android
+
+#endif // ANDROID_HARDWARE_EFFECT_BUFFER_HAL_HIDL_4_0_H
diff --git a/media/libaudiohal/4.0/EffectHalHidl.cpp b/media/libaudiohal/4.0/EffectHalHidl.cpp
new file mode 100644
index 0000000..c99c4c8
--- /dev/null
+++ b/media/libaudiohal/4.0/EffectHalHidl.cpp
@@ -0,0 +1,342 @@
+/*
+ * Copyright (C) 2016 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_TAG "EffectHalHidl"
+//#define LOG_NDEBUG 0
+
+#include <common/all-versions/VersionUtils.h>
+#include <hwbinder/IPCThreadState.h>
+#include <media/EffectsFactoryApi.h>
+#include <utils/Log.h>
+
+#include "ConversionHelperHidl.h"
+#include "EffectBufferHalHidl.h"
+#include "EffectHalHidl.h"
+#include "HidlUtils.h"
+
+using ::android::hardware::audio::effect::V4_0::AudioBuffer;
+using ::android::hardware::audio::effect::V4_0::EffectBufferAccess;
+using ::android::hardware::audio::effect::V4_0::EffectConfigParameters;
+using ::android::hardware::audio::effect::V4_0::MessageQueueFlagBits;
+using ::android::hardware::audio::effect::V4_0::Result;
+using ::android::hardware::audio::common::V4_0::HidlUtils;
+using ::android::hardware::audio::common::V4_0::AudioChannelMask;
+using ::android::hardware::audio::common::V4_0::AudioFormat;
+using ::android::hardware::audio::common::utils::mkEnumConverter;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::MQDescriptorSync;
+using ::android::hardware::Return;
+
+namespace android {
+namespace V4_0 {
+
+EffectHalHidl::EffectHalHidl(const sp<IEffect>& effect, uint64_t effectId)
+ : mEffect(effect), mEffectId(effectId), mBuffersChanged(true), mEfGroup(nullptr) {
+}
+
+EffectHalHidl::~EffectHalHidl() {
+ if (mEffect != 0) {
+ close();
+ mEffect.clear();
+ hardware::IPCThreadState::self()->flushCommands();
+ }
+ if (mEfGroup) {
+ EventFlag::deleteEventFlag(&mEfGroup);
+ }
+}
+
+// static
+void EffectHalHidl::effectDescriptorToHal(
+ const EffectDescriptor& descriptor, effect_descriptor_t* halDescriptor) {
+ HidlUtils::uuidToHal(descriptor.type, &halDescriptor->type);
+ HidlUtils::uuidToHal(descriptor.uuid, &halDescriptor->uuid);
+ halDescriptor->flags = static_cast<uint32_t>(descriptor.flags);
+ halDescriptor->cpuLoad = descriptor.cpuLoad;
+ halDescriptor->memoryUsage = descriptor.memoryUsage;
+ memcpy(halDescriptor->name, descriptor.name.data(), descriptor.name.size());
+ memcpy(halDescriptor->implementor,
+ descriptor.implementor.data(), descriptor.implementor.size());
+}
+
+// TODO(mnaganov): These buffer conversion functions should be shared with Effect wrapper
+// via HidlUtils. Move them there when hardware/interfaces will get un-frozen again.
+
+// static
+void EffectHalHidl::effectBufferConfigFromHal(
+ const buffer_config_t& halConfig, EffectBufferConfig* config) {
+ config->samplingRateHz = halConfig.samplingRate;
+ config->channels = mkEnumConverter<AudioChannelMask>(halConfig.channels);
+ config->format = AudioFormat(halConfig.format);
+ config->accessMode = EffectBufferAccess(halConfig.accessMode);
+ config->mask = mkEnumConverter<EffectConfigParameters>(halConfig.mask);
+}
+
+// static
+void EffectHalHidl::effectBufferConfigToHal(
+ const EffectBufferConfig& config, buffer_config_t* halConfig) {
+ halConfig->buffer.frameCount = 0;
+ halConfig->buffer.raw = NULL;
+ halConfig->samplingRate = config.samplingRateHz;
+ halConfig->channels = static_cast<uint32_t>(config.channels);
+ halConfig->bufferProvider.cookie = NULL;
+ halConfig->bufferProvider.getBuffer = NULL;
+ halConfig->bufferProvider.releaseBuffer = NULL;
+ halConfig->format = static_cast<uint8_t>(config.format);
+ halConfig->accessMode = static_cast<uint8_t>(config.accessMode);
+ halConfig->mask = static_cast<uint8_t>(config.mask);
+}
+
+// static
+void EffectHalHidl::effectConfigFromHal(const effect_config_t& halConfig, EffectConfig* config) {
+ effectBufferConfigFromHal(halConfig.inputCfg, &config->inputCfg);
+ effectBufferConfigFromHal(halConfig.outputCfg, &config->outputCfg);
+}
+
+// static
+void EffectHalHidl::effectConfigToHal(const EffectConfig& config, effect_config_t* halConfig) {
+ effectBufferConfigToHal(config.inputCfg, &halConfig->inputCfg);
+ effectBufferConfigToHal(config.outputCfg, &halConfig->outputCfg);
+}
+
+// static
+status_t EffectHalHidl::analyzeResult(const Result& result) {
+ switch (result) {
+ case Result::OK: return OK;
+ case Result::INVALID_ARGUMENTS: return BAD_VALUE;
+ case Result::INVALID_STATE: return NOT_ENOUGH_DATA;
+ case Result::NOT_INITIALIZED: return NO_INIT;
+ case Result::NOT_SUPPORTED: return INVALID_OPERATION;
+ case Result::RESULT_TOO_BIG: return NO_MEMORY;
+ default: return NO_INIT;
+ }
+}
+
+status_t EffectHalHidl::setInBuffer(const sp<EffectBufferHalInterface>& buffer) {
+ if (!mBuffersChanged) {
+ if (buffer.get() == nullptr || mInBuffer.get() == nullptr) {
+ mBuffersChanged = buffer.get() != mInBuffer.get();
+ } else {
+ mBuffersChanged = buffer->audioBuffer() != mInBuffer->audioBuffer();
+ }
+ }
+ mInBuffer = buffer;
+ return OK;
+}
+
+status_t EffectHalHidl::setOutBuffer(const sp<EffectBufferHalInterface>& buffer) {
+ if (!mBuffersChanged) {
+ if (buffer.get() == nullptr || mOutBuffer.get() == nullptr) {
+ mBuffersChanged = buffer.get() != mOutBuffer.get();
+ } else {
+ mBuffersChanged = buffer->audioBuffer() != mOutBuffer->audioBuffer();
+ }
+ }
+ mOutBuffer = buffer;
+ return OK;
+}
+
+status_t EffectHalHidl::process() {
+ return processImpl(static_cast<uint32_t>(MessageQueueFlagBits::REQUEST_PROCESS));
+}
+
+status_t EffectHalHidl::processReverse() {
+ return processImpl(static_cast<uint32_t>(MessageQueueFlagBits::REQUEST_PROCESS_REVERSE));
+}
+
+status_t EffectHalHidl::prepareForProcessing() {
+ std::unique_ptr<StatusMQ> tempStatusMQ;
+ Result retval;
+ Return<void> ret = mEffect->prepareForProcessing(
+ [&](Result r, const MQDescriptorSync<Result>& statusMQ) {
+ retval = r;
+ if (retval == Result::OK) {
+ tempStatusMQ.reset(new StatusMQ(statusMQ));
+ if (tempStatusMQ->isValid() && tempStatusMQ->getEventFlagWord()) {
+ EventFlag::createEventFlag(tempStatusMQ->getEventFlagWord(), &mEfGroup);
+ }
+ }
+ });
+ if (!ret.isOk() || retval != Result::OK) {
+ return ret.isOk() ? analyzeResult(retval) : FAILED_TRANSACTION;
+ }
+ if (!tempStatusMQ || !tempStatusMQ->isValid() || !mEfGroup) {
+ ALOGE_IF(!tempStatusMQ, "Failed to obtain status message queue for effects");
+ ALOGE_IF(tempStatusMQ && !tempStatusMQ->isValid(),
+ "Status message queue for effects is invalid");
+ ALOGE_IF(!mEfGroup, "Event flag creation for effects failed");
+ return NO_INIT;
+ }
+ mStatusMQ = std::move(tempStatusMQ);
+ return OK;
+}
+
+bool EffectHalHidl::needToResetBuffers() {
+ if (mBuffersChanged) return true;
+ bool inBufferFrameCountUpdated = mInBuffer->checkFrameCountChange();
+ bool outBufferFrameCountUpdated = mOutBuffer->checkFrameCountChange();
+ return inBufferFrameCountUpdated || outBufferFrameCountUpdated;
+}
+
+status_t EffectHalHidl::processImpl(uint32_t mqFlag) {
+ if (mEffect == 0 || mInBuffer == 0 || mOutBuffer == 0) return NO_INIT;
+ status_t status;
+ if (!mStatusMQ && (status = prepareForProcessing()) != OK) {
+ return status;
+ }
+ if (needToResetBuffers() && (status = setProcessBuffers()) != OK) {
+ return status;
+ }
+ // The data is already in the buffers, just need to flush it and wake up the server side.
+ std::atomic_thread_fence(std::memory_order_release);
+ mEfGroup->wake(mqFlag);
+ uint32_t efState = 0;
+retry:
+ status_t ret = mEfGroup->wait(
+ static_cast<uint32_t>(MessageQueueFlagBits::DONE_PROCESSING), &efState);
+ if (efState & static_cast<uint32_t>(MessageQueueFlagBits::DONE_PROCESSING)) {
+ Result retval = Result::NOT_INITIALIZED;
+ mStatusMQ->read(&retval);
+ if (retval == Result::OK || retval == Result::INVALID_STATE) {
+ // Sync back the changed contents of the buffer.
+ std::atomic_thread_fence(std::memory_order_acquire);
+ }
+ return analyzeResult(retval);
+ }
+ if (ret == -EAGAIN || ret == -EINTR) {
+ // Spurious wakeup. This normally retries no more than once.
+ goto retry;
+ }
+ return ret;
+}
+
+status_t EffectHalHidl::setProcessBuffers() {
+ Return<Result> ret = mEffect->setProcessBuffers(
+ static_cast<EffectBufferHalHidl*>(mInBuffer.get())->hidlBuffer(),
+ static_cast<EffectBufferHalHidl*>(mOutBuffer.get())->hidlBuffer());
+ if (ret.isOk() && ret == Result::OK) {
+ mBuffersChanged = false;
+ return OK;
+ }
+ return ret.isOk() ? analyzeResult(ret) : FAILED_TRANSACTION;
+}
+
+status_t EffectHalHidl::command(uint32_t cmdCode, uint32_t cmdSize, void *pCmdData,
+ uint32_t *replySize, void *pReplyData) {
+ if (mEffect == 0) return NO_INIT;
+
+ // Special cases.
+ if (cmdCode == EFFECT_CMD_SET_CONFIG || cmdCode == EFFECT_CMD_SET_CONFIG_REVERSE) {
+ return setConfigImpl(cmdCode, cmdSize, pCmdData, replySize, pReplyData);
+ } else if (cmdCode == EFFECT_CMD_GET_CONFIG || cmdCode == EFFECT_CMD_GET_CONFIG_REVERSE) {
+ return getConfigImpl(cmdCode, replySize, pReplyData);
+ }
+
+ // Common case.
+ hidl_vec<uint8_t> hidlData;
+ if (pCmdData != nullptr && cmdSize > 0) {
+ hidlData.setToExternal(reinterpret_cast<uint8_t*>(pCmdData), cmdSize);
+ }
+ status_t status;
+ uint32_t replySizeStub = 0;
+ if (replySize == nullptr || pReplyData == nullptr) replySize = &replySizeStub;
+ Return<void> ret = mEffect->command(cmdCode, hidlData, *replySize,
+ [&](int32_t s, const hidl_vec<uint8_t>& result) {
+ status = s;
+ if (status == 0) {
+ if (*replySize > result.size()) *replySize = result.size();
+ if (pReplyData != nullptr && *replySize > 0) {
+ memcpy(pReplyData, &result[0], *replySize);
+ }
+ }
+ });
+ return ret.isOk() ? status : FAILED_TRANSACTION;
+}
+
+status_t EffectHalHidl::getDescriptor(effect_descriptor_t *pDescriptor) {
+ if (mEffect == 0) return NO_INIT;
+ Result retval = Result::NOT_INITIALIZED;
+ Return<void> ret = mEffect->getDescriptor(
+ [&](Result r, const EffectDescriptor& result) {
+ retval = r;
+ if (retval == Result::OK) {
+ effectDescriptorToHal(result, pDescriptor);
+ }
+ });
+ return ret.isOk() ? analyzeResult(retval) : FAILED_TRANSACTION;
+}
+
+status_t EffectHalHidl::close() {
+ if (mEffect == 0) return NO_INIT;
+ Return<Result> ret = mEffect->close();
+ return ret.isOk() ? analyzeResult(ret) : FAILED_TRANSACTION;
+}
+
+status_t EffectHalHidl::getConfigImpl(
+ uint32_t cmdCode, uint32_t *replySize, void *pReplyData) {
+ if (replySize == NULL || *replySize != sizeof(effect_config_t) || pReplyData == NULL) {
+ return BAD_VALUE;
+ }
+ status_t result = FAILED_TRANSACTION;
+ Return<void> ret;
+ if (cmdCode == EFFECT_CMD_GET_CONFIG) {
+ ret = mEffect->getConfig([&] (Result r, const EffectConfig &hidlConfig) {
+ result = analyzeResult(r);
+ if (r == Result::OK) {
+ effectConfigToHal(hidlConfig, static_cast<effect_config_t*>(pReplyData));
+ }
+ });
+ } else {
+ ret = mEffect->getConfigReverse([&] (Result r, const EffectConfig &hidlConfig) {
+ result = analyzeResult(r);
+ if (r == Result::OK) {
+ effectConfigToHal(hidlConfig, static_cast<effect_config_t*>(pReplyData));
+ }
+ });
+ }
+ if (!ret.isOk()) {
+ result = FAILED_TRANSACTION;
+ }
+ return result;
+}
+
+status_t EffectHalHidl::setConfigImpl(
+ uint32_t cmdCode, uint32_t cmdSize, void *pCmdData, uint32_t *replySize, void *pReplyData) {
+ if (pCmdData == NULL || cmdSize != sizeof(effect_config_t) ||
+ replySize == NULL || *replySize != sizeof(int32_t) || pReplyData == NULL) {
+ return BAD_VALUE;
+ }
+ const effect_config_t *halConfig = static_cast<effect_config_t*>(pCmdData);
+ if (halConfig->inputCfg.bufferProvider.getBuffer != NULL ||
+ halConfig->inputCfg.bufferProvider.releaseBuffer != NULL ||
+ halConfig->outputCfg.bufferProvider.getBuffer != NULL ||
+ halConfig->outputCfg.bufferProvider.releaseBuffer != NULL) {
+ ALOGE("Buffer provider callbacks are not supported");
+ }
+ EffectConfig hidlConfig;
+ effectConfigFromHal(*halConfig, &hidlConfig);
+ Return<Result> ret = cmdCode == EFFECT_CMD_SET_CONFIG ?
+ mEffect->setConfig(hidlConfig, nullptr, nullptr) :
+ mEffect->setConfigReverse(hidlConfig, nullptr, nullptr);
+ status_t result = FAILED_TRANSACTION;
+ if (ret.isOk()) {
+ result = analyzeResult(ret);
+ *static_cast<int32_t*>(pReplyData) = result;
+ }
+ return result;
+}
+
+} // namespace V4_0
+} // namespace android
diff --git a/media/libaudiohal/4.0/EffectHalHidl.h b/media/libaudiohal/4.0/EffectHalHidl.h
new file mode 100644
index 0000000..5a4dab1
--- /dev/null
+++ b/media/libaudiohal/4.0/EffectHalHidl.h
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2016 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 ANDROID_HARDWARE_EFFECT_HAL_HIDL_4_0_H
+#define ANDROID_HARDWARE_EFFECT_HAL_HIDL_4_0_H
+
+#include <android/hardware/audio/effect/4.0/IEffect.h>
+#include <media/audiohal/EffectHalInterface.h>
+#include <fmq/EventFlag.h>
+#include <fmq/MessageQueue.h>
+#include <system/audio_effect.h>
+
+using ::android::hardware::audio::effect::V4_0::EffectBufferConfig;
+using ::android::hardware::audio::effect::V4_0::EffectConfig;
+using ::android::hardware::audio::effect::V4_0::EffectDescriptor;
+using ::android::hardware::audio::effect::V4_0::IEffect;
+using ::android::hardware::EventFlag;
+using ::android::hardware::MessageQueue;
+
+namespace android {
+namespace V4_0 {
+
+class EffectHalHidl : public EffectHalInterface
+{
+ public:
+ // Set the input buffer.
+ virtual status_t setInBuffer(const sp<EffectBufferHalInterface>& buffer);
+
+ // Set the output buffer.
+ virtual status_t setOutBuffer(const sp<EffectBufferHalInterface>& buffer);
+
+ // Effect process function.
+ virtual status_t process();
+
+ // Process reverse stream function. This function is used to pass
+ // a reference stream to the effect engine.
+ virtual status_t processReverse();
+
+ // Send a command and receive a response to/from effect engine.
+ virtual status_t command(uint32_t cmdCode, uint32_t cmdSize, void *pCmdData,
+ uint32_t *replySize, void *pReplyData);
+
+ // Returns the effect descriptor.
+ virtual status_t getDescriptor(effect_descriptor_t *pDescriptor);
+
+ // Free resources on the remote side.
+ virtual status_t close();
+
+ // Whether it's a local implementation.
+ virtual bool isLocal() const { return false; }
+
+ uint64_t effectId() const { return mEffectId; }
+
+ static void effectDescriptorToHal(
+ const EffectDescriptor& descriptor, effect_descriptor_t* halDescriptor);
+
+ private:
+ friend class EffectsFactoryHalHidl;
+ typedef MessageQueue<
+ hardware::audio::effect::V4_0::Result, hardware::kSynchronizedReadWrite> StatusMQ;
+
+ sp<IEffect> mEffect;
+ const uint64_t mEffectId;
+ sp<EffectBufferHalInterface> mInBuffer;
+ sp<EffectBufferHalInterface> mOutBuffer;
+ bool mBuffersChanged;
+ std::unique_ptr<StatusMQ> mStatusMQ;
+ EventFlag* mEfGroup;
+
+ static status_t analyzeResult(const hardware::audio::effect::V4_0::Result& result);
+ static void effectBufferConfigFromHal(
+ const buffer_config_t& halConfig, EffectBufferConfig* config);
+ static void effectBufferConfigToHal(
+ const EffectBufferConfig& config, buffer_config_t* halConfig);
+ static void effectConfigFromHal(const effect_config_t& halConfig, EffectConfig* config);
+ static void effectConfigToHal(const EffectConfig& config, effect_config_t* halConfig);
+
+ // Can not be constructed directly by clients.
+ EffectHalHidl(const sp<IEffect>& effect, uint64_t effectId);
+
+ // The destructor automatically releases the effect.
+ virtual ~EffectHalHidl();
+
+ status_t getConfigImpl(uint32_t cmdCode, uint32_t *replySize, void *pReplyData);
+ status_t prepareForProcessing();
+ bool needToResetBuffers();
+ status_t processImpl(uint32_t mqFlag);
+ status_t setConfigImpl(
+ uint32_t cmdCode, uint32_t cmdSize, void *pCmdData,
+ uint32_t *replySize, void *pReplyData);
+ status_t setProcessBuffers();
+};
+
+} // namespace V4_0
+} // namespace android
+
+#endif // ANDROID_HARDWARE_EFFECT_HAL_HIDL_4_0_H
diff --git a/media/libaudiohal/4.0/EffectsFactoryHalHidl.cpp b/media/libaudiohal/4.0/EffectsFactoryHalHidl.cpp
new file mode 100644
index 0000000..dfed784
--- /dev/null
+++ b/media/libaudiohal/4.0/EffectsFactoryHalHidl.cpp
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2016 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_TAG "EffectsFactoryHalHidl"
+//#define LOG_NDEBUG 0
+
+#include <cutils/native_handle.h>
+#include <libaudiohal/4.0/EffectsFactoryHalHidl.h>
+
+#include "ConversionHelperHidl.h"
+#include "EffectBufferHalHidl.h"
+#include "EffectHalHidl.h"
+#include "HidlUtils.h"
+
+using ::android::hardware::audio::common::V4_0::HidlUtils;
+using ::android::hardware::audio::common::V4_0::Uuid;
+using ::android::hardware::audio::effect::V4_0::IEffect;
+using ::android::hardware::audio::effect::V4_0::Result;
+using ::android::hardware::Return;
+
+namespace android {
+namespace V4_0 {
+
+EffectsFactoryHalHidl::EffectsFactoryHalHidl() : ConversionHelperHidl("EffectsFactory") {
+ mEffectsFactory = IEffectsFactory::getService();
+ if (mEffectsFactory == 0) {
+ ALOGE("Failed to obtain IEffectsFactory service, terminating process.");
+ exit(1);
+ }
+}
+
+EffectsFactoryHalHidl::~EffectsFactoryHalHidl() {
+}
+
+status_t EffectsFactoryHalHidl::queryAllDescriptors() {
+ if (mEffectsFactory == 0) return NO_INIT;
+ Result retval = Result::NOT_INITIALIZED;
+ Return<void> ret = mEffectsFactory->getAllDescriptors(
+ [&](Result r, const hidl_vec<EffectDescriptor>& result) {
+ retval = r;
+ if (retval == Result::OK) {
+ mLastDescriptors = result;
+ }
+ });
+ if (ret.isOk()) {
+ return retval == Result::OK ? OK : NO_INIT;
+ }
+ mLastDescriptors.resize(0);
+ return processReturn(__FUNCTION__, ret);
+}
+
+status_t EffectsFactoryHalHidl::queryNumberEffects(uint32_t *pNumEffects) {
+ status_t queryResult = queryAllDescriptors();
+ if (queryResult == OK) {
+ *pNumEffects = mLastDescriptors.size();
+ }
+ return queryResult;
+}
+
+status_t EffectsFactoryHalHidl::getDescriptor(
+ uint32_t index, effect_descriptor_t *pDescriptor) {
+ // TODO: We need somehow to track the changes on the server side
+ // or figure out how to convert everybody to query all the descriptors at once.
+ // TODO: check for nullptr
+ if (mLastDescriptors.size() == 0) {
+ status_t queryResult = queryAllDescriptors();
+ if (queryResult != OK) return queryResult;
+ }
+ if (index >= mLastDescriptors.size()) return NAME_NOT_FOUND;
+ EffectHalHidl::effectDescriptorToHal(mLastDescriptors[index], pDescriptor);
+ return OK;
+}
+
+status_t EffectsFactoryHalHidl::getDescriptor(
+ const effect_uuid_t *pEffectUuid, effect_descriptor_t *pDescriptor) {
+ // TODO: check for nullptr
+ if (mEffectsFactory == 0) return NO_INIT;
+ Uuid hidlUuid;
+ HidlUtils::uuidFromHal(*pEffectUuid, &hidlUuid);
+ Result retval = Result::NOT_INITIALIZED;
+ Return<void> ret = mEffectsFactory->getDescriptor(hidlUuid,
+ [&](Result r, const EffectDescriptor& result) {
+ retval = r;
+ if (retval == Result::OK) {
+ EffectHalHidl::effectDescriptorToHal(result, pDescriptor);
+ }
+ });
+ if (ret.isOk()) {
+ if (retval == Result::OK) return OK;
+ else if (retval == Result::INVALID_ARGUMENTS) return NAME_NOT_FOUND;
+ else return NO_INIT;
+ }
+ return processReturn(__FUNCTION__, ret);
+}
+
+status_t EffectsFactoryHalHidl::createEffect(
+ const effect_uuid_t *pEffectUuid, int32_t sessionId, int32_t ioId,
+ sp<EffectHalInterface> *effect) {
+ if (mEffectsFactory == 0) return NO_INIT;
+ Uuid hidlUuid;
+ HidlUtils::uuidFromHal(*pEffectUuid, &hidlUuid);
+ Result retval = Result::NOT_INITIALIZED;
+ Return<void> ret = mEffectsFactory->createEffect(
+ hidlUuid, sessionId, ioId,
+ [&](Result r, const sp<IEffect>& result, uint64_t effectId) {
+ retval = r;
+ if (retval == Result::OK) {
+ *effect = new EffectHalHidl(result, effectId);
+ }
+ });
+ if (ret.isOk()) {
+ if (retval == Result::OK) return OK;
+ else if (retval == Result::INVALID_ARGUMENTS) return NAME_NOT_FOUND;
+ else return NO_INIT;
+ }
+ return processReturn(__FUNCTION__, ret);
+}
+
+status_t EffectsFactoryHalHidl::dumpEffects(int fd) {
+ if (mEffectsFactory == 0) return NO_INIT;
+ native_handle_t* hidlHandle = native_handle_create(1, 0);
+ hidlHandle->data[0] = fd;
+ Return<void> ret = mEffectsFactory->debug(hidlHandle, {} /* options */);
+ native_handle_delete(hidlHandle);
+ return processReturn(__FUNCTION__, ret);
+}
+
+status_t EffectsFactoryHalHidl::allocateBuffer(size_t size, sp<EffectBufferHalInterface>* buffer) {
+ return EffectBufferHalHidl::allocate(size, buffer);
+}
+
+status_t EffectsFactoryHalHidl::mirrorBuffer(void* external, size_t size,
+ sp<EffectBufferHalInterface>* buffer) {
+ return EffectBufferHalHidl::mirror(external, size, buffer);
+}
+
+
+} // namespace V4_0
+} // namespace android
diff --git a/media/libaudiohal/4.0/StreamHalHidl.cpp b/media/libaudiohal/4.0/StreamHalHidl.cpp
new file mode 100644
index 0000000..de16e98
--- /dev/null
+++ b/media/libaudiohal/4.0/StreamHalHidl.cpp
@@ -0,0 +1,758 @@
+/*
+ * Copyright (C) 2016 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_TAG "StreamHalHidl"
+//#define LOG_NDEBUG 0
+
+#include <android/hardware/audio/4.0/IStreamOutCallback.h>
+#include <hwbinder/IPCThreadState.h>
+#include <mediautils/SchedulingPolicyService.h>
+#include <utils/Log.h>
+
+#include "DeviceHalHidl.h"
+#include "EffectHalHidl.h"
+#include "StreamHalHidl.h"
+#include "VersionUtils.h"
+
+using ::android::hardware::audio::common::V4_0::AudioChannelMask;
+using ::android::hardware::audio::common::V4_0::AudioFormat;
+using ::android::hardware::audio::common::V4_0::ThreadInfo;
+using ::android::hardware::audio::V4_0::AudioDrain;
+using ::android::hardware::audio::V4_0::IStreamOutCallback;
+using ::android::hardware::audio::V4_0::MessageQueueFlagBits;
+using ::android::hardware::audio::V4_0::MmapBufferInfo;
+using ::android::hardware::audio::V4_0::MmapPosition;
+using ::android::hardware::audio::V4_0::ParameterValue;
+using ::android::hardware::audio::V4_0::Result;
+using ::android::hardware::audio::V4_0::TimeSpec;
+using ::android::hardware::MQDescriptorSync;
+using ::android::hardware::Return;
+using ::android::hardware::Void;
+using ReadCommand = ::android::hardware::audio::V4_0::IStreamIn::ReadCommand;
+
+namespace android {
+namespace V4_0 {
+
+StreamHalHidl::StreamHalHidl(IStream *stream)
+ : ConversionHelperHidl("Stream"),
+ mStream(stream),
+ mHalThreadPriority(HAL_THREAD_PRIORITY_DEFAULT),
+ mCachedBufferSize(0){
+
+ // Instrument audio signal power logging.
+ // Note: This assumes channel mask, format, and sample rate do not change after creation.
+ if (mStream != nullptr && mStreamPowerLog.isUserDebugOrEngBuild()) {
+ // Obtain audio properties (see StreamHalHidl::getAudioProperties() below).
+ Return<void> ret = mStream->getAudioProperties(
+ [&](auto sr, auto m, auto f) {
+ mStreamPowerLog.init(sr,
+ static_cast<audio_channel_mask_t>(m),
+ static_cast<audio_format_t>(f));
+ });
+ }
+}
+
+StreamHalHidl::~StreamHalHidl() {
+ mStream = nullptr;
+}
+
+status_t StreamHalHidl::getSampleRate(uint32_t *rate) {
+ if (!mStream) return NO_INIT;
+ return processReturn("getSampleRate", mStream->getSampleRate(), rate);
+}
+
+status_t StreamHalHidl::getBufferSize(size_t *size) {
+ if (!mStream) return NO_INIT;
+ status_t status = processReturn("getBufferSize", mStream->getBufferSize(), size);
+ if (status == OK) {
+ mCachedBufferSize = *size;
+ }
+ return status;
+}
+
+status_t StreamHalHidl::getChannelMask(audio_channel_mask_t *mask) {
+ if (!mStream) return NO_INIT;
+ return processReturn("getChannelMask", mStream->getChannelMask(), mask);
+}
+
+status_t StreamHalHidl::getFormat(audio_format_t *format) {
+ if (!mStream) return NO_INIT;
+ return processReturn("getFormat", mStream->getFormat(), format);
+}
+
+status_t StreamHalHidl::getAudioProperties(
+ uint32_t *sampleRate, audio_channel_mask_t *mask, audio_format_t *format) {
+ if (!mStream) return NO_INIT;
+ Return<void> ret = mStream->getAudioProperties(
+ [&](uint32_t sr, auto m, auto f) {
+ *sampleRate = sr;
+ *mask = static_cast<audio_channel_mask_t>(m);
+ *format = static_cast<audio_format_t>(f);
+ });
+ return processReturn("getAudioProperties", ret);
+}
+
+status_t StreamHalHidl::setParameters(const String8& kvPairs) {
+ if (!mStream) return NO_INIT;
+ hidl_vec<ParameterValue> hidlParams;
+ status_t status = parametersFromHal(kvPairs, &hidlParams);
+ if (status != OK) return status;
+ return processReturn("setParameters",
+ utils::setParameters(mStream, hidlParams, {} /* options */));
+}
+
+status_t StreamHalHidl::getParameters(const String8& keys, String8 *values) {
+ values->clear();
+ if (!mStream) return NO_INIT;
+ hidl_vec<hidl_string> hidlKeys;
+ status_t status = keysFromHal(keys, &hidlKeys);
+ if (status != OK) return status;
+ Result retval;
+ Return<void> ret = utils::getParameters(
+ mStream,
+ {} /* context */,
+ hidlKeys,
+ [&](Result r, const hidl_vec<ParameterValue>& parameters) {
+ retval = r;
+ if (retval == Result::OK) {
+ parametersToHal(parameters, values);
+ }
+ });
+ return processReturn("getParameters", ret, retval);
+}
+
+status_t StreamHalHidl::addEffect(sp<EffectHalInterface> effect) {
+ if (!mStream) return NO_INIT;
+ return processReturn("addEffect", mStream->addEffect(
+ static_cast<EffectHalHidl*>(effect.get())->effectId()));
+}
+
+status_t StreamHalHidl::removeEffect(sp<EffectHalInterface> effect) {
+ if (!mStream) return NO_INIT;
+ return processReturn("removeEffect", mStream->removeEffect(
+ static_cast<EffectHalHidl*>(effect.get())->effectId()));
+}
+
+status_t StreamHalHidl::standby() {
+ if (!mStream) return NO_INIT;
+ return processReturn("standby", mStream->standby());
+}
+
+status_t StreamHalHidl::dump(int fd) {
+ if (!mStream) return NO_INIT;
+ native_handle_t* hidlHandle = native_handle_create(1, 0);
+ hidlHandle->data[0] = fd;
+ Return<void> ret = mStream->debug(hidlHandle, {} /* options */);
+ native_handle_delete(hidlHandle);
+ mStreamPowerLog.dump(fd);
+ return processReturn("dump", ret);
+}
+
+status_t StreamHalHidl::start() {
+ if (!mStream) return NO_INIT;
+ return processReturn("start", mStream->start());
+}
+
+status_t StreamHalHidl::stop() {
+ if (!mStream) return NO_INIT;
+ return processReturn("stop", mStream->stop());
+}
+
+status_t StreamHalHidl::createMmapBuffer(int32_t minSizeFrames,
+ struct audio_mmap_buffer_info *info) {
+ Result retval;
+ Return<void> ret = mStream->createMmapBuffer(
+ minSizeFrames,
+ [&](Result r, const MmapBufferInfo& hidlInfo) {
+ retval = r;
+ if (retval == Result::OK) {
+ const native_handle *handle = hidlInfo.sharedMemory.handle();
+ if (handle->numFds > 0) {
+ info->shared_memory_fd = handle->data[0];
+ info->buffer_size_frames = hidlInfo.bufferSizeFrames;
+ info->burst_size_frames = hidlInfo.burstSizeFrames;
+ // info->shared_memory_address is not needed in HIDL context
+ info->shared_memory_address = NULL;
+ } else {
+ retval = Result::NOT_INITIALIZED;
+ }
+ }
+ });
+ return processReturn("createMmapBuffer", ret, retval);
+}
+
+status_t StreamHalHidl::getMmapPosition(struct audio_mmap_position *position) {
+ Result retval;
+ Return<void> ret = mStream->getMmapPosition(
+ [&](Result r, const MmapPosition& hidlPosition) {
+ retval = r;
+ if (retval == Result::OK) {
+ position->time_nanoseconds = hidlPosition.timeNanoseconds;
+ position->position_frames = hidlPosition.positionFrames;
+ }
+ });
+ return processReturn("getMmapPosition", ret, retval);
+}
+
+status_t StreamHalHidl::setHalThreadPriority(int priority) {
+ mHalThreadPriority = priority;
+ return OK;
+}
+
+status_t StreamHalHidl::getCachedBufferSize(size_t *size) {
+ if (mCachedBufferSize != 0) {
+ *size = mCachedBufferSize;
+ return OK;
+ }
+ return getBufferSize(size);
+}
+
+bool StreamHalHidl::requestHalThreadPriority(pid_t threadPid, pid_t threadId) {
+ if (mHalThreadPriority == HAL_THREAD_PRIORITY_DEFAULT) {
+ return true;
+ }
+ int err = requestPriority(
+ threadPid, threadId,
+ mHalThreadPriority, false /*isForApp*/, true /*asynchronous*/);
+ ALOGE_IF(err, "failed to set priority %d for pid %d tid %d; error %d",
+ mHalThreadPriority, threadPid, threadId, err);
+ // Audio will still work, but latency will be higher and sometimes unacceptable.
+ return err == 0;
+}
+
+namespace {
+
+/* Notes on callback ownership.
+
+This is how (Hw)Binder ownership model looks like. The server implementation
+is owned by Binder framework (via sp<>). Proxies are owned by clients.
+When the last proxy disappears, Binder framework releases the server impl.
+
+Thus, it is not needed to keep any references to StreamOutCallback (this is
+the server impl) -- it will live as long as HAL server holds a strong ref to
+IStreamOutCallback proxy. We clear that reference by calling 'clearCallback'
+from the destructor of StreamOutHalHidl.
+
+The callback only keeps a weak reference to the stream. The stream is owned
+by AudioFlinger.
+
+*/
+
+struct StreamOutCallback : public IStreamOutCallback {
+ StreamOutCallback(const wp<StreamOutHalHidl>& stream) : mStream(stream) {}
+
+ // IStreamOutCallback implementation
+ Return<void> onWriteReady() override {
+ sp<StreamOutHalHidl> stream = mStream.promote();
+ if (stream != 0) {
+ stream->onWriteReady();
+ }
+ return Void();
+ }
+
+ Return<void> onDrainReady() override {
+ sp<StreamOutHalHidl> stream = mStream.promote();
+ if (stream != 0) {
+ stream->onDrainReady();
+ }
+ return Void();
+ }
+
+ Return<void> onError() override {
+ sp<StreamOutHalHidl> stream = mStream.promote();
+ if (stream != 0) {
+ stream->onError();
+ }
+ return Void();
+ }
+
+ private:
+ wp<StreamOutHalHidl> mStream;
+};
+
+} // namespace
+
+StreamOutHalHidl::StreamOutHalHidl(const sp<IStreamOut>& stream)
+ : StreamHalHidl(stream.get()), mStream(stream), mWriterClient(0), mEfGroup(nullptr) {
+}
+
+StreamOutHalHidl::~StreamOutHalHidl() {
+ if (mStream != 0) {
+ if (mCallback.unsafe_get()) {
+ processReturn("clearCallback", mStream->clearCallback());
+ }
+ processReturn("close", mStream->close());
+ mStream.clear();
+ }
+ mCallback.clear();
+ hardware::IPCThreadState::self()->flushCommands();
+ if (mEfGroup) {
+ EventFlag::deleteEventFlag(&mEfGroup);
+ }
+}
+
+status_t StreamOutHalHidl::getFrameSize(size_t *size) {
+ if (mStream == 0) return NO_INIT;
+ return processReturn("getFrameSize", mStream->getFrameSize(), size);
+}
+
+status_t StreamOutHalHidl::getLatency(uint32_t *latency) {
+ if (mStream == 0) return NO_INIT;
+ if (mWriterClient == gettid() && mCommandMQ) {
+ return callWriterThread(
+ WriteCommand::GET_LATENCY, "getLatency", nullptr, 0,
+ [&](const WriteStatus& writeStatus) {
+ *latency = writeStatus.reply.latencyMs;
+ });
+ } else {
+ return processReturn("getLatency", mStream->getLatency(), latency);
+ }
+}
+
+status_t StreamOutHalHidl::setVolume(float left, float right) {
+ if (mStream == 0) return NO_INIT;
+ return processReturn("setVolume", mStream->setVolume(left, right));
+}
+
+status_t StreamOutHalHidl::write(const void *buffer, size_t bytes, size_t *written) {
+ if (mStream == 0) return NO_INIT;
+ *written = 0;
+
+ if (bytes == 0 && !mDataMQ) {
+ // Can't determine the size for the MQ buffer. Wait for a non-empty write request.
+ ALOGW_IF(mCallback.unsafe_get(), "First call to async write with 0 bytes");
+ return OK;
+ }
+
+ status_t status;
+ if (!mDataMQ) {
+ // In case if playback starts close to the end of a compressed track, the bytes
+ // that need to be written is less than the actual buffer size. Need to use
+ // full buffer size for the MQ since otherwise after seeking back to the middle
+ // data will be truncated.
+ size_t bufferSize;
+ if ((status = getCachedBufferSize(&bufferSize)) != OK) {
+ return status;
+ }
+ if (bytes > bufferSize) bufferSize = bytes;
+ if ((status = prepareForWriting(bufferSize)) != OK) {
+ return status;
+ }
+ }
+
+ status = callWriterThread(
+ WriteCommand::WRITE, "write", static_cast<const uint8_t*>(buffer), bytes,
+ [&] (const WriteStatus& writeStatus) {
+ *written = writeStatus.reply.written;
+ // Diagnostics of the cause of b/35813113.
+ ALOGE_IF(*written > bytes,
+ "hal reports more bytes written than asked for: %lld > %lld",
+ (long long)*written, (long long)bytes);
+ });
+ mStreamPowerLog.log(buffer, *written);
+ return status;
+}
+
+status_t StreamOutHalHidl::callWriterThread(
+ WriteCommand cmd, const char* cmdName,
+ const uint8_t* data, size_t dataSize, StreamOutHalHidl::WriterCallback callback) {
+ if (!mCommandMQ->write(&cmd)) {
+ ALOGE("command message queue write failed for \"%s\"", cmdName);
+ return -EAGAIN;
+ }
+ if (data != nullptr) {
+ size_t availableToWrite = mDataMQ->availableToWrite();
+ if (dataSize > availableToWrite) {
+ ALOGW("truncating write data from %lld to %lld due to insufficient data queue space",
+ (long long)dataSize, (long long)availableToWrite);
+ dataSize = availableToWrite;
+ }
+ if (!mDataMQ->write(data, dataSize)) {
+ ALOGE("data message queue write failed for \"%s\"", cmdName);
+ }
+ }
+ mEfGroup->wake(static_cast<uint32_t>(MessageQueueFlagBits::NOT_EMPTY));
+
+ // TODO: Remove manual event flag handling once blocking MQ is implemented. b/33815422
+ uint32_t efState = 0;
+retry:
+ status_t ret = mEfGroup->wait(static_cast<uint32_t>(MessageQueueFlagBits::NOT_FULL), &efState);
+ if (efState & static_cast<uint32_t>(MessageQueueFlagBits::NOT_FULL)) {
+ WriteStatus writeStatus;
+ writeStatus.retval = Result::NOT_INITIALIZED;
+ if (!mStatusMQ->read(&writeStatus)) {
+ ALOGE("status message read failed for \"%s\"", cmdName);
+ }
+ if (writeStatus.retval == Result::OK) {
+ ret = OK;
+ callback(writeStatus);
+ } else {
+ ret = processReturn(cmdName, writeStatus.retval);
+ }
+ return ret;
+ }
+ if (ret == -EAGAIN || ret == -EINTR) {
+ // Spurious wakeup. This normally retries no more than once.
+ goto retry;
+ }
+ return ret;
+}
+
+status_t StreamOutHalHidl::prepareForWriting(size_t bufferSize) {
+ std::unique_ptr<CommandMQ> tempCommandMQ;
+ std::unique_ptr<DataMQ> tempDataMQ;
+ std::unique_ptr<StatusMQ> tempStatusMQ;
+ Result retval;
+ pid_t halThreadPid, halThreadTid;
+ Return<void> ret = mStream->prepareForWriting(
+ 1, bufferSize,
+ [&](Result r,
+ const CommandMQ::Descriptor& commandMQ,
+ const DataMQ::Descriptor& dataMQ,
+ const StatusMQ::Descriptor& statusMQ,
+ const ThreadInfo& halThreadInfo) {
+ retval = r;
+ if (retval == Result::OK) {
+ tempCommandMQ.reset(new CommandMQ(commandMQ));
+ tempDataMQ.reset(new DataMQ(dataMQ));
+ tempStatusMQ.reset(new StatusMQ(statusMQ));
+ if (tempDataMQ->isValid() && tempDataMQ->getEventFlagWord()) {
+ EventFlag::createEventFlag(tempDataMQ->getEventFlagWord(), &mEfGroup);
+ }
+ halThreadPid = halThreadInfo.pid;
+ halThreadTid = halThreadInfo.tid;
+ }
+ });
+ if (!ret.isOk() || retval != Result::OK) {
+ return processReturn("prepareForWriting", ret, retval);
+ }
+ if (!tempCommandMQ || !tempCommandMQ->isValid() ||
+ !tempDataMQ || !tempDataMQ->isValid() ||
+ !tempStatusMQ || !tempStatusMQ->isValid() ||
+ !mEfGroup) {
+ ALOGE_IF(!tempCommandMQ, "Failed to obtain command message queue for writing");
+ ALOGE_IF(tempCommandMQ && !tempCommandMQ->isValid(),
+ "Command message queue for writing is invalid");
+ ALOGE_IF(!tempDataMQ, "Failed to obtain data message queue for writing");
+ ALOGE_IF(tempDataMQ && !tempDataMQ->isValid(), "Data message queue for writing is invalid");
+ ALOGE_IF(!tempStatusMQ, "Failed to obtain status message queue for writing");
+ ALOGE_IF(tempStatusMQ && !tempStatusMQ->isValid(),
+ "Status message queue for writing is invalid");
+ ALOGE_IF(!mEfGroup, "Event flag creation for writing failed");
+ return NO_INIT;
+ }
+ requestHalThreadPriority(halThreadPid, halThreadTid);
+
+ mCommandMQ = std::move(tempCommandMQ);
+ mDataMQ = std::move(tempDataMQ);
+ mStatusMQ = std::move(tempStatusMQ);
+ mWriterClient = gettid();
+ return OK;
+}
+
+status_t StreamOutHalHidl::getRenderPosition(uint32_t *dspFrames) {
+ if (mStream == 0) return NO_INIT;
+ Result retval;
+ Return<void> ret = mStream->getRenderPosition(
+ [&](Result r, uint32_t d) {
+ retval = r;
+ if (retval == Result::OK) {
+ *dspFrames = d;
+ }
+ });
+ return processReturn("getRenderPosition", ret, retval);
+}
+
+status_t StreamOutHalHidl::getNextWriteTimestamp(int64_t *timestamp) {
+ if (mStream == 0) return NO_INIT;
+ Result retval;
+ Return<void> ret = mStream->getNextWriteTimestamp(
+ [&](Result r, int64_t t) {
+ retval = r;
+ if (retval == Result::OK) {
+ *timestamp = t;
+ }
+ });
+ return processReturn("getRenderPosition", ret, retval);
+}
+
+status_t StreamOutHalHidl::setCallback(wp<StreamOutHalInterfaceCallback> callback) {
+ if (mStream == 0) return NO_INIT;
+ status_t status = processReturn(
+ "setCallback", mStream->setCallback(new StreamOutCallback(this)));
+ if (status == OK) {
+ mCallback = callback;
+ }
+ return status;
+}
+
+status_t StreamOutHalHidl::supportsPauseAndResume(bool *supportsPause, bool *supportsResume) {
+ if (mStream == 0) return NO_INIT;
+ Return<void> ret = mStream->supportsPauseAndResume(
+ [&](bool p, bool r) {
+ *supportsPause = p;
+ *supportsResume = r;
+ });
+ return processReturn("supportsPauseAndResume", ret);
+}
+
+status_t StreamOutHalHidl::pause() {
+ if (mStream == 0) return NO_INIT;
+ return processReturn("pause", mStream->pause());
+}
+
+status_t StreamOutHalHidl::resume() {
+ if (mStream == 0) return NO_INIT;
+ return processReturn("pause", mStream->resume());
+}
+
+status_t StreamOutHalHidl::supportsDrain(bool *supportsDrain) {
+ if (mStream == 0) return NO_INIT;
+ return processReturn("supportsDrain", mStream->supportsDrain(), supportsDrain);
+}
+
+status_t StreamOutHalHidl::drain(bool earlyNotify) {
+ if (mStream == 0) return NO_INIT;
+ return processReturn(
+ "drain", mStream->drain(earlyNotify ? AudioDrain::EARLY_NOTIFY : AudioDrain::ALL));
+}
+
+status_t StreamOutHalHidl::flush() {
+ if (mStream == 0) return NO_INIT;
+ return processReturn("pause", mStream->flush());
+}
+
+status_t StreamOutHalHidl::getPresentationPosition(uint64_t *frames, struct timespec *timestamp) {
+ if (mStream == 0) return NO_INIT;
+ if (mWriterClient == gettid() && mCommandMQ) {
+ return callWriterThread(
+ WriteCommand::GET_PRESENTATION_POSITION, "getPresentationPosition", nullptr, 0,
+ [&](const WriteStatus& writeStatus) {
+ *frames = writeStatus.reply.presentationPosition.frames;
+ timestamp->tv_sec = writeStatus.reply.presentationPosition.timeStamp.tvSec;
+ timestamp->tv_nsec = writeStatus.reply.presentationPosition.timeStamp.tvNSec;
+ });
+ } else {
+ Result retval;
+ Return<void> ret = mStream->getPresentationPosition(
+ [&](Result r, uint64_t hidlFrames, const TimeSpec& hidlTimeStamp) {
+ retval = r;
+ if (retval == Result::OK) {
+ *frames = hidlFrames;
+ timestamp->tv_sec = hidlTimeStamp.tvSec;
+ timestamp->tv_nsec = hidlTimeStamp.tvNSec;
+ }
+ });
+ return processReturn("getPresentationPosition", ret, retval);
+ }
+}
+
+void StreamOutHalHidl::onWriteReady() {
+ sp<StreamOutHalInterfaceCallback> callback = mCallback.promote();
+ if (callback == 0) return;
+ ALOGV("asyncCallback onWriteReady");
+ callback->onWriteReady();
+}
+
+void StreamOutHalHidl::onDrainReady() {
+ sp<StreamOutHalInterfaceCallback> callback = mCallback.promote();
+ if (callback == 0) return;
+ ALOGV("asyncCallback onDrainReady");
+ callback->onDrainReady();
+}
+
+void StreamOutHalHidl::onError() {
+ sp<StreamOutHalInterfaceCallback> callback = mCallback.promote();
+ if (callback == 0) return;
+ ALOGV("asyncCallback onError");
+ callback->onError();
+}
+
+
+StreamInHalHidl::StreamInHalHidl(const sp<IStreamIn>& stream)
+ : StreamHalHidl(stream.get()), mStream(stream), mReaderClient(0), mEfGroup(nullptr) {
+}
+
+StreamInHalHidl::~StreamInHalHidl() {
+ if (mStream != 0) {
+ processReturn("close", mStream->close());
+ mStream.clear();
+ hardware::IPCThreadState::self()->flushCommands();
+ }
+ if (mEfGroup) {
+ EventFlag::deleteEventFlag(&mEfGroup);
+ }
+}
+
+status_t StreamInHalHidl::getFrameSize(size_t *size) {
+ if (mStream == 0) return NO_INIT;
+ return processReturn("getFrameSize", mStream->getFrameSize(), size);
+}
+
+status_t StreamInHalHidl::setGain(float gain) {
+ if (mStream == 0) return NO_INIT;
+ return processReturn("setGain", mStream->setGain(gain));
+}
+
+status_t StreamInHalHidl::read(void *buffer, size_t bytes, size_t *read) {
+ if (mStream == 0) return NO_INIT;
+ *read = 0;
+
+ if (bytes == 0 && !mDataMQ) {
+ // Can't determine the size for the MQ buffer. Wait for a non-empty read request.
+ return OK;
+ }
+
+ status_t status;
+ if (!mDataMQ && (status = prepareForReading(bytes)) != OK) {
+ return status;
+ }
+
+ ReadParameters params;
+ params.command = ReadCommand::READ;
+ params.params.read = bytes;
+ status = callReaderThread(params, "read",
+ [&](const ReadStatus& readStatus) {
+ const size_t availToRead = mDataMQ->availableToRead();
+ if (!mDataMQ->read(static_cast<uint8_t*>(buffer), std::min(bytes, availToRead))) {
+ ALOGE("data message queue read failed for \"read\"");
+ }
+ ALOGW_IF(availToRead != readStatus.reply.read,
+ "HAL read report inconsistent: mq = %d, status = %d",
+ (int32_t)availToRead, (int32_t)readStatus.reply.read);
+ *read = readStatus.reply.read;
+ });
+ mStreamPowerLog.log(buffer, *read);
+ return status;
+}
+
+status_t StreamInHalHidl::callReaderThread(
+ const ReadParameters& params, const char* cmdName,
+ StreamInHalHidl::ReaderCallback callback) {
+ if (!mCommandMQ->write(¶ms)) {
+ ALOGW("command message queue write failed");
+ return -EAGAIN;
+ }
+ mEfGroup->wake(static_cast<uint32_t>(MessageQueueFlagBits::NOT_FULL));
+
+ // TODO: Remove manual event flag handling once blocking MQ is implemented. b/33815422
+ uint32_t efState = 0;
+retry:
+ status_t ret = mEfGroup->wait(static_cast<uint32_t>(MessageQueueFlagBits::NOT_EMPTY), &efState);
+ if (efState & static_cast<uint32_t>(MessageQueueFlagBits::NOT_EMPTY)) {
+ ReadStatus readStatus;
+ readStatus.retval = Result::NOT_INITIALIZED;
+ if (!mStatusMQ->read(&readStatus)) {
+ ALOGE("status message read failed for \"%s\"", cmdName);
+ }
+ if (readStatus.retval == Result::OK) {
+ ret = OK;
+ callback(readStatus);
+ } else {
+ ret = processReturn(cmdName, readStatus.retval);
+ }
+ return ret;
+ }
+ if (ret == -EAGAIN || ret == -EINTR) {
+ // Spurious wakeup. This normally retries no more than once.
+ goto retry;
+ }
+ return ret;
+}
+
+status_t StreamInHalHidl::prepareForReading(size_t bufferSize) {
+ std::unique_ptr<CommandMQ> tempCommandMQ;
+ std::unique_ptr<DataMQ> tempDataMQ;
+ std::unique_ptr<StatusMQ> tempStatusMQ;
+ Result retval;
+ pid_t halThreadPid, halThreadTid;
+ Return<void> ret = mStream->prepareForReading(
+ 1, bufferSize,
+ [&](Result r,
+ const CommandMQ::Descriptor& commandMQ,
+ const DataMQ::Descriptor& dataMQ,
+ const StatusMQ::Descriptor& statusMQ,
+ const ThreadInfo& halThreadInfo) {
+ retval = r;
+ if (retval == Result::OK) {
+ tempCommandMQ.reset(new CommandMQ(commandMQ));
+ tempDataMQ.reset(new DataMQ(dataMQ));
+ tempStatusMQ.reset(new StatusMQ(statusMQ));
+ if (tempDataMQ->isValid() && tempDataMQ->getEventFlagWord()) {
+ EventFlag::createEventFlag(tempDataMQ->getEventFlagWord(), &mEfGroup);
+ }
+ halThreadPid = halThreadInfo.pid;
+ halThreadTid = halThreadInfo.tid;
+ }
+ });
+ if (!ret.isOk() || retval != Result::OK) {
+ return processReturn("prepareForReading", ret, retval);
+ }
+ if (!tempCommandMQ || !tempCommandMQ->isValid() ||
+ !tempDataMQ || !tempDataMQ->isValid() ||
+ !tempStatusMQ || !tempStatusMQ->isValid() ||
+ !mEfGroup) {
+ ALOGE_IF(!tempCommandMQ, "Failed to obtain command message queue for writing");
+ ALOGE_IF(tempCommandMQ && !tempCommandMQ->isValid(),
+ "Command message queue for writing is invalid");
+ ALOGE_IF(!tempDataMQ, "Failed to obtain data message queue for reading");
+ ALOGE_IF(tempDataMQ && !tempDataMQ->isValid(), "Data message queue for reading is invalid");
+ ALOGE_IF(!tempStatusMQ, "Failed to obtain status message queue for reading");
+ ALOGE_IF(tempStatusMQ && !tempStatusMQ->isValid(),
+ "Status message queue for reading is invalid");
+ ALOGE_IF(!mEfGroup, "Event flag creation for reading failed");
+ return NO_INIT;
+ }
+ requestHalThreadPriority(halThreadPid, halThreadTid);
+
+ mCommandMQ = std::move(tempCommandMQ);
+ mDataMQ = std::move(tempDataMQ);
+ mStatusMQ = std::move(tempStatusMQ);
+ mReaderClient = gettid();
+ return OK;
+}
+
+status_t StreamInHalHidl::getInputFramesLost(uint32_t *framesLost) {
+ if (mStream == 0) return NO_INIT;
+ return processReturn("getInputFramesLost", mStream->getInputFramesLost(), framesLost);
+}
+
+status_t StreamInHalHidl::getCapturePosition(int64_t *frames, int64_t *time) {
+ if (mStream == 0) return NO_INIT;
+ if (mReaderClient == gettid() && mCommandMQ) {
+ ReadParameters params;
+ params.command = ReadCommand::GET_CAPTURE_POSITION;
+ return callReaderThread(params, "getCapturePosition",
+ [&](const ReadStatus& readStatus) {
+ *frames = readStatus.reply.capturePosition.frames;
+ *time = readStatus.reply.capturePosition.time;
+ });
+ } else {
+ Result retval;
+ Return<void> ret = mStream->getCapturePosition(
+ [&](Result r, uint64_t hidlFrames, uint64_t hidlTime) {
+ retval = r;
+ if (retval == Result::OK) {
+ *frames = hidlFrames;
+ *time = hidlTime;
+ }
+ });
+ return processReturn("getCapturePosition", ret, retval);
+ }
+}
+
+} // namespace V4_0
+} // namespace android
diff --git a/media/libaudiohal/4.0/StreamHalHidl.h b/media/libaudiohal/4.0/StreamHalHidl.h
new file mode 100644
index 0000000..8d4dc8c
--- /dev/null
+++ b/media/libaudiohal/4.0/StreamHalHidl.h
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2016 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 ANDROID_HARDWARE_STREAM_HAL_HIDL_4_0_H
+#define ANDROID_HARDWARE_STREAM_HAL_HIDL_4_0_H
+
+#include <atomic>
+
+#include <android/hardware/audio/4.0/IStream.h>
+#include <android/hardware/audio/4.0/IStreamIn.h>
+#include <android/hardware/audio/4.0/IStreamOut.h>
+#include <fmq/EventFlag.h>
+#include <fmq/MessageQueue.h>
+#include <media/audiohal/StreamHalInterface.h>
+
+#include "ConversionHelperHidl.h"
+#include "StreamPowerLog.h"
+
+using ::android::hardware::audio::V4_0::IStream;
+using ::android::hardware::audio::V4_0::IStreamIn;
+using ::android::hardware::audio::V4_0::IStreamOut;
+using ::android::hardware::EventFlag;
+using ::android::hardware::MessageQueue;
+using ::android::hardware::Return;
+using ReadParameters = ::android::hardware::audio::V4_0::IStreamIn::ReadParameters;
+using ReadStatus = ::android::hardware::audio::V4_0::IStreamIn::ReadStatus;
+using WriteCommand = ::android::hardware::audio::V4_0::IStreamOut::WriteCommand;
+using WriteStatus = ::android::hardware::audio::V4_0::IStreamOut::WriteStatus;
+
+namespace android {
+namespace V4_0 {
+
+class DeviceHalHidl;
+
+class StreamHalHidl : public virtual StreamHalInterface, public ConversionHelperHidl
+{
+ public:
+ // Return the sampling rate in Hz - eg. 44100.
+ virtual status_t getSampleRate(uint32_t *rate);
+
+ // Return size of input/output buffer in bytes for this stream - eg. 4800.
+ virtual status_t getBufferSize(size_t *size);
+
+ // Return the channel mask.
+ virtual status_t getChannelMask(audio_channel_mask_t *mask);
+
+ // Return the audio format - e.g. AUDIO_FORMAT_PCM_16_BIT.
+ virtual status_t getFormat(audio_format_t *format);
+
+ // Convenience method.
+ virtual status_t getAudioProperties(
+ uint32_t *sampleRate, audio_channel_mask_t *mask, audio_format_t *format);
+
+ // Set audio stream parameters.
+ virtual status_t setParameters(const String8& kvPairs);
+
+ // Get audio stream parameters.
+ virtual status_t getParameters(const String8& keys, String8 *values);
+
+ // Add or remove the effect on the stream.
+ virtual status_t addEffect(sp<EffectHalInterface> effect);
+ virtual status_t removeEffect(sp<EffectHalInterface> effect);
+
+ // Put the audio hardware input/output into standby mode.
+ virtual status_t standby();
+
+ virtual status_t dump(int fd);
+
+ // Start a stream operating in mmap mode.
+ virtual status_t start();
+
+ // Stop a stream operating in mmap mode.
+ virtual status_t stop();
+
+ // Retrieve information on the data buffer in mmap mode.
+ virtual status_t createMmapBuffer(int32_t minSizeFrames,
+ struct audio_mmap_buffer_info *info);
+
+ // Get current read/write position in the mmap buffer
+ virtual status_t getMmapPosition(struct audio_mmap_position *position);
+
+ // Set the priority of the thread that interacts with the HAL
+ // (must match the priority of the audioflinger's thread that calls 'read' / 'write')
+ virtual status_t setHalThreadPriority(int priority);
+
+ protected:
+ // Subclasses can not be constructed directly by clients.
+ explicit StreamHalHidl(IStream *stream);
+
+ // The destructor automatically closes the stream.
+ virtual ~StreamHalHidl();
+
+ status_t getCachedBufferSize(size_t *size);
+
+ bool requestHalThreadPriority(pid_t threadPid, pid_t threadId);
+
+ // mStreamPowerLog is used for audio signal power logging.
+ StreamPowerLog mStreamPowerLog;
+
+ private:
+ const int HAL_THREAD_PRIORITY_DEFAULT = -1;
+ IStream *mStream;
+ int mHalThreadPriority;
+ size_t mCachedBufferSize;
+};
+
+class StreamOutHalHidl : public StreamOutHalInterface, public StreamHalHidl {
+ public:
+ // Return the frame size (number of bytes per sample) of a stream.
+ virtual status_t getFrameSize(size_t *size);
+
+ // Return the audio hardware driver estimated latency in milliseconds.
+ virtual status_t getLatency(uint32_t *latency);
+
+ // Use this method in situations where audio mixing is done in the hardware.
+ virtual status_t setVolume(float left, float right);
+
+ // Write audio buffer to driver.
+ virtual status_t write(const void *buffer, size_t bytes, size_t *written);
+
+ // Return the number of audio frames written by the audio dsp to DAC since
+ // the output has exited standby.
+ virtual status_t getRenderPosition(uint32_t *dspFrames);
+
+ // Get the local time at which the next write to the audio driver will be presented.
+ virtual status_t getNextWriteTimestamp(int64_t *timestamp);
+
+ // Set the callback for notifying completion of non-blocking write and drain.
+ virtual status_t setCallback(wp<StreamOutHalInterfaceCallback> callback);
+
+ // Returns whether pause and resume operations are supported.
+ virtual status_t supportsPauseAndResume(bool *supportsPause, bool *supportsResume);
+
+ // Notifies to the audio driver to resume playback following a pause.
+ virtual status_t pause();
+
+ // Notifies to the audio driver to resume playback following a pause.
+ virtual status_t resume();
+
+ // Returns whether drain operation is supported.
+ virtual status_t supportsDrain(bool *supportsDrain);
+
+ // Requests notification when data buffered by the driver/hardware has been played.
+ virtual status_t drain(bool earlyNotify);
+
+ // Notifies to the audio driver to flush the queued data.
+ virtual status_t flush();
+
+ // Return a recent count of the number of audio frames presented to an external observer.
+ virtual status_t getPresentationPosition(uint64_t *frames, struct timespec *timestamp);
+
+ // Methods used by StreamOutCallback (HIDL).
+ void onWriteReady();
+ void onDrainReady();
+ void onError();
+
+ private:
+ friend class DeviceHalHidl;
+ typedef MessageQueue<WriteCommand, hardware::kSynchronizedReadWrite> CommandMQ;
+ typedef MessageQueue<uint8_t, hardware::kSynchronizedReadWrite> DataMQ;
+ typedef MessageQueue<WriteStatus, hardware::kSynchronizedReadWrite> StatusMQ;
+
+ wp<StreamOutHalInterfaceCallback> mCallback;
+ sp<IStreamOut> mStream;
+ std::unique_ptr<CommandMQ> mCommandMQ;
+ std::unique_ptr<DataMQ> mDataMQ;
+ std::unique_ptr<StatusMQ> mStatusMQ;
+ std::atomic<pid_t> mWriterClient;
+ EventFlag* mEfGroup;
+
+ // Can not be constructed directly by clients.
+ StreamOutHalHidl(const sp<IStreamOut>& stream);
+
+ virtual ~StreamOutHalHidl();
+
+ using WriterCallback = std::function<void(const WriteStatus& writeStatus)>;
+ status_t callWriterThread(
+ WriteCommand cmd, const char* cmdName,
+ const uint8_t* data, size_t dataSize, WriterCallback callback);
+ status_t prepareForWriting(size_t bufferSize);
+};
+
+class StreamInHalHidl : public StreamInHalInterface, public StreamHalHidl {
+ public:
+ // Return the frame size (number of bytes per sample) of a stream.
+ virtual status_t getFrameSize(size_t *size);
+
+ // Set the input gain for the audio driver.
+ virtual status_t setGain(float gain);
+
+ // Read audio buffer in from driver.
+ virtual status_t read(void *buffer, size_t bytes, size_t *read);
+
+ // Return the amount of input frames lost in the audio driver.
+ virtual status_t getInputFramesLost(uint32_t *framesLost);
+
+ // Return a recent count of the number of audio frames received and
+ // the clock time associated with that frame count.
+ virtual status_t getCapturePosition(int64_t *frames, int64_t *time);
+
+ private:
+ friend class DeviceHalHidl;
+ typedef MessageQueue<ReadParameters, hardware::kSynchronizedReadWrite> CommandMQ;
+ typedef MessageQueue<uint8_t, hardware::kSynchronizedReadWrite> DataMQ;
+ typedef MessageQueue<ReadStatus, hardware::kSynchronizedReadWrite> StatusMQ;
+
+ sp<IStreamIn> mStream;
+ std::unique_ptr<CommandMQ> mCommandMQ;
+ std::unique_ptr<DataMQ> mDataMQ;
+ std::unique_ptr<StatusMQ> mStatusMQ;
+ std::atomic<pid_t> mReaderClient;
+ EventFlag* mEfGroup;
+
+ // Can not be constructed directly by clients.
+ StreamInHalHidl(const sp<IStreamIn>& stream);
+
+ virtual ~StreamInHalHidl();
+
+ using ReaderCallback = std::function<void(const ReadStatus& readStatus)>;
+ status_t callReaderThread(
+ const ReadParameters& params, const char* cmdName, ReaderCallback callback);
+ status_t prepareForReading(size_t bufferSize);
+};
+
+} // namespace V4_0
+} // namespace android
+
+#endif // ANDROID_HARDWARE_STREAM_HAL_HIDL_4_0_H
diff --git a/media/libaudiohal/4.0/StreamHalLocal.cpp b/media/libaudiohal/4.0/StreamHalLocal.cpp
new file mode 100644
index 0000000..592a931
--- /dev/null
+++ b/media/libaudiohal/4.0/StreamHalLocal.cpp
@@ -0,0 +1,319 @@
+/*
+ * Copyright (C) 2016 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_TAG "StreamHalLocal"
+//#define LOG_NDEBUG 0
+
+#include <hardware/audio.h>
+#include <utils/Log.h>
+
+#include "DeviceHalLocal.h"
+#include "StreamHalLocal.h"
+#include "VersionUtils.h"
+
+namespace android {
+namespace V4_0 {
+
+StreamHalLocal::StreamHalLocal(audio_stream_t *stream, sp<DeviceHalLocal> device)
+ : mDevice(device),
+ mStream(stream) {
+ // Instrument audio signal power logging.
+ // Note: This assumes channel mask, format, and sample rate do not change after creation.
+ if (mStream != nullptr && mStreamPowerLog.isUserDebugOrEngBuild()) {
+ mStreamPowerLog.init(mStream->get_sample_rate(mStream),
+ mStream->get_channels(mStream),
+ mStream->get_format(mStream));
+ }
+}
+
+StreamHalLocal::~StreamHalLocal() {
+ mStream = 0;
+ mDevice.clear();
+}
+
+status_t StreamHalLocal::getSampleRate(uint32_t *rate) {
+ *rate = mStream->get_sample_rate(mStream);
+ return OK;
+}
+
+status_t StreamHalLocal::getBufferSize(size_t *size) {
+ *size = mStream->get_buffer_size(mStream);
+ return OK;
+}
+
+status_t StreamHalLocal::getChannelMask(audio_channel_mask_t *mask) {
+ *mask = mStream->get_channels(mStream);
+ return OK;
+}
+
+status_t StreamHalLocal::getFormat(audio_format_t *format) {
+ *format = mStream->get_format(mStream);
+ return OK;
+}
+
+status_t StreamHalLocal::getAudioProperties(
+ uint32_t *sampleRate, audio_channel_mask_t *mask, audio_format_t *format) {
+ *sampleRate = mStream->get_sample_rate(mStream);
+ *mask = mStream->get_channels(mStream);
+ *format = mStream->get_format(mStream);
+ return OK;
+}
+
+status_t StreamHalLocal::setParameters(const String8& kvPairs) {
+ return mStream->set_parameters(mStream, kvPairs.string());
+}
+
+status_t StreamHalLocal::getParameters(const String8& keys, String8 *values) {
+ char *halValues = mStream->get_parameters(mStream, keys.string());
+ if (halValues != NULL) {
+ values->setTo(halValues);
+ free(halValues);
+ } else {
+ values->clear();
+ }
+ return OK;
+}
+
+status_t StreamHalLocal::addEffect(sp<EffectHalInterface>) {
+ LOG_ALWAYS_FATAL("Local streams can not have effects");
+ return INVALID_OPERATION;
+}
+
+status_t StreamHalLocal::removeEffect(sp<EffectHalInterface>) {
+ LOG_ALWAYS_FATAL("Local streams can not have effects");
+ return INVALID_OPERATION;
+}
+
+status_t StreamHalLocal::standby() {
+ return mStream->standby(mStream);
+}
+
+status_t StreamHalLocal::dump(int fd) {
+ status_t status = mStream->dump(mStream, fd);
+ mStreamPowerLog.dump(fd);
+ return status;
+}
+
+status_t StreamHalLocal::setHalThreadPriority(int) {
+ // Don't need to do anything as local hal is executed by audioflinger directly
+ // on the same thread.
+ return OK;
+}
+
+StreamOutHalLocal::StreamOutHalLocal(audio_stream_out_t *stream, sp<DeviceHalLocal> device)
+ : StreamHalLocal(&stream->common, device), mStream(stream) {
+}
+
+StreamOutHalLocal::~StreamOutHalLocal() {
+ mCallback.clear();
+ mDevice->closeOutputStream(mStream);
+ mStream = 0;
+}
+
+status_t StreamOutHalLocal::getFrameSize(size_t *size) {
+ *size = audio_stream_out_frame_size(mStream);
+ return OK;
+}
+
+status_t StreamOutHalLocal::getLatency(uint32_t *latency) {
+ *latency = mStream->get_latency(mStream);
+ return OK;
+}
+
+status_t StreamOutHalLocal::setVolume(float left, float right) {
+ if (mStream->set_volume == NULL) return INVALID_OPERATION;
+ return mStream->set_volume(mStream, left, right);
+}
+
+status_t StreamOutHalLocal::write(const void *buffer, size_t bytes, size_t *written) {
+ ssize_t writeResult = mStream->write(mStream, buffer, bytes);
+ if (writeResult > 0) {
+ *written = writeResult;
+ mStreamPowerLog.log(buffer, *written);
+ return OK;
+ } else {
+ *written = 0;
+ return writeResult;
+ }
+}
+
+status_t StreamOutHalLocal::getRenderPosition(uint32_t *dspFrames) {
+ return mStream->get_render_position(mStream, dspFrames);
+}
+
+status_t StreamOutHalLocal::getNextWriteTimestamp(int64_t *timestamp) {
+ if (mStream->get_next_write_timestamp == NULL) return INVALID_OPERATION;
+ return mStream->get_next_write_timestamp(mStream, timestamp);
+}
+
+status_t StreamOutHalLocal::setCallback(wp<StreamOutHalInterfaceCallback> callback) {
+ if (mStream->set_callback == NULL) return INVALID_OPERATION;
+ status_t result = mStream->set_callback(mStream, StreamOutHalLocal::asyncCallback, this);
+ if (result == OK) {
+ mCallback = callback;
+ }
+ return result;
+}
+
+// static
+int StreamOutHalLocal::asyncCallback(stream_callback_event_t event, void*, void *cookie) {
+ // We act as if we gave a wp<StreamOutHalLocal> to HAL. This way we should handle
+ // correctly the case when the callback is invoked while StreamOutHalLocal's destructor is
+ // already running, because the destructor is invoked after the refcount has been atomically
+ // decremented.
+ wp<StreamOutHalLocal> weakSelf(static_cast<StreamOutHalLocal*>(cookie));
+ sp<StreamOutHalLocal> self = weakSelf.promote();
+ if (self == 0) return 0;
+ sp<StreamOutHalInterfaceCallback> callback = self->mCallback.promote();
+ if (callback == 0) return 0;
+ ALOGV("asyncCallback() event %d", event);
+ switch (event) {
+ case STREAM_CBK_EVENT_WRITE_READY:
+ callback->onWriteReady();
+ break;
+ case STREAM_CBK_EVENT_DRAIN_READY:
+ callback->onDrainReady();
+ break;
+ case STREAM_CBK_EVENT_ERROR:
+ callback->onError();
+ break;
+ default:
+ ALOGW("asyncCallback() unknown event %d", event);
+ break;
+ }
+ return 0;
+}
+
+status_t StreamOutHalLocal::supportsPauseAndResume(bool *supportsPause, bool *supportsResume) {
+ *supportsPause = mStream->pause != NULL;
+ *supportsResume = mStream->resume != NULL;
+ return OK;
+}
+
+status_t StreamOutHalLocal::pause() {
+ if (mStream->pause == NULL) return INVALID_OPERATION;
+ return mStream->pause(mStream);
+}
+
+status_t StreamOutHalLocal::resume() {
+ if (mStream->resume == NULL) return INVALID_OPERATION;
+ return mStream->resume(mStream);
+}
+
+status_t StreamOutHalLocal::supportsDrain(bool *supportsDrain) {
+ *supportsDrain = mStream->drain != NULL;
+ return OK;
+}
+
+status_t StreamOutHalLocal::drain(bool earlyNotify) {
+ if (mStream->drain == NULL) return INVALID_OPERATION;
+ return mStream->drain(mStream, earlyNotify ? AUDIO_DRAIN_EARLY_NOTIFY : AUDIO_DRAIN_ALL);
+}
+
+status_t StreamOutHalLocal::flush() {
+ if (mStream->flush == NULL) return INVALID_OPERATION;
+ return mStream->flush(mStream);
+}
+
+status_t StreamOutHalLocal::getPresentationPosition(uint64_t *frames, struct timespec *timestamp) {
+ if (mStream->get_presentation_position == NULL) return INVALID_OPERATION;
+ return mStream->get_presentation_position(mStream, frames, timestamp);
+}
+
+status_t StreamOutHalLocal::start() {
+ if (mStream->start == NULL) return INVALID_OPERATION;
+ return mStream->start(mStream);
+}
+
+status_t StreamOutHalLocal::stop() {
+ if (mStream->stop == NULL) return INVALID_OPERATION;
+ return mStream->stop(mStream);
+}
+
+status_t StreamOutHalLocal::createMmapBuffer(int32_t minSizeFrames,
+ struct audio_mmap_buffer_info *info) {
+ if (mStream->create_mmap_buffer == NULL) return INVALID_OPERATION;
+ return mStream->create_mmap_buffer(mStream, minSizeFrames, info);
+}
+
+status_t StreamOutHalLocal::getMmapPosition(struct audio_mmap_position *position) {
+ if (mStream->get_mmap_position == NULL) return INVALID_OPERATION;
+ return mStream->get_mmap_position(mStream, position);
+}
+
+StreamInHalLocal::StreamInHalLocal(audio_stream_in_t *stream, sp<DeviceHalLocal> device)
+ : StreamHalLocal(&stream->common, device), mStream(stream) {
+}
+
+StreamInHalLocal::~StreamInHalLocal() {
+ mDevice->closeInputStream(mStream);
+ mStream = 0;
+}
+
+status_t StreamInHalLocal::getFrameSize(size_t *size) {
+ *size = audio_stream_in_frame_size(mStream);
+ return OK;
+}
+
+status_t StreamInHalLocal::setGain(float gain) {
+ return mStream->set_gain(mStream, gain);
+}
+
+status_t StreamInHalLocal::read(void *buffer, size_t bytes, size_t *read) {
+ ssize_t readResult = mStream->read(mStream, buffer, bytes);
+ if (readResult > 0) {
+ *read = readResult;
+ mStreamPowerLog.log( buffer, *read);
+ return OK;
+ } else {
+ *read = 0;
+ return readResult;
+ }
+}
+
+status_t StreamInHalLocal::getInputFramesLost(uint32_t *framesLost) {
+ *framesLost = mStream->get_input_frames_lost(mStream);
+ return OK;
+}
+
+status_t StreamInHalLocal::getCapturePosition(int64_t *frames, int64_t *time) {
+ if (mStream->get_capture_position == NULL) return INVALID_OPERATION;
+ return mStream->get_capture_position(mStream, frames, time);
+}
+
+status_t StreamInHalLocal::start() {
+ if (mStream->start == NULL) return INVALID_OPERATION;
+ return mStream->start(mStream);
+}
+
+status_t StreamInHalLocal::stop() {
+ if (mStream->stop == NULL) return INVALID_OPERATION;
+ return mStream->stop(mStream);
+}
+
+status_t StreamInHalLocal::createMmapBuffer(int32_t minSizeFrames,
+ struct audio_mmap_buffer_info *info) {
+ if (mStream->create_mmap_buffer == NULL) return INVALID_OPERATION;
+ return mStream->create_mmap_buffer(mStream, minSizeFrames, info);
+}
+
+status_t StreamInHalLocal::getMmapPosition(struct audio_mmap_position *position) {
+ if (mStream->get_mmap_position == NULL) return INVALID_OPERATION;
+ return mStream->get_mmap_position(mStream, position);
+}
+
+} // namespace V4_0
+} // namespace android
diff --git a/media/libaudiohal/4.0/StreamHalLocal.h b/media/libaudiohal/4.0/StreamHalLocal.h
new file mode 100644
index 0000000..076bc4c
--- /dev/null
+++ b/media/libaudiohal/4.0/StreamHalLocal.h
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2016 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 ANDROID_HARDWARE_STREAM_HAL_LOCAL_4_0_H
+#define ANDROID_HARDWARE_STREAM_HAL_LOCAL_4_0_H
+
+#include <media/audiohal/StreamHalInterface.h>
+#include "StreamPowerLog.h"
+
+namespace android {
+namespace V4_0 {
+
+class DeviceHalLocal;
+
+class StreamHalLocal : public virtual StreamHalInterface
+{
+ public:
+ // Return the sampling rate in Hz - eg. 44100.
+ virtual status_t getSampleRate(uint32_t *rate);
+
+ // Return size of input/output buffer in bytes for this stream - eg. 4800.
+ virtual status_t getBufferSize(size_t *size);
+
+ // Return the channel mask.
+ virtual status_t getChannelMask(audio_channel_mask_t *mask);
+
+ // Return the audio format - e.g. AUDIO_FORMAT_PCM_16_BIT.
+ virtual status_t getFormat(audio_format_t *format);
+
+ // Convenience method.
+ virtual status_t getAudioProperties(
+ uint32_t *sampleRate, audio_channel_mask_t *mask, audio_format_t *format);
+
+ // Set audio stream parameters.
+ virtual status_t setParameters(const String8& kvPairs);
+
+ // Get audio stream parameters.
+ virtual status_t getParameters(const String8& keys, String8 *values);
+
+ // Add or remove the effect on the stream.
+ virtual status_t addEffect(sp<EffectHalInterface> effect);
+ virtual status_t removeEffect(sp<EffectHalInterface> effect);
+
+ // Put the audio hardware input/output into standby mode.
+ virtual status_t standby();
+
+ virtual status_t dump(int fd);
+
+ // Start a stream operating in mmap mode.
+ virtual status_t start() = 0;
+
+ // Stop a stream operating in mmap mode.
+ virtual status_t stop() = 0;
+
+ // Retrieve information on the data buffer in mmap mode.
+ virtual status_t createMmapBuffer(int32_t minSizeFrames,
+ struct audio_mmap_buffer_info *info) = 0;
+
+ // Get current read/write position in the mmap buffer
+ virtual status_t getMmapPosition(struct audio_mmap_position *position) = 0;
+
+ // Set the priority of the thread that interacts with the HAL
+ // (must match the priority of the audioflinger's thread that calls 'read' / 'write')
+ virtual status_t setHalThreadPriority(int priority);
+
+ protected:
+ // Subclasses can not be constructed directly by clients.
+ StreamHalLocal(audio_stream_t *stream, sp<DeviceHalLocal> device);
+
+ // The destructor automatically closes the stream.
+ virtual ~StreamHalLocal();
+
+ sp<DeviceHalLocal> mDevice;
+
+ // mStreamPowerLog is used for audio signal power logging.
+ StreamPowerLog mStreamPowerLog;
+
+ private:
+ audio_stream_t *mStream;
+};
+
+class StreamOutHalLocal : public StreamOutHalInterface, public StreamHalLocal {
+ public:
+ // Return the frame size (number of bytes per sample) of a stream.
+ virtual status_t getFrameSize(size_t *size);
+
+ // Return the audio hardware driver estimated latency in milliseconds.
+ virtual status_t getLatency(uint32_t *latency);
+
+ // Use this method in situations where audio mixing is done in the hardware.
+ virtual status_t setVolume(float left, float right);
+
+ // Write audio buffer to driver.
+ virtual status_t write(const void *buffer, size_t bytes, size_t *written);
+
+ // Return the number of audio frames written by the audio dsp to DAC since
+ // the output has exited standby.
+ virtual status_t getRenderPosition(uint32_t *dspFrames);
+
+ // Get the local time at which the next write to the audio driver will be presented.
+ virtual status_t getNextWriteTimestamp(int64_t *timestamp);
+
+ // Set the callback for notifying completion of non-blocking write and drain.
+ virtual status_t setCallback(wp<StreamOutHalInterfaceCallback> callback);
+
+ // Returns whether pause and resume operations are supported.
+ virtual status_t supportsPauseAndResume(bool *supportsPause, bool *supportsResume);
+
+ // Notifies to the audio driver to resume playback following a pause.
+ virtual status_t pause();
+
+ // Notifies to the audio driver to resume playback following a pause.
+ virtual status_t resume();
+
+ // Returns whether drain operation is supported.
+ virtual status_t supportsDrain(bool *supportsDrain);
+
+ // Requests notification when data buffered by the driver/hardware has been played.
+ virtual status_t drain(bool earlyNotify);
+
+ // Notifies to the audio driver to flush the queued data.
+ virtual status_t flush();
+
+ // Return a recent count of the number of audio frames presented to an external observer.
+ virtual status_t getPresentationPosition(uint64_t *frames, struct timespec *timestamp);
+
+ // Start a stream operating in mmap mode.
+ virtual status_t start();
+
+ // Stop a stream operating in mmap mode.
+ virtual status_t stop();
+
+ // Retrieve information on the data buffer in mmap mode.
+ virtual status_t createMmapBuffer(int32_t minSizeFrames,
+ struct audio_mmap_buffer_info *info);
+
+ // Get current read/write position in the mmap buffer
+ virtual status_t getMmapPosition(struct audio_mmap_position *position);
+
+ private:
+ audio_stream_out_t *mStream;
+ wp<StreamOutHalInterfaceCallback> mCallback;
+
+ friend class DeviceHalLocal;
+
+ // Can not be constructed directly by clients.
+ StreamOutHalLocal(audio_stream_out_t *stream, sp<DeviceHalLocal> device);
+
+ virtual ~StreamOutHalLocal();
+
+ static int asyncCallback(stream_callback_event_t event, void *param, void *cookie);
+};
+
+class StreamInHalLocal : public StreamInHalInterface, public StreamHalLocal {
+ public:
+ // Return the frame size (number of bytes per sample) of a stream.
+ virtual status_t getFrameSize(size_t *size);
+
+ // Set the input gain for the audio driver.
+ virtual status_t setGain(float gain);
+
+ // Read audio buffer in from driver.
+ virtual status_t read(void *buffer, size_t bytes, size_t *read);
+
+ // Return the amount of input frames lost in the audio driver.
+ virtual status_t getInputFramesLost(uint32_t *framesLost);
+
+ // Return a recent count of the number of audio frames received and
+ // the clock time associated with that frame count.
+ virtual status_t getCapturePosition(int64_t *frames, int64_t *time);
+
+ // Start a stream operating in mmap mode.
+ virtual status_t start();
+
+ // Stop a stream operating in mmap mode.
+ virtual status_t stop();
+
+ // Retrieve information on the data buffer in mmap mode.
+ virtual status_t createMmapBuffer(int32_t minSizeFrames,
+ struct audio_mmap_buffer_info *info);
+
+ // Get current read/write position in the mmap buffer
+ virtual status_t getMmapPosition(struct audio_mmap_position *position);
+
+ private:
+ audio_stream_in_t *mStream;
+
+ friend class DeviceHalLocal;
+
+ // Can not be constructed directly by clients.
+ StreamInHalLocal(audio_stream_in_t *stream, sp<DeviceHalLocal> device);
+
+ virtual ~StreamInHalLocal();
+};
+
+} // namespace V4_0
+} // namespace android
+
+#endif // ANDROID_HARDWARE_STREAM_HAL_LOCAL_4_0_H
diff --git a/media/libaudiohal/4.0/StreamPowerLog.h b/media/libaudiohal/4.0/StreamPowerLog.h
new file mode 100644
index 0000000..57b7201
--- /dev/null
+++ b/media/libaudiohal/4.0/StreamPowerLog.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2017 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 ANDROID_HARDWARE_STREAM_POWER_LOG_4_0_H
+#define ANDROID_HARDWARE_STREAM_POWER_LOG_4_0_H
+
+#include <audio_utils/clock.h>
+#include <audio_utils/PowerLog.h>
+#include <cutils/properties.h>
+#include <system/audio.h>
+
+namespace android {
+namespace V4_0 {
+
+class StreamPowerLog {
+public:
+ StreamPowerLog() :
+ mIsUserDebugOrEngBuild(is_userdebug_or_eng_build()),
+ mPowerLog(nullptr),
+ mFrameSize(0) {
+ // use init() to set up the power log.
+ }
+
+ ~StreamPowerLog() {
+ power_log_destroy(mPowerLog); // OK for null mPowerLog
+ mPowerLog = nullptr;
+ }
+
+ // A one-time initialization (do not call twice) before using StreamPowerLog.
+ void init(uint32_t sampleRate, audio_channel_mask_t channelMask, audio_format_t format) {
+ if (mPowerLog == nullptr) {
+ // Note: A way to get channel count for both input and output channel masks
+ // but does not check validity of the channel mask.
+ const uint32_t channelCount = popcount(audio_channel_mask_get_bits(channelMask));
+ mFrameSize = channelCount * audio_bytes_per_sample(format);
+ if (mFrameSize > 0) {
+ const size_t kPowerLogFramesPerEntry =
+ (long long)sampleRate * kPowerLogSamplingIntervalMs / 1000;
+ mPowerLog = power_log_create(
+ sampleRate,
+ channelCount,
+ format,
+ kPowerLogEntries,
+ kPowerLogFramesPerEntry);
+ }
+ }
+ // mPowerLog may be NULL (not the right build, format not accepted, etc.).
+ }
+
+ // Dump the power log to fd.
+ void dump(int fd) const {
+ // OK for null mPowerLog
+ (void)power_log_dump(
+ mPowerLog, fd, " " /* prefix */, kPowerLogLines, 0 /* limit_ns */);
+ }
+
+ // Log the audio data contained in buffer.
+ void log(const void *buffer, size_t sizeInBytes) const {
+ if (mPowerLog != nullptr) { // mFrameSize is always nonzero if mPowerLog exists.
+ power_log_log(
+ mPowerLog, buffer, sizeInBytes / mFrameSize, audio_utils_get_real_time_ns());
+ }
+ }
+
+ bool isUserDebugOrEngBuild() const {
+ return mIsUserDebugOrEngBuild;
+ }
+
+private:
+
+ static inline bool is_userdebug_or_eng_build() {
+ char value[PROPERTY_VALUE_MAX];
+ (void)property_get("ro.build.type", value, "unknown"); // ignore actual length
+ return strcmp(value, "userdebug") == 0 || strcmp(value, "eng") == 0;
+ }
+
+ // Audio signal power log configuration.
+ static const size_t kPowerLogLines = 40;
+ static const size_t kPowerLogSamplingIntervalMs = 50;
+ static const size_t kPowerLogEntries = (1 /* minutes */ * 60 /* seconds */ * 1000 /* msec */
+ / kPowerLogSamplingIntervalMs);
+
+ const bool mIsUserDebugOrEngBuild;
+ power_log_t *mPowerLog;
+ size_t mFrameSize;
+};
+
+} // namespace V4_0
+} // namespace android
+
+#endif // ANDROID_HARDWARE_STREAM_POWER_LOG_4_0_H
diff --git a/media/libaudiohal/4.0/VersionUtils.h b/media/libaudiohal/4.0/VersionUtils.h
new file mode 100644
index 0000000..1246c2e
--- /dev/null
+++ b/media/libaudiohal/4.0/VersionUtils.h
@@ -0,0 +1,49 @@
+/*
+ * 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 ANDROID_HARDWARE_VERSION_UTILS_4_0_H
+#define ANDROID_HARDWARE_VERSION_UTILS_4_0_H
+
+#include <android/hardware/audio/4.0/types.h>
+#include <hidl/HidlSupport.h>
+
+using ::android::hardware::audio::V4_0::ParameterValue;
+using ::android::hardware::audio::V4_0::Result;
+using ::android::hardware::Return;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::hidl_string;
+
+namespace android {
+namespace V4_0 {
+namespace utils {
+
+template <class T, class Callback>
+Return<void> getParameters(T& object, hidl_vec<ParameterValue> context,
+ hidl_vec<hidl_string> keys, Callback callback) {
+ return object->getParameters(context, keys, callback);
+}
+
+template <class T>
+Return<Result> setParameters(T& object, hidl_vec<ParameterValue> context,
+ hidl_vec<ParameterValue> keys) {
+ return object->setParameters(context, keys);
+}
+
+} // namespace utils
+} // namespace V4_0
+} // namespace android
+
+#endif // ANDROID_HARDWARE_VERSION_UTILS_4_0_H
diff --git a/media/libaudiohal/4.0/include/libaudiohal/4.0/DevicesFactoryHalHybrid.h b/media/libaudiohal/4.0/include/libaudiohal/4.0/DevicesFactoryHalHybrid.h
new file mode 100644
index 0000000..abf6de0
--- /dev/null
+++ b/media/libaudiohal/4.0/include/libaudiohal/4.0/DevicesFactoryHalHybrid.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2017 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 ANDROID_HARDWARE_DEVICES_FACTORY_HAL_HYBRID_4_0_H
+#define ANDROID_HARDWARE_DEVICES_FACTORY_HAL_HYBRID_4_0_H
+
+#include <media/audiohal/DevicesFactoryHalInterface.h>
+#include <utils/Errors.h>
+#include <utils/RefBase.h>
+
+namespace android {
+namespace V4_0 {
+
+class DevicesFactoryHalHybrid : public DevicesFactoryHalInterface
+{
+ public:
+ // Opens a device with the specified name. To close the device, it is
+ // necessary to release references to the returned object.
+ virtual status_t openDevice(const char *name, sp<DeviceHalInterface> *device);
+
+ private:
+ friend class DevicesFactoryHalInterface;
+
+ // Can not be constructed directly by clients.
+ DevicesFactoryHalHybrid();
+
+ virtual ~DevicesFactoryHalHybrid();
+
+ sp<DevicesFactoryHalInterface> mLocalFactory;
+ sp<DevicesFactoryHalInterface> mHidlFactory;
+};
+
+} // namespace V4_0
+} // namespace android
+
+#endif // ANDROID_HARDWARE_DEVICES_FACTORY_HAL_HYBRID_4_0_H
diff --git a/media/libaudiohal/4.0/include/libaudiohal/4.0/EffectsFactoryHalHidl.h b/media/libaudiohal/4.0/include/libaudiohal/4.0/EffectsFactoryHalHidl.h
new file mode 100644
index 0000000..680b7a1
--- /dev/null
+++ b/media/libaudiohal/4.0/include/libaudiohal/4.0/EffectsFactoryHalHidl.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2016 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 ANDROID_HARDWARE_EFFECTS_FACTORY_HAL_HIDL_4_0_H
+#define ANDROID_HARDWARE_EFFECTS_FACTORY_HAL_HIDL_4_0_H
+
+#include <android/hardware/audio/effect/4.0/IEffectsFactory.h>
+#include <android/hardware/audio/effect/4.0/types.h>
+#include <media/audiohal/EffectsFactoryHalInterface.h>
+
+#include "ConversionHelperHidl.h"
+
+namespace android {
+namespace V4_0 {
+
+using ::android::hardware::audio::effect::V4_0::EffectDescriptor;
+using ::android::hardware::audio::effect::V4_0::IEffectsFactory;
+using ::android::hardware::hidl_vec;
+
+class EffectsFactoryHalHidl : public EffectsFactoryHalInterface, public ConversionHelperHidl
+{
+ public:
+ // Returns the number of different effects in all loaded libraries.
+ virtual status_t queryNumberEffects(uint32_t *pNumEffects);
+
+ // Returns a descriptor of the next available effect.
+ virtual status_t getDescriptor(uint32_t index,
+ effect_descriptor_t *pDescriptor);
+
+ virtual status_t getDescriptor(const effect_uuid_t *pEffectUuid,
+ effect_descriptor_t *pDescriptor);
+
+ // Creates an effect engine of the specified type.
+ // To release the effect engine, it is necessary to release references
+ // to the returned effect object.
+ virtual status_t createEffect(const effect_uuid_t *pEffectUuid,
+ int32_t sessionId, int32_t ioId,
+ sp<EffectHalInterface> *effect);
+
+ virtual status_t dumpEffects(int fd);
+
+ status_t allocateBuffer(size_t size, sp<EffectBufferHalInterface>* buffer) override;
+ status_t mirrorBuffer(void* external, size_t size,
+ sp<EffectBufferHalInterface>* buffer) override;
+
+ private:
+ friend class EffectsFactoryHalInterface;
+
+ sp<IEffectsFactory> mEffectsFactory;
+ hidl_vec<EffectDescriptor> mLastDescriptors;
+
+ // Can not be constructed directly by clients.
+ EffectsFactoryHalHidl();
+ virtual ~EffectsFactoryHalHidl();
+
+ status_t queryAllDescriptors();
+};
+
+} // namespace V4_0
+} // namespace android
+
+#endif // ANDROID_HARDWARE_EFFECTS_FACTORY_HAL_HIDL_4_0_H
diff --git a/media/libaudiohal/Android.bp b/media/libaudiohal/Android.bp
index 7ecb9d8..3a5df27 100644
--- a/media/libaudiohal/Android.bp
+++ b/media/libaudiohal/Android.bp
@@ -12,9 +12,12 @@
],
shared_libs: [
- "android.hardware.audio@2.0",
"android.hardware.audio.effect@2.0",
+ "android.hardware.audio.effect@4.0",
+ "android.hardware.audio@2.0",
+ "android.hardware.audio@4.0",
"libaudiohal@2.0",
+ "libaudiohal@4.0",
"libutils",
],
diff --git a/media/libaudiohal/DevicesFactoryHalInterface.cpp b/media/libaudiohal/DevicesFactoryHalInterface.cpp
index cfec3d6..4c8eaf6 100644
--- a/media/libaudiohal/DevicesFactoryHalInterface.cpp
+++ b/media/libaudiohal/DevicesFactoryHalInterface.cpp
@@ -14,16 +14,20 @@
* limitations under the License.
*/
-#include "DevicesFactoryHalHybrid.h"
#include <android/hardware/audio/2.0/IDevicesFactory.h>
+#include <android/hardware/audio/4.0/IDevicesFactory.h>
-using namespace ::android::hardware::audio;
+#include <DevicesFactoryHalHybrid.h>
+#include <libaudiohal/4.0/DevicesFactoryHalHybrid.h>
namespace android {
// static
sp<DevicesFactoryHalInterface> DevicesFactoryHalInterface::create() {
- if (V2_0::IDevicesFactory::getService() != nullptr) {
+ if (hardware::audio::V4_0::IDevicesFactory::getService() != nullptr) {
+ return new V4_0::DevicesFactoryHalHybrid();
+ }
+ if (hardware::audio::V2_0::IDevicesFactory::getService() != nullptr) {
return new DevicesFactoryHalHybrid();
}
return nullptr;
diff --git a/media/libaudiohal/EffectsFactoryHalInterface.cpp b/media/libaudiohal/EffectsFactoryHalInterface.cpp
index 01a171e..ead1fa2 100644
--- a/media/libaudiohal/EffectsFactoryHalInterface.cpp
+++ b/media/libaudiohal/EffectsFactoryHalInterface.cpp
@@ -14,21 +14,21 @@
* limitations under the License.
*/
-#define LOG_TAG "EffectsFactoryHalHidl"
-//#define LOG_NDEBUG 0
+#include <android/hardware/audio/effect/2.0/IEffectsFactory.h>
+#include <android/hardware/audio/effect/4.0/IEffectsFactory.h>
-#include "EffectHalHidl.h"
-#include "EffectsFactoryHalHidl.h"
+#include <EffectsFactoryHalHidl.h>
+#include <libaudiohal/4.0/EffectsFactoryHalHidl.h>
-using ::android::hardware::Return;
-
-using namespace ::android::hardware::audio::effect;
namespace android {
// static
sp<EffectsFactoryHalInterface> EffectsFactoryHalInterface::create() {
- if (V2_0::IEffectsFactory::getService() != nullptr) {
+ if (hardware::audio::effect::V4_0::IEffectsFactory::getService() != nullptr) {
+ return new V4_0::EffectsFactoryHalHidl();
+ }
+ if (hardware::audio::effect::V2_0::IEffectsFactory::getService() != nullptr) {
return new EffectsFactoryHalHidl();
}
return nullptr;
diff --git a/media/libheif/HeifDecoderImpl.cpp b/media/libheif/HeifDecoderImpl.cpp
index 2dfbdca..63130c4 100644
--- a/media/libheif/HeifDecoderImpl.cpp
+++ b/media/libheif/HeifDecoderImpl.cpp
@@ -140,6 +140,11 @@
// have been caught above.
CHECK(offset >= mCachedOffset);
+ off64_t resultOffset;
+ if (__builtin_add_overflow(offset, size, &resultOffset)) {
+ return ERROR_IO;
+ }
+
if (size == 0) {
return 0;
}
diff --git a/media/libmedia/NdkWrapper.cpp b/media/libmedia/NdkWrapper.cpp
index 5e47b48..2cdb44e 100644
--- a/media/libmedia/NdkWrapper.cpp
+++ b/media/libmedia/NdkWrapper.cpp
@@ -81,11 +81,16 @@
AMEDIAFORMAT_KEY_STRIDE,
AMEDIAFORMAT_KEY_TRACK_ID,
AMEDIAFORMAT_KEY_WIDTH,
+ AMEDIAFORMAT_KEY_DISPLAY_HEIGHT,
+ AMEDIAFORMAT_KEY_DISPLAY_WIDTH,
+ AMEDIAFORMAT_KEY_TEMPORAL_LAYER_ID,
+ AMEDIAFORMAT_KEY_TRACK_INDEX,
};
static const char *AMediaFormatKeyGroupInt64[] = {
AMEDIAFORMAT_KEY_DURATION,
AMEDIAFORMAT_KEY_REPEAT_PREVIOUS_FRAME_AFTER,
+ AMEDIAFORMAT_KEY_TIME_US,
};
static const char *AMediaFormatKeyGroupString[] = {
@@ -96,6 +101,14 @@
static const char *AMediaFormatKeyGroupBuffer[] = {
AMEDIAFORMAT_KEY_HDR_STATIC_INFO,
+ AMEDIAFORMAT_KEY_SEI,
+ AMEDIAFORMAT_KEY_MPEG_USER_DATA,
+};
+
+static const char *AMediaFormatKeyGroupCsd[] = {
+ AMEDIAFORMAT_KEY_CSD_0,
+ AMEDIAFORMAT_KEY_CSD_1,
+ AMEDIAFORMAT_KEY_CSD_2,
};
static const char *AMediaFormatKeyGroupRect[] = {
@@ -111,6 +124,10 @@
static status_t translateErrorCode(media_status_t err) {
if (err == AMEDIA_OK) {
return OK;
+ } else if (err == AMEDIA_ERROR_END_OF_STREAM) {
+ return ERROR_END_OF_STREAM;
+ } else if (err == AMEDIA_ERROR_IO) {
+ return ERROR_IO;
} else if (err == AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
return -EAGAIN;
}
@@ -303,11 +320,19 @@
}
sp<AMessage> AMediaFormatWrapper::toAMessage() const {
+ sp<AMessage> msg;
+ writeToAMessage(msg);
+ return msg;
+}
+
+void AMediaFormatWrapper::writeToAMessage(sp<AMessage> &msg) const {
if (mAMediaFormat == NULL) {
- return NULL;
+ msg = NULL;
}
- sp<AMessage> msg = new AMessage;
+ if (msg == NULL) {
+ msg = new AMessage;
+ }
for (auto& key : AMediaFormatKeyGroupInt32) {
int32_t val;
if (getInt32(key, &val)) {
@@ -334,6 +359,16 @@
msg->setBuffer(key, buffer);
}
}
+ for (auto& key : AMediaFormatKeyGroupCsd) {
+ void *data;
+ size_t size;
+ if (getBuffer(key, &data, &size)) {
+ sp<ABuffer> buffer = ABuffer::CreateAsCopy(data, size);
+ buffer->meta()->setInt32(AMEDIAFORMAT_KEY_CSD, 1);
+ buffer->meta()->setInt64(AMEDIAFORMAT_KEY_TIME_US, 0);
+ msg->setBuffer(key, buffer);
+ }
+ }
for (auto& key : AMediaFormatKeyGroupRect) {
int32_t left, top, right, bottom;
if (getRect(key, &left, &top, &right, &bottom)) {
@@ -351,7 +386,6 @@
}
}
}
- return msg;
}
const char* AMediaFormatWrapper::toString() const {
@@ -1034,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;
}
@@ -1150,6 +1192,15 @@
return AMediaExtractor_getSampleTime(mAMediaExtractor);
}
+status_t AMediaExtractorWrapper::getSampleFormat(sp<AMediaFormatWrapper> &formatWrapper) {
+ if (mAMediaExtractor == NULL) {
+ return DEAD_OBJECT;
+ }
+ AMediaFormat *format = AMediaFormat_new();
+ formatWrapper = new AMediaFormatWrapper(format);
+ return translateErrorCode(AMediaExtractor_getSampleFormat(mAMediaExtractor, format));
+}
+
int64_t AMediaExtractorWrapper::getCachedDuration() {
if (mAMediaExtractor == NULL) {
return -1;
diff --git a/media/libmedia/TypeConverter.cpp b/media/libmedia/TypeConverter.cpp
index 4cadeb1..9323d6d 100644
--- a/media/libmedia/TypeConverter.cpp
+++ b/media/libmedia/TypeConverter.cpp
@@ -55,6 +55,8 @@
MAKE_STRING_FROM_ENUM(AUDIO_DEVICE_OUT_BUS),
MAKE_STRING_FROM_ENUM(AUDIO_DEVICE_OUT_PROXY),
MAKE_STRING_FROM_ENUM(AUDIO_DEVICE_OUT_USB_HEADSET),
+ MAKE_STRING_FROM_ENUM(AUDIO_DEVICE_OUT_HEARING_AID),
+ MAKE_STRING_FROM_ENUM(AUDIO_DEVICE_OUT_ECHO_CANCELLER),
MAKE_STRING_FROM_ENUM(AUDIO_DEVICE_OUT_DEFAULT),
// STUB must be after DEFAULT, so the latter is picked up by toString first.
MAKE_STRING_FROM_ENUM(AUDIO_DEVICE_OUT_STUB),
@@ -115,9 +117,7 @@
MAKE_STRING_FROM_ENUM(AUDIO_OUTPUT_FLAG_DIRECT_PCM),
MAKE_STRING_FROM_ENUM(AUDIO_OUTPUT_FLAG_MMAP_NOIRQ),
MAKE_STRING_FROM_ENUM(AUDIO_OUTPUT_FLAG_VOIP_RX),
- // FIXME: this cast will be removed when the flag will be
- // declared in types.hal for audio HAL V4.0 and auto imported to audio-base.h
- {"AUDIO_OUTPUT_FLAG_INCALL_MUSIC", static_cast<audio_output_flags_t>(AUDIO_OUTPUT_FLAG_INCALL_MUSIC)},
+ MAKE_STRING_FROM_ENUM(AUDIO_OUTPUT_FLAG_INCALL_MUSIC),
TERMINATOR
};
@@ -131,6 +131,7 @@
MAKE_STRING_FROM_ENUM(AUDIO_INPUT_FLAG_SYNC),
MAKE_STRING_FROM_ENUM(AUDIO_INPUT_FLAG_MMAP_NOIRQ),
MAKE_STRING_FROM_ENUM(AUDIO_INPUT_FLAG_VOIP_TX),
+ MAKE_STRING_FROM_ENUM(AUDIO_INPUT_FLAG_HW_AV_SYNC),
TERMINATOR
};
@@ -157,6 +158,7 @@
MAKE_STRING_FROM_ENUM(AUDIO_FORMAT_AAC_LD),
MAKE_STRING_FROM_ENUM(AUDIO_FORMAT_AAC_HE_V2),
MAKE_STRING_FROM_ENUM(AUDIO_FORMAT_AAC_ELD),
+ MAKE_STRING_FROM_ENUM(AUDIO_FORMAT_AAC_XHE),
MAKE_STRING_FROM_ENUM(AUDIO_FORMAT_AAC_ADTS_MAIN),
MAKE_STRING_FROM_ENUM(AUDIO_FORMAT_AAC_ADTS_LC),
MAKE_STRING_FROM_ENUM(AUDIO_FORMAT_AAC_ADTS_SSR),
@@ -167,6 +169,7 @@
MAKE_STRING_FROM_ENUM(AUDIO_FORMAT_AAC_ADTS_LD),
MAKE_STRING_FROM_ENUM(AUDIO_FORMAT_AAC_ADTS_HE_V2),
MAKE_STRING_FROM_ENUM(AUDIO_FORMAT_AAC_ADTS_ELD),
+ MAKE_STRING_FROM_ENUM(AUDIO_FORMAT_AAC_ADTS_XHE),
MAKE_STRING_FROM_ENUM(AUDIO_FORMAT_VORBIS),
MAKE_STRING_FROM_ENUM(AUDIO_FORMAT_HE_AAC_V1),
MAKE_STRING_FROM_ENUM(AUDIO_FORMAT_HE_AAC_V2),
@@ -197,6 +200,11 @@
MAKE_STRING_FROM_ENUM(AUDIO_FORMAT_APTX_HD),
MAKE_STRING_FROM_ENUM(AUDIO_FORMAT_AC4),
MAKE_STRING_FROM_ENUM(AUDIO_FORMAT_LDAC),
+ MAKE_STRING_FROM_ENUM(AUDIO_FORMAT_MAT),
+ MAKE_STRING_FROM_ENUM(AUDIO_FORMAT_E_AC3_JOC),
+ MAKE_STRING_FROM_ENUM(AUDIO_FORMAT_MAT_1_0),
+ MAKE_STRING_FROM_ENUM(AUDIO_FORMAT_MAT_2_0),
+ MAKE_STRING_FROM_ENUM(AUDIO_FORMAT_MAT_2_1),
TERMINATOR
};
@@ -206,10 +214,18 @@
MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_OUT_MONO),
MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_OUT_STEREO),
MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_OUT_2POINT1),
+ MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_OUT_2POINT0POINT2),
+ MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_OUT_2POINT1POINT2),
+ MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_OUT_3POINT0POINT2),
+ MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_OUT_3POINT1POINT2),
MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_OUT_QUAD),
+ MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_OUT_QUAD_BACK),
+ MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_OUT_QUAD_SIDE),
MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_OUT_SURROUND),
MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_OUT_PENTA),
MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_OUT_5POINT1),
+ MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_OUT_5POINT1_BACK),
+ MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_OUT_5POINT1_SIDE),
MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_OUT_6POINT1),
MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_OUT_7POINT1),
TERMINATOR
@@ -222,6 +238,11 @@
MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_IN_STEREO),
MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_IN_FRONT_BACK),
MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_IN_6),
+ MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_IN_2POINT0POINT2),
+ MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_IN_2POINT1POINT2),
+ MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_IN_3POINT0POINT2),
+ MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_IN_3POINT1POINT2),
+ MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_IN_5POINT1),
MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_IN_VOICE_UPLINK_MONO),
MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_IN_VOICE_DNLINK_MONO),
MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_IN_VOICE_CALL_MONO),
@@ -308,8 +329,6 @@
MAKE_STRING_FROM_ENUM(AUDIO_USAGE_ASSISTANCE_SONIFICATION),
MAKE_STRING_FROM_ENUM(AUDIO_USAGE_GAME),
MAKE_STRING_FROM_ENUM(AUDIO_USAGE_VIRTUAL_SOURCE),
- MAKE_STRING_FROM_ENUM(AUDIO_USAGE_CNT),
- MAKE_STRING_FROM_ENUM(AUDIO_USAGE_MAX),
TERMINATOR
};
@@ -325,8 +344,6 @@
MAKE_STRING_FROM_ENUM(AUDIO_SOURCE_VOICE_COMMUNICATION),
MAKE_STRING_FROM_ENUM(AUDIO_SOURCE_REMOTE_SUBMIX),
MAKE_STRING_FROM_ENUM(AUDIO_SOURCE_UNPROCESSED),
- MAKE_STRING_FROM_ENUM(AUDIO_SOURCE_CNT),
- MAKE_STRING_FROM_ENUM(AUDIO_SOURCE_MAX),
MAKE_STRING_FROM_ENUM(AUDIO_SOURCE_FM_TUNER),
MAKE_STRING_FROM_ENUM(AUDIO_SOURCE_HOTWORD),
TERMINATOR
diff --git a/media/libmedia/include/media/NdkWrapper.h b/media/libmedia/include/media/NdkWrapper.h
index b71b758..c97d171 100644
--- a/media/libmedia/include/media/NdkWrapper.h
+++ b/media/libmedia/include/media/NdkWrapper.h
@@ -45,6 +45,7 @@
class MetaData;
struct AMediaFormatWrapper : public RefBase {
+
static sp<AMediaFormatWrapper> Create(const sp<AMessage> &message);
AMediaFormatWrapper();
@@ -54,6 +55,7 @@
AMediaFormat *getAMediaFormat() const;
sp<AMessage> toAMessage() const ;
+ void writeToAMessage(sp<AMessage>&) const ;
const char* toString() const ;
status_t release();
@@ -285,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);
@@ -313,6 +317,8 @@
int64_t getSampleTime();
+ status_t getSampleFormat(sp<AMediaFormatWrapper> &formatWrapper);
+
int64_t getCachedDuration();
bool advance();
diff --git a/packages/MediaComponents/test/src/android/media/MockActivity.java b/media/libmediaextractor/include/media/ExtractorUtils.h
similarity index 63%
rename from packages/MediaComponents/test/src/android/media/MockActivity.java
rename to media/libmediaextractor/include/media/ExtractorUtils.h
index 4627530..22f9349 100644
--- a/packages/MediaComponents/test/src/android/media/MockActivity.java
+++ b/media/libmediaextractor/include/media/ExtractorUtils.h
@@ -1,5 +1,5 @@
/*
- * Copyright 2018 The Android Open Source Project
+ * 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.
@@ -14,9 +14,19 @@
* limitations under the License.
*/
-package android.media;
+#ifndef EXTRACTOR_UTILS_H_
-import android.app.Activity;
+#define EXTRACTOR_UTILS_H_
-public class MockActivity extends Activity {
+#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/libmediaplayer2/nuplayer2/GenericSource2.cpp b/media/libmediaplayer2/nuplayer2/GenericSource2.cpp
index 790581a..196b103 100644
--- a/media/libmediaplayer2/nuplayer2/GenericSource2.cpp
+++ b/media/libmediaplayer2/nuplayer2/GenericSource2.cpp
@@ -35,13 +35,13 @@
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/AMessage.h>
#include <media/stagefright/DataSourceFactory.h>
-#include <media/stagefright/FileSource.h>
#include <media/stagefright/InterfaceUtils.h>
#include <media/stagefright/MediaBuffer.h>
#include <media/stagefright/MediaClock.h>
#include <media/stagefright/MediaDefs.h>
#include <media/stagefright/MediaExtractorFactory.h>
#include <media/stagefright/MetaData.h>
+#include <media/stagefright/NdkUtils.h>
#include <media/stagefright/Utils.h>
#include "../../libstagefright/include/NuCachedSource2.h"
#include "../../libstagefright/include/HTTPBase.h"
@@ -161,29 +161,40 @@
}
status_t NuPlayer2::GenericSource2::initFromDataSource() {
- sp<IMediaExtractor> extractor;
- CHECK(mDataSource != NULL);
+ mExtractor = new AMediaExtractorWrapper(AMediaExtractor_new());
+ CHECK(mDataSource != NULL || mFd != -1);
sp<DataSource> dataSource = mDataSource;
+ const int fd = mFd;
+ const int64_t offset = mOffset;
+ const int64_t length = mLength;
mLock.unlock();
// This might take long time if data source is not reliable.
- extractor = MediaExtractorFactory::Create(dataSource, NULL);
+ status_t err;
+ if (dataSource != nullptr) {
+ mDataSourceWrapper = new AMediaDataSourceWrapper(dataSource);
+ err = mExtractor->setDataSource(mDataSourceWrapper->getAMediaDataSource());
+ } else {
+ err = mExtractor->setDataSource(fd, offset, length);
+ }
- if (extractor == NULL) {
- ALOGE("initFromDataSource, cannot create extractor!");
+ if (err != OK) {
+ ALOGE("initFromDataSource, failed to create data source!");
+ mLock.lock();
return UNKNOWN_ERROR;
}
- sp<MetaData> fileMeta = extractor->getMetaData();
-
- size_t numtracks = extractor->countTracks();
+ size_t numtracks = mExtractor->getTrackCount();
if (numtracks == 0) {
ALOGE("initFromDataSource, source has no track!");
+ mLock.lock();
return UNKNOWN_ERROR;
}
mLock.lock();
- mFileMeta = fileMeta;
+ mFd = -1;
+ mDataSource = dataSource;
+ mFileMeta = convertMediaFormatWrapperToMetaData(mExtractor->getFormat());
if (mFileMeta != NULL) {
int64_t duration;
if (mFileMeta->findInt64(kKeyDuration, &duration)) {
@@ -196,18 +207,22 @@
mMimes.clear();
for (size_t i = 0; i < numtracks; ++i) {
- sp<IMediaSource> track = extractor->getTrack(i);
- if (track == NULL) {
- continue;
- }
- sp<MetaData> meta = extractor->getTrackMetaData(i);
- if (meta == NULL) {
+ sp<AMediaFormatWrapper> trackFormat = mExtractor->getTrackFormat(i);
+ if (trackFormat == NULL) {
ALOGE("no metadata for track %zu", i);
return UNKNOWN_ERROR;
}
+ sp<AMediaExtractorWrapper> trackExtractor = new AMediaExtractorWrapper(AMediaExtractor_new());
+ if (mDataSourceWrapper != nullptr) {
+ err = trackExtractor->setDataSource(mDataSourceWrapper->getAMediaDataSource());
+ } else {
+ err = trackExtractor->setDataSource(fd, offset, length);
+ }
+
const char *mime;
+ sp<MetaData> meta = convertMediaFormatWrapperToMetaData(trackFormat);
CHECK(meta->findCString(kKeyMIMEType, &mime));
ALOGV("initFromDataSource track[%zu]: %s", i, mime);
@@ -217,11 +232,11 @@
// extractor operation, some extractors might modify meta
// during getTrack() and make it invalid.
if (!strncasecmp(mime, "audio/", 6)) {
- if (mAudioTrack.mSource == NULL) {
+ if (mAudioTrack.mExtractor == NULL) {
mAudioTrack.mIndex = i;
- mAudioTrack.mSource = track;
- mAudioTrack.mPackets =
- new AnotherPacketSource(mAudioTrack.mSource->getFormat());
+ mAudioTrack.mExtractor = trackExtractor;
+ mAudioTrack.mExtractor->selectTrack(i);
+ mAudioTrack.mPackets = new AnotherPacketSource(meta);
if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_VORBIS)) {
mAudioIsVorbis = true;
@@ -232,18 +247,18 @@
mMimes.add(String8(mime));
}
} else if (!strncasecmp(mime, "video/", 6)) {
- if (mVideoTrack.mSource == NULL) {
+ if (mVideoTrack.mExtractor == NULL) {
mVideoTrack.mIndex = i;
- mVideoTrack.mSource = track;
- mVideoTrack.mPackets =
- new AnotherPacketSource(mVideoTrack.mSource->getFormat());
+ mVideoTrack.mExtractor = trackExtractor;
+ mVideoTrack.mExtractor->selectTrack(i);
+ mVideoTrack.mPackets = new AnotherPacketSource(meta);
// video always at the beginning
mMimes.insertAt(String8(mime), 0);
}
}
- mSources.push(track);
+ mExtractors.push(trackExtractor);
int64_t durationUs;
if (meta->findInt64(kKeyDuration, &durationUs)) {
if (durationUs > mDurationUs) {
@@ -259,10 +274,10 @@
}
}
- ALOGV("initFromDataSource mSources.size(): %zu mIsSecure: %d mime[0]: %s", mSources.size(),
+ ALOGV("initFromDataSource mExtractors.size(): %zu mIsSecure: %d mime[0]: %s", mExtractors.size(),
mIsSecure, (mMimes.isEmpty() ? "NONE" : mMimes[0].string()));
- if (mSources.size() == 0) {
+ if (mExtractors.size() == 0) {
ALOGE("b/23705695");
return UNKNOWN_ERROR;
}
@@ -294,31 +309,10 @@
return OK;
}
-status_t NuPlayer2::GenericSource2::startSources() {
- // Start the selected A/V tracks now before we start buffering.
- // Widevine sources might re-initialize crypto when starting, if we delay
- // this to start(), all data buffered during prepare would be wasted.
- // (We don't actually start reading until start().)
- //
- // TODO: this logic may no longer be relevant after the removal of widevine
- // support
- if (mAudioTrack.mSource != NULL && mAudioTrack.mSource->start() != OK) {
- ALOGE("failed to start audio track!");
- return UNKNOWN_ERROR;
- }
-
- if (mVideoTrack.mSource != NULL && mVideoTrack.mSource->start() != OK) {
- ALOGE("failed to start video track!");
- return UNKNOWN_ERROR;
- }
-
- return OK;
-}
-
int64_t NuPlayer2::GenericSource2::getLastReadPosition() {
- if (mAudioTrack.mSource != NULL) {
+ if (mAudioTrack.mExtractor != NULL) {
return mAudioTimeUs;
- } else if (mVideoTrack.mSource != NULL) {
+ } else if (mVideoTrack.mExtractor != NULL) {
return mVideoTimeUs;
} else {
return 0;
@@ -391,51 +385,16 @@
if (!mDisconnected) {
mDataSource = dataSource;
}
- } else {
- if (property_get_bool("media.stagefright.extractremote", true) &&
- !FileSource::requiresDrm(mFd, mOffset, mLength, nullptr /* mime */)) {
- sp<IBinder> binder =
- defaultServiceManager()->getService(String16("media.extractor"));
- if (binder != nullptr) {
- ALOGD("FileSource remote");
- sp<IMediaExtractorService> mediaExService(
- interface_cast<IMediaExtractorService>(binder));
- sp<IDataSource> source =
- mediaExService->makeIDataSource(mFd, mOffset, mLength);
- ALOGV("IDataSource(FileSource): %p %d %lld %lld",
- source.get(), mFd, (long long)mOffset, (long long)mLength);
- if (source.get() != nullptr) {
- mDataSource = CreateDataSourceFromIDataSource(source);
- if (mDataSource != nullptr) {
- // Close the local file descriptor as it is not needed anymore.
- close(mFd);
- mFd = -1;
- }
- } else {
- ALOGW("extractor service cannot make data source");
- }
- } else {
- ALOGW("extractor service not running");
- }
- }
- if (mDataSource == nullptr) {
- ALOGD("FileSource local");
- mDataSource = new FileSource(mFd, mOffset, mLength);
- }
- // TODO: close should always be done on mFd, see the lines following
- // CreateDataSourceFromIDataSource above,
- // and the FileSource constructor should dup the mFd argument as needed.
- mFd = -1;
}
- if (mDataSource == NULL) {
+ if (mFd == -1 && mDataSource == NULL) {
ALOGE("Failed to create data source!");
notifyPreparedAndCleanup(UNKNOWN_ERROR);
return;
}
}
- if (mDataSource->flags() & DataSource::kIsCachingDataSource) {
+ if (mDataSource != nullptr && mDataSource->flags() & DataSource::kIsCachingDataSource) {
mCachedSource = static_cast<NuCachedSource2 *>(mDataSource.get());
}
@@ -452,7 +411,7 @@
return;
}
- if (mVideoTrack.mSource != NULL) {
+ if (mVideoTrack.mExtractor != NULL) {
sp<MetaData> meta = getFormatMeta_l(false /* audio */);
sp<AMessage> msg = new AMessage;
err = convertMetaDataToMessage(meta, &msg);
@@ -479,13 +438,6 @@
void NuPlayer2::GenericSource2::finishPrepareAsync() {
ALOGV("finishPrepareAsync");
- status_t err = startSources();
- if (err != OK) {
- ALOGE("Failed to init start data source!");
- notifyPreparedAndCleanup(err);
- return;
- }
-
if (mIsStreaming) {
mCachedSource->resumeFetchingIfNecessary();
mPreparing = true;
@@ -494,11 +446,11 @@
notifyPrepared();
}
- if (mAudioTrack.mSource != NULL) {
+ if (mAudioTrack.mExtractor != NULL) {
postReadBuffer(MEDIA_TRACK_TYPE_AUDIO);
}
- if (mVideoTrack.mSource != NULL) {
+ if (mVideoTrack.mExtractor != NULL) {
postReadBuffer(MEDIA_TRACK_TYPE_VIDEO);
}
}
@@ -520,11 +472,11 @@
Mutex::Autolock _l(mLock);
ALOGI("start");
- if (mAudioTrack.mSource != NULL) {
+ if (mAudioTrack.mExtractor != NULL) {
postReadBuffer(MEDIA_TRACK_TYPE_AUDIO);
}
- if (mVideoTrack.mSource != NULL) {
+ if (mVideoTrack.mExtractor != NULL) {
postReadBuffer(MEDIA_TRACK_TYPE_VIDEO);
}
@@ -563,6 +515,9 @@
} else if (httpSource != NULL) {
static_cast<HTTPBase *>(httpSource.get())->disconnect();
}
+
+ mDataSourceWrapper = NULL;
+
}
status_t NuPlayer2::GenericSource2::feedMoreTSData() {
@@ -630,30 +585,27 @@
{
int32_t trackIndex;
CHECK(msg->findInt32("trackIndex", &trackIndex));
- const sp<IMediaSource> source = mSources.itemAt(trackIndex);
+ const sp<AMediaExtractorWrapper> extractor = mExtractors.itemAt(trackIndex);
Track* track;
- const char *mime;
+ AString mime;
media_track_type trackType, counterpartType;
- sp<MetaData> meta = source->getFormat();
- meta->findCString(kKeyMIMEType, &mime);
- if (!strncasecmp(mime, "audio/", 6)) {
+ sp<AMediaFormatWrapper> format = extractor->getTrackFormat(trackIndex);
+ format->getString(AMEDIAFORMAT_KEY_MIME, &mime);
+ if (!strncasecmp(mime.c_str(), "audio/", 6)) {
track = &mAudioTrack;
trackType = MEDIA_TRACK_TYPE_AUDIO;
counterpartType = MEDIA_TRACK_TYPE_VIDEO;;
} else {
- CHECK(!strncasecmp(mime, "video/", 6));
+ CHECK(!strncasecmp(mime.c_str(), "video/", 6));
track = &mVideoTrack;
trackType = MEDIA_TRACK_TYPE_VIDEO;
counterpartType = MEDIA_TRACK_TYPE_AUDIO;;
}
- if (track->mSource != NULL) {
- track->mSource->stop();
- }
- track->mSource = source;
- track->mSource->start();
+ track->mExtractor = extractor;
+ track->mExtractor->selectSingleTrack(trackIndex);
track->mIndex = trackIndex;
++mAudioDataGeneration;
++mVideoDataGeneration;
@@ -786,11 +738,10 @@
return;
}
- uint32_t textType;
- const void *data;
+ void *data = NULL;
size_t size = 0;
- if (mTimedTextTrack.mSource->getFormat()->findData(
- kKeyTextFormatData, &textType, &data, &size)) {
+ if (mTimedTextTrack.mExtractor->getTrackFormat(mTimedTextTrack.mIndex)->getBuffer(
+ "text", &data, &size)) {
mGlobalTimedText = new ABuffer(size);
if (mGlobalTimedText->data()) {
memcpy(mGlobalTimedText->data(), data, size);
@@ -806,19 +757,36 @@
}
}
+sp<AMessage> NuPlayer2::GenericSource2::getFormat(bool audio) {
+ Mutex::Autolock _l(mLock);
+ return getFormat_l(audio);
+}
+
sp<MetaData> NuPlayer2::GenericSource2::getFormatMeta(bool audio) {
Mutex::Autolock _l(mLock);
return getFormatMeta_l(audio);
}
-sp<MetaData> NuPlayer2::GenericSource2::getFormatMeta_l(bool audio) {
- sp<IMediaSource> source = audio ? mAudioTrack.mSource : mVideoTrack.mSource;
+sp<AMessage> NuPlayer2::GenericSource2::getFormat_l(bool audio) {
+ sp<AMediaExtractorWrapper> extractor = audio ? mAudioTrack.mExtractor : mVideoTrack.mExtractor;
+ size_t trackIndex = audio ? mAudioTrack.mIndex : mVideoTrack.mIndex;
- if (source == NULL) {
+ if (extractor == NULL) {
return NULL;
}
- return source->getFormat();
+ return extractor->getTrackFormat(trackIndex)->toAMessage();
+}
+
+sp<MetaData> NuPlayer2::GenericSource2::getFormatMeta_l(bool audio) {
+ sp<AMediaExtractorWrapper> extractor = audio ? mAudioTrack.mExtractor : mVideoTrack.mExtractor;
+ size_t trackIndex = audio ? mAudioTrack.mIndex : mVideoTrack.mIndex;
+
+ if (extractor == NULL) {
+ return NULL;
+ }
+
+ return convertMediaFormatWrapperToMetaData(extractor->getTrackFormat(trackIndex));
}
status_t NuPlayer2::GenericSource2::dequeueAccessUnit(
@@ -833,7 +801,7 @@
Track *track = audio ? &mAudioTrack : &mVideoTrack;
- if (track->mSource == NULL) {
+ if (track->mExtractor == NULL) {
return -EWOULDBLOCK;
}
@@ -878,11 +846,11 @@
}
if (result != OK) {
- if (mSubtitleTrack.mSource != NULL) {
+ if (mSubtitleTrack.mExtractor != NULL) {
mSubtitleTrack.mPackets->clear();
mFetchSubtitleDataGeneration++;
}
- if (mTimedTextTrack.mSource != NULL) {
+ if (mTimedTextTrack.mExtractor != NULL) {
mTimedTextTrack.mPackets->clear();
mFetchTimedTextDataGeneration++;
}
@@ -898,7 +866,7 @@
mVideoLastDequeueTimeUs = timeUs;
}
- if (mSubtitleTrack.mSource != NULL
+ if (mSubtitleTrack.mExtractor != NULL
&& !mSubtitleTrack.mPackets->hasBufferAvailable(&eosResult)) {
sp<AMessage> msg = new AMessage(kWhatFetchSubtitleData, this);
msg->setInt64("timeUs", timeUs);
@@ -906,7 +874,7 @@
msg->post();
}
- if (mTimedTextTrack.mSource != NULL
+ if (mTimedTextTrack.mExtractor != NULL
&& !mTimedTextTrack.mPackets->hasBufferAvailable(&eosResult)) {
sp<AMessage> msg = new AMessage(kWhatFetchTimedTextData, this);
msg->setInt64("timeUs", timeUs);
@@ -925,50 +893,47 @@
size_t NuPlayer2::GenericSource2::getTrackCount() const {
Mutex::Autolock _l(mLock);
- return mSources.size();
+ return mExtractors.size();
}
sp<AMessage> NuPlayer2::GenericSource2::getTrackInfo(size_t trackIndex) const {
Mutex::Autolock _l(mLock);
- size_t trackCount = mSources.size();
+ size_t trackCount = mExtractors.size();
if (trackIndex >= trackCount) {
return NULL;
}
- sp<AMessage> format = new AMessage();
- sp<MetaData> meta = mSources.itemAt(trackIndex)->getFormat();
- if (meta == NULL) {
+ sp<AMessage> format = mExtractors.itemAt(trackIndex)->getTrackFormat(trackIndex)->toAMessage();
+ if (format == NULL) {
ALOGE("no metadata for track %zu", trackIndex);
return NULL;
}
- const char *mime;
- CHECK(meta->findCString(kKeyMIMEType, &mime));
- format->setString("mime", mime);
+ AString mime;
+ CHECK(format->findString(AMEDIAFORMAT_KEY_MIME, &mime));
int32_t trackType;
- if (!strncasecmp(mime, "video/", 6)) {
+ if (!strncasecmp(mime.c_str(), "video/", 6)) {
trackType = MEDIA_TRACK_TYPE_VIDEO;
- } else if (!strncasecmp(mime, "audio/", 6)) {
+ } else if (!strncasecmp(mime.c_str(), "audio/", 6)) {
trackType = MEDIA_TRACK_TYPE_AUDIO;
- } else if (!strcasecmp(mime, MEDIA_MIMETYPE_TEXT_3GPP)) {
+ } else if (!strcasecmp(mime.c_str(), MEDIA_MIMETYPE_TEXT_3GPP)) {
trackType = MEDIA_TRACK_TYPE_TIMEDTEXT;
} else {
trackType = MEDIA_TRACK_TYPE_UNKNOWN;
}
format->setInt32("type", trackType);
- const char *lang;
- if (!meta->findCString(kKeyMediaLanguage, &lang)) {
- lang = "und";
+ AString lang;
+ if (!format->findString("language", &lang)) {
+ format->setString("language", "und");
}
- format->setString("language", lang);
if (trackType == MEDIA_TRACK_TYPE_SUBTITLE) {
int32_t isAutoselect = 1, isDefault = 0, isForced = 0;
- meta->findInt32(kKeyTrackIsAutoselect, &isAutoselect);
- meta->findInt32(kKeyTrackIsDefault, &isDefault);
- meta->findInt32(kKeyTrackIsForced, &isForced);
+ format->findInt32(AMEDIAFORMAT_KEY_IS_AUTOSELECT, &isAutoselect);
+ format->findInt32(AMEDIAFORMAT_KEY_IS_DEFAULT, &isDefault);
+ format->findInt32(AMEDIAFORMAT_KEY_IS_FORCED_SUBTITLE, &isForced);
format->setInt32("auto", !!isAutoselect);
format->setInt32("default", !!isDefault);
@@ -998,7 +963,7 @@
break;
}
- if (track != NULL && track->mSource != NULL) {
+ if (track != NULL && track->mExtractor != NULL) {
return track->mIndex;
}
@@ -1009,49 +974,45 @@
Mutex::Autolock _l(mLock);
ALOGV("%s track: %zu", select ? "select" : "deselect", trackIndex);
- if (trackIndex >= mSources.size()) {
+ if (trackIndex >= mExtractors.size()) {
return BAD_INDEX;
}
if (!select) {
Track* track = NULL;
- if (mSubtitleTrack.mSource != NULL && trackIndex == mSubtitleTrack.mIndex) {
+ if (mSubtitleTrack.mExtractor != NULL && trackIndex == mSubtitleTrack.mIndex) {
track = &mSubtitleTrack;
mFetchSubtitleDataGeneration++;
- } else if (mTimedTextTrack.mSource != NULL && trackIndex == mTimedTextTrack.mIndex) {
+ } else if (mTimedTextTrack.mExtractor != NULL && trackIndex == mTimedTextTrack.mIndex) {
track = &mTimedTextTrack;
mFetchTimedTextDataGeneration++;
}
if (track == NULL) {
return INVALID_OPERATION;
}
- track->mSource->stop();
- track->mSource = NULL;
+ track->mExtractor = NULL;
track->mPackets->clear();
return OK;
}
- const sp<IMediaSource> source = mSources.itemAt(trackIndex);
- sp<MetaData> meta = source->getFormat();
+ const sp<AMediaExtractorWrapper> extractor = mExtractors.itemAt(trackIndex);
+ sp<MetaData> meta = convertMediaFormatWrapperToMetaData(extractor->getTrackFormat(trackIndex));
const char *mime;
CHECK(meta->findCString(kKeyMIMEType, &mime));
if (!strncasecmp(mime, "text/", 5)) {
bool isSubtitle = strcasecmp(mime, MEDIA_MIMETYPE_TEXT_3GPP);
Track *track = isSubtitle ? &mSubtitleTrack : &mTimedTextTrack;
- if (track->mSource != NULL && track->mIndex == trackIndex) {
+ if (track->mExtractor != NULL && track->mIndex == trackIndex) {
return OK;
}
track->mIndex = trackIndex;
- if (track->mSource != NULL) {
- track->mSource->stop();
- }
- track->mSource = mSources.itemAt(trackIndex);
- track->mSource->start();
+ track->mExtractor = mExtractors.itemAt(trackIndex);
+ track->mExtractor->selectSingleTrack(trackIndex);
if (track->mPackets == NULL) {
- track->mPackets = new AnotherPacketSource(track->mSource->getFormat());
+ track->mPackets = new AnotherPacketSource(meta);
} else {
track->mPackets->clear();
- track->mPackets->setFormat(track->mSource->getFormat());
+ track->mPackets->setFormat(meta);
}
@@ -1062,7 +1023,7 @@
}
status_t eosResult; // ignored
- if (mSubtitleTrack.mSource != NULL
+ if (mSubtitleTrack.mExtractor != NULL
&& !mSubtitleTrack.mPackets->hasBufferAvailable(&eosResult)) {
sp<AMessage> msg = new AMessage(kWhatFetchSubtitleData, this);
msg->setInt64("timeUs", timeUs);
@@ -1074,7 +1035,7 @@
msg2->setInt32("generation", mFetchTimedTextDataGeneration);
msg2->post();
- if (mTimedTextTrack.mSource != NULL
+ if (mTimedTextTrack.mExtractor != NULL
&& !mTimedTextTrack.mPackets->hasBufferAvailable(&eosResult)) {
sp<AMessage> msg = new AMessage(kWhatFetchTimedTextData, this);
msg->setInt64("timeUs", timeUs);
@@ -1086,7 +1047,7 @@
} else if (!strncasecmp(mime, "audio/", 6) || !strncasecmp(mime, "video/", 6)) {
bool audio = !strncasecmp(mime, "audio/", 6);
Track *track = audio ? &mAudioTrack : &mVideoTrack;
- if (track->mSource != NULL && track->mIndex == trackIndex) {
+ if (track->mExtractor != NULL && track->mIndex == trackIndex) {
return OK;
}
@@ -1133,7 +1094,7 @@
}
status_t NuPlayer2::GenericSource2::doSeek(int64_t seekTimeUs, MediaPlayer2SeekMode mode) {
- if (mVideoTrack.mSource != NULL) {
+ if (mVideoTrack.mExtractor != NULL) {
++mVideoDataGeneration;
int64_t actualTimeUs;
@@ -1145,18 +1106,18 @@
mVideoLastDequeueTimeUs = actualTimeUs;
}
- if (mAudioTrack.mSource != NULL) {
+ if (mAudioTrack.mExtractor != NULL) {
++mAudioDataGeneration;
readBuffer(MEDIA_TRACK_TYPE_AUDIO, seekTimeUs, MediaPlayer2SeekMode::SEEK_CLOSEST);
mAudioLastDequeueTimeUs = seekTimeUs;
}
- if (mSubtitleTrack.mSource != NULL) {
+ if (mSubtitleTrack.mExtractor != NULL) {
mSubtitleTrack.mPackets->clear();
mFetchSubtitleDataGeneration++;
}
- if (mTimedTextTrack.mSource != NULL) {
+ if (mTimedTextTrack.mExtractor != NULL) {
mTimedTextTrack.mPackets->clear();
mFetchTimedTextDataGeneration++;
}
@@ -1227,10 +1188,12 @@
}
if (trackType == MEDIA_TRACK_TYPE_TIMEDTEXT) {
- const char *mime;
- CHECK(mTimedTextTrack.mSource != NULL
- && mTimedTextTrack.mSource->getFormat()->findCString(kKeyMIMEType, &mime));
- meta->setString("mime", mime);
+ AString mime;
+ sp<AMediaExtractorWrapper> extractor = mTimedTextTrack.mExtractor;
+ size_t trackIndex = mTimedTextTrack.mIndex;
+ CHECK(extractor != NULL
+ && extractor->getTrackFormat(trackIndex)->getString(AMEDIAFORMAT_KEY_MIME, &mime));
+ meta->setString("mime", mime.c_str());
}
int64_t durationUs;
@@ -1239,7 +1202,7 @@
}
if (trackType == MEDIA_TRACK_TYPE_SUBTITLE) {
- meta->setInt32("trackIndex", mSubtitleTrack.mIndex);
+ meta->setInt32(AMEDIAFORMAT_KEY_TRACK_INDEX, mSubtitleTrack.mIndex);
}
uint32_t dataType; // unused
@@ -1255,7 +1218,7 @@
if (mb->meta_data().findData(
kKeyMpegUserData, &dataType, &mpegUserDataPointer, &mpegUserDataLength)) {
sp<ABuffer> mpegUserData = ABuffer::CreateAsCopy(mpegUserDataPointer, mpegUserDataLength);
- meta->setBuffer("mpegUserData", mpegUserData);
+ meta->setBuffer(AMEDIAFORMAT_KEY_MPEG_USER_DATA, mpegUserData);
}
mb->release();
@@ -1327,7 +1290,7 @@
TRESPASS();
}
- if (track->mSource == NULL) {
+ if (track->mExtractor == NULL) {
return;
}
@@ -1335,109 +1298,77 @@
*actualTimeUs = seekTimeUs;
}
- MediaSource::ReadOptions options;
bool seeking = false;
+ sp<AMediaExtractorWrapper> extractor = track->mExtractor;
if (seekTimeUs >= 0) {
- options.setSeekTo(seekTimeUs, mode);
+ extractor->seekTo(seekTimeUs, mode);
seeking = true;
}
- const bool couldReadMultiple = (track->mSource->supportReadMultiple());
-
- if (couldReadMultiple) {
- options.setNonBlocking();
- }
-
int32_t generation = getDataGeneration(trackType);
for (size_t numBuffers = 0; numBuffers < maxBuffers; ) {
- Vector<MediaBufferBase *> mediaBuffers;
- status_t err = NO_ERROR;
+ Vector<sp<ABuffer> > aBuffers;
- sp<IMediaSource> source = track->mSource;
mLock.unlock();
- if (couldReadMultiple) {
- err = source->readMultiple(
- &mediaBuffers, maxBuffers - numBuffers, &options);
- } else {
- MediaBufferBase *mbuf = NULL;
- err = source->read(&mbuf, &options);
- if (err == OK && mbuf != NULL) {
- mediaBuffers.push_back(mbuf);
- }
+
+ sp<AMediaFormatWrapper> format;
+ ssize_t sampleSize = -1;
+ status_t err = extractor->getSampleFormat(format);
+ if (err == OK) {
+ sampleSize = extractor->getSampleSize();
}
+
+ if (err != OK || sampleSize < 0) {
+ mLock.lock();
+ track->mPackets->signalEOS(err != OK ? err : ERROR_END_OF_STREAM);
+ break;
+ }
+
+ sp<ABuffer> abuf = new ABuffer(sampleSize);
+ sampleSize = extractor->readSampleData(abuf);
mLock.lock();
- options.clearNonPersistent();
-
- size_t id = 0;
- size_t count = mediaBuffers.size();
-
// in case track has been changed since we don't have lock for some time.
if (generation != getDataGeneration(trackType)) {
- for (; id < count; ++id) {
- mediaBuffers[id]->release();
- }
break;
}
- for (; id < count; ++id) {
- int64_t timeUs;
- MediaBufferBase *mbuf = mediaBuffers[id];
- if (!mbuf->meta_data().findInt64(kKeyTime, &timeUs)) {
- mbuf->meta_data().dumpToLog();
- track->mPackets->signalEOS(ERROR_MALFORMED);
- break;
- }
- if (trackType == MEDIA_TRACK_TYPE_AUDIO) {
- mAudioTimeUs = timeUs;
- } else if (trackType == MEDIA_TRACK_TYPE_VIDEO) {
- mVideoTimeUs = timeUs;
- }
-
- queueDiscontinuityIfNeeded(seeking, formatChange, trackType, track);
-
- sp<ABuffer> buffer = mediaBufferToABuffer(mbuf, trackType);
- if (numBuffers == 0 && actualTimeUs != nullptr) {
- *actualTimeUs = timeUs;
- }
- if (seeking && buffer != nullptr) {
- sp<AMessage> meta = buffer->meta();
- if (meta != nullptr && mode == MediaPlayer2SeekMode::SEEK_CLOSEST
- && seekTimeUs > timeUs) {
- sp<AMessage> extra = new AMessage;
- extra->setInt64("resume-at-mediaTimeUs", seekTimeUs);
- meta->setMessage("extra", extra);
- }
- }
-
- track->mPackets->queueAccessUnit(buffer);
- formatChange = false;
- seeking = false;
- ++numBuffers;
- }
- if (id < count) {
- // Error, some mediaBuffer doesn't have kKeyTime.
- for (; id < count; ++id) {
- mediaBuffers[id]->release();
- }
+ int64_t timeUs = extractor->getSampleTime();
+ if (timeUs < 0) {
+ track->mPackets->signalEOS(ERROR_MALFORMED);
break;
}
- if (err == WOULD_BLOCK) {
- break;
- } else if (err == INFO_FORMAT_CHANGED) {
-#if 0
- track->mPackets->queueDiscontinuity(
- ATSParser::DISCONTINUITY_FORMATCHANGE,
- NULL,
- false /* discard */);
-#endif
- } else if (err != OK) {
- queueDiscontinuityIfNeeded(seeking, formatChange, trackType, track);
- track->mPackets->signalEOS(err);
- break;
+ sp<AMessage> meta = abuf->meta();
+ format->writeToAMessage(meta);
+ meta->setInt64("timeUs", timeUs);
+ if (trackType == MEDIA_TRACK_TYPE_AUDIO) {
+ mAudioTimeUs = timeUs;
+ } else if (trackType == MEDIA_TRACK_TYPE_VIDEO) {
+ mVideoTimeUs = timeUs;
}
+
+ queueDiscontinuityIfNeeded(seeking, formatChange, trackType, track);
+
+ if (numBuffers == 0 && actualTimeUs != nullptr) {
+ *actualTimeUs = timeUs;
+ }
+ if (seeking) {
+ if (meta != nullptr && mode == MediaPlayer2SeekMode::SEEK_CLOSEST
+ && seekTimeUs > timeUs) {
+ sp<AMessage> extra = new AMessage;
+ extra->setInt64("resume-at-mediaTimeUs", seekTimeUs);
+ meta->setMessage("extra", extra);
+ }
+ }
+
+ track->mPackets->queueAccessUnit(abuf);
+ formatChange = false;
+ seeking = false;
+ ++numBuffers;
+ extractor->advance();
+
}
if (mIsStreaming
@@ -1453,7 +1384,7 @@
if (mPreparing || mSentPauseOnBuffering) {
Track *counterTrack =
(trackType == MEDIA_TRACK_TYPE_VIDEO ? &mAudioTrack : &mVideoTrack);
- if (counterTrack->mSource != NULL) {
+ if (counterTrack->mExtractor != NULL) {
durationUs = counterTrack->mPackets->getBufferedDurationUs(&finalResult);
}
if (finalResult == ERROR_END_OF_STREAM || durationUs >= markUs) {
@@ -1649,26 +1580,22 @@
// same source without being reset (called by prepareAsync/initFromDataSource)
mIsDrmReleased = false;
- if (mFileMeta == NULL) {
- ALOGI("checkDrmInfo: No metadata");
+ if (mExtractor == NULL) {
+ ALOGV("checkDrmInfo: No extractor");
return OK; // letting the caller responds accordingly
}
- uint32_t type;
- const void *pssh;
- size_t psshsize;
-
- if (!mFileMeta->findData(kKeyPssh, &type, &pssh, &psshsize)) {
+ PsshInfo *psshInfo = mExtractor->getPsshInfo();
+ if (psshInfo == NULL) {
ALOGV("checkDrmInfo: No PSSH");
return OK; // source without DRM info
}
- sp<ABuffer> drmInfoBuffer = NuPlayer2Drm::retrieveDrmInfo(pssh, psshsize);
- ALOGV("checkDrmInfo: MEDIA2_DRM_INFO PSSH size: %d drmInfoBuffer size: %d",
- (int)psshsize, (int)drmInfoBuffer->size());
+ sp<ABuffer> drmInfoBuffer = NuPlayer2Drm::retrieveDrmInfo(psshInfo);
+ ALOGV("checkDrmInfo: MEDIA_DRM_INFO PSSH drm info size: %d", (int)drmInfoBuffer->size());
if (drmInfoBuffer->size() == 0) {
- ALOGE("checkDrmInfo: Unexpected drmInfoBuffer size: 0");
+ ALOGE("checkDrmInfo: Unexpected parcel size: 0");
return UNKNOWN_ERROR;
}
diff --git a/media/libmediaplayer2/nuplayer2/GenericSource2.h b/media/libmediaplayer2/nuplayer2/GenericSource2.h
index 896c397..9bc5182 100644
--- a/media/libmediaplayer2/nuplayer2/GenericSource2.h
+++ b/media/libmediaplayer2/nuplayer2/GenericSource2.h
@@ -25,6 +25,9 @@
#include <media/stagefright/MediaBuffer.h>
#include <mediaplayer2/mediaplayer2.h>
+#include <media/NdkMediaDataSource.h>
+#include <media/NdkMediaExtractor.h>
+#include <media/NdkWrapper.h>
namespace android {
@@ -101,6 +104,7 @@
virtual void onMessageReceived(const sp<AMessage> &msg);
+ virtual sp<AMessage> getFormat(bool audio);
virtual sp<MetaData> getFormatMeta(bool audio);
private:
@@ -122,19 +126,14 @@
struct Track {
size_t mIndex;
- sp<IMediaSource> mSource;
+ sp<AMediaExtractorWrapper> mExtractor;
sp<AnotherPacketSource> mPackets;
};
- Vector<sp<IMediaSource> > mSources;
- Track mAudioTrack;
int64_t mAudioTimeUs;
int64_t mAudioLastDequeueTimeUs;
- Track mVideoTrack;
int64_t mVideoTimeUs;
int64_t mVideoLastDequeueTimeUs;
- Track mSubtitleTrack;
- Track mTimedTextTrack;
BufferingSettings mBufferingSettings;
int32_t mPrevBufferPercentage;
@@ -164,12 +163,20 @@
sp<NuCachedSource2> mCachedSource;
sp<DataSource> mHttpSource;
sp<MetaData> mFileMeta;
+ sp<AMediaDataSourceWrapper> mDataSourceWrapper;
+ sp<AMediaExtractorWrapper> mExtractor;
+ Vector<sp<AMediaExtractorWrapper> > mExtractors;
bool mStarted;
bool mPreparing;
int64_t mBitrate;
uint32_t mPendingReadBufferTypes;
sp<ABuffer> mGlobalTimedText;
+ Track mVideoTrack;
+ Track mAudioTrack;
+ Track mSubtitleTrack;
+ Track mTimedTextTrack;
+
mutable Mutex mLock;
sp<ALooper> mLooper;
@@ -227,6 +234,7 @@
void sendCacheStats();
+ sp<AMessage> getFormat_l(bool audio);
sp<MetaData> getFormatMeta_l(bool audio);
int32_t getDataGeneration(media_track_type type) const;
diff --git a/media/libmediaplayer2/nuplayer2/NuPlayer2.cpp b/media/libmediaplayer2/nuplayer2/NuPlayer2.cpp
index 5971a8b..060b698 100644
--- a/media/libmediaplayer2/nuplayer2/NuPlayer2.cpp
+++ b/media/libmediaplayer2/nuplayer2/NuPlayer2.cpp
@@ -2842,7 +2842,7 @@
void NuPlayer2::sendSubtitleData(const sp<ABuffer> &buffer, int32_t baseIndex) {
int32_t trackIndex;
int64_t timeUs, durationUs;
- CHECK(buffer->meta()->findInt32("trackIndex", &trackIndex));
+ CHECK(buffer->meta()->findInt32(AMEDIAFORMAT_KEY_TRACK_INDEX, &trackIndex));
CHECK(buffer->meta()->findInt64("timeUs", &timeUs));
CHECK(buffer->meta()->findInt64("durationUs", &durationUs));
diff --git a/media/libmediaplayer2/nuplayer2/NuPlayer2CCDecoder.cpp b/media/libmediaplayer2/nuplayer2/NuPlayer2CCDecoder.cpp
index e4afd5b..e48e388 100644
--- a/media/libmediaplayer2/nuplayer2/NuPlayer2CCDecoder.cpp
+++ b/media/libmediaplayer2/nuplayer2/NuPlayer2CCDecoder.cpp
@@ -21,6 +21,7 @@
#include "NuPlayer2CCDecoder.h"
+#include <media/NdkMediaFormat.h>
#include <media/stagefright/foundation/ABitReader.h>
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/ADebug.h>
@@ -301,7 +302,7 @@
// returns true if a new CC track is found
bool NuPlayer2::CCDecoder::extractFromMPEGUserData(const sp<ABuffer> &accessUnit) {
sp<ABuffer> mpegUserData;
- if (!accessUnit->meta()->findBuffer("mpegUserData", &mpegUserData)
+ if (!accessUnit->meta()->findBuffer(AMEDIAFORMAT_KEY_MPEG_USER_DATA, &mpegUserData)
|| mpegUserData == NULL) {
return false;
}
@@ -538,7 +539,7 @@
dumpBytePair(ccBuf);
#endif
- ccBuf->meta()->setInt32("trackIndex", mSelectedTrack);
+ ccBuf->meta()->setInt32(AMEDIAFORMAT_KEY_TRACK_INDEX, mSelectedTrack);
ccBuf->meta()->setInt64("timeUs", timeUs);
ccBuf->meta()->setInt64("durationUs", 0ll);
diff --git a/media/libmediaplayerservice/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp
index 5cbf976..30c0b1c 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.cpp
+++ b/media/libmediaplayerservice/StagefrightRecorder.cpp
@@ -89,6 +89,13 @@
static const char *kRecorderVideoTimescale = "android.media.mediarecorder.video-timescale";
static const char *kRecorderWidth = "android.media.mediarecorder.width";
+// new fields, not yet frozen in the public Java API definitions
+static const char *kRecorderAudioMime = "android.media.mediarecorder.audio.mime";
+static const char *kRecorderVideoMime = "android.media.mediarecorder.video.mime";
+static const char *kRecorderDurationMs = "android.media.mediarecorder.durationMs";
+static const char *kRecorderPaused = "android.media.mediarecorder.pausedMs";
+static const char *kRecorderNumPauses = "android.media.mediarecorder.NPauses";
+
// To collect the encoder usage for the battery app
static void addBatteryData(uint32_t params) {
@@ -105,7 +112,7 @@
: MediaRecorderBase(opPackageName),
mWriter(NULL),
mOutputFd(-1),
- mAudioSource(AUDIO_SOURCE_CNT),
+ mAudioSource((audio_source_t)AUDIO_SOURCE_CNT), // initialize with invalid value
mVideoSource(VIDEO_SOURCE_LIST_END),
mStarted(false),
mSelectedDeviceId(AUDIO_PORT_HANDLE_NONE),
@@ -126,21 +133,18 @@
}
// log the current record, provided it has some information worth recording
- if (mAnalyticsDirty && mAnalyticsItem != NULL) {
- updateMetrics();
- if (mAnalyticsItem->count() > 0) {
- mAnalyticsItem->selfrecord();
- }
- delete mAnalyticsItem;
- mAnalyticsItem = NULL;
- }
+ // NB: this also reclaims & clears mAnalyticsItem.
+ flushAndResetMetrics(false);
}
void StagefrightRecorder::updateMetrics() {
ALOGV("updateMetrics");
- // we'll populate the values from the raw fields.
- // (NOT going to populate as we go through the various set* ops)
+ // we run as part of the media player service; what we really want to
+ // know is the app which requested the recording.
+ mAnalyticsItem->setUid(mClientUid);
+
+ // populate the values from the raw fields.
// TBD mOutputFormat = OUTPUT_FORMAT_THREE_GPP;
// TBD mAudioEncoder = AUDIO_ENCODER_AMR_NB;
@@ -168,7 +172,6 @@
// TBD mTrackEveryTimeDurationUs = 0;
mAnalyticsItem->setInt32(kRecorderCaptureFpsEnable, mCaptureFpsEnable);
mAnalyticsItem->setDouble(kRecorderCaptureFps, mCaptureFps);
- // TBD mCaptureFps = -1.0;
// TBD mCameraSourceTimeLapse = NULL;
// TBD mMetaDataStoredInVideoBuffers = kMetadataBufferTypeInvalid;
// TBD mEncoderProfiles = MediaProfiles::getInstance();
@@ -177,14 +180,17 @@
// PII mLongitudex10000 = -3600000;
// TBD mTotalBitRate = 0;
- // TBD: some duration information (capture, paused)
- //
-
+ // duration information (recorded, paused, # of pauses)
+ mAnalyticsItem->setInt64(kRecorderDurationMs, (mDurationRecordedUs+500)/1000 );
+ if (mNPauses != 0) {
+ mAnalyticsItem->setInt64(kRecorderPaused, (mDurationPausedUs+500)/1000 );
+ mAnalyticsItem->setInt32(kRecorderNumPauses, mNPauses);
+ }
}
-void StagefrightRecorder::resetMetrics() {
- ALOGV("resetMetrics");
- // flush anything we have, restart the record
+void StagefrightRecorder::flushAndResetMetrics(bool reinitialize) {
+ ALOGV("flushAndResetMetrics");
+ // flush anything we have, maybe setup a new record
if (mAnalyticsDirty && mAnalyticsItem != NULL) {
updateMetrics();
if (mAnalyticsItem->count() > 0) {
@@ -193,8 +199,10 @@
delete mAnalyticsItem;
mAnalyticsItem = NULL;
}
- mAnalyticsItem = new MediaAnalyticsItem(kKeyRecorder);
mAnalyticsDirty = false;
+ if (reinitialize) {
+ mAnalyticsItem = new MediaAnalyticsItem(kKeyRecorder);
+ }
}
status_t StagefrightRecorder::init() {
@@ -1030,6 +1038,8 @@
mAnalyticsDirty = true;
mStarted = true;
+ mStartedRecordingUs = systemTime() / 1000;
+
uint32_t params = IMediaPlayerService::kBatteryDataCodecStarted;
if (mAudioSource != AUDIO_SOURCE_CNT) {
params |= IMediaPlayerService::kBatteryDataTrackAudio;
@@ -1109,6 +1119,14 @@
return NULL;
}
+ // log audio mime type for media metrics
+ if (mAnalyticsItem != NULL) {
+ AString audiomime;
+ if (format->findString("mime", &audiomime)) {
+ mAnalyticsItem->setCString(kRecorderAudioMime, audiomime.c_str());
+ }
+ }
+
int32_t maxInputSize;
CHECK(audioSource->getFormat()->findInt32(
kKeyMaxInputSize, &maxInputSize));
@@ -1655,6 +1673,14 @@
break;
}
+ // log video mime type for media metrics
+ if (mAnalyticsItem != NULL) {
+ AString videomime;
+ if (format->findString("mime", &videomime)) {
+ mAnalyticsItem->setCString(kRecorderVideoMime, videomime.c_str());
+ }
+ }
+
if (cameraSource != NULL) {
sp<MetaData> meta = cameraSource->getFormat();
@@ -1917,6 +1943,13 @@
sp<MetaData> meta = new MetaData;
meta->setInt64(kKeyTime, mPauseStartTimeUs);
+ if (mStartedRecordingUs != 0) {
+ // should always be true
+ int64_t recordingUs = mPauseStartTimeUs - mStartedRecordingUs;
+ mDurationRecordedUs += recordingUs;
+ mStartedRecordingUs = 0;
+ }
+
if (mAudioEncoderSource != NULL) {
mAudioEncoderSource->pause();
}
@@ -1975,6 +2008,16 @@
source->setInputBufferTimeOffset((int64_t)timeOffset);
source->start(meta.get());
}
+
+
+ // sum info on pause duration
+ // (ignore the 30msec of overlap adjustment factored into mTotalPausedDurationUs)
+ int64_t pausedUs = resumeStartTimeUs - mPauseStartTimeUs;
+ mDurationPausedUs += pausedUs;
+ mNPauses++;
+ // and a timestamp marking that we're back to recording....
+ mStartedRecordingUs = resumeStartTimeUs;
+
mPauseStartTimeUs = 0;
return OK;
@@ -2003,10 +2046,28 @@
mWriter.clear();
}
- resetMetrics();
+ // account for the last 'segment' -- whether paused or recording
+ if (mPauseStartTimeUs != 0) {
+ // we were paused
+ int64_t additive = stopTimeUs - mPauseStartTimeUs;
+ mDurationPausedUs += additive;
+ mNPauses++;
+ } else if (mStartedRecordingUs != 0) {
+ // we were recording
+ int64_t additive = stopTimeUs - mStartedRecordingUs;
+ mDurationRecordedUs += additive;
+ } else {
+ ALOGW("stop while neither recording nor paused");
+ }
+ flushAndResetMetrics(true);
+
+ mDurationRecordedUs = 0;
+ mDurationPausedUs = 0;
+ mNPauses = 0;
mTotalPausedDurationUs = 0;
mPauseStartTimeUs = 0;
+ mStartedRecordingUs = 0;
mGraphicBufferProducer.clear();
mPersistentSurface.clear();
@@ -2047,7 +2108,7 @@
stop();
// No audio or video source by default
- mAudioSource = AUDIO_SOURCE_CNT;
+ mAudioSource = (audio_source_t)AUDIO_SOURCE_CNT; // reset to invalid value
mVideoSource = VIDEO_SOURCE_LIST_END;
// Default parameters
@@ -2085,6 +2146,12 @@
mLongitudex10000 = -3600000;
mTotalBitRate = 0;
+ // tracking how long we recorded.
+ mDurationRecordedUs = 0;
+ mStartedRecordingUs = 0;
+ mDurationPausedUs = 0;
+ mNPauses = 0;
+
mOutputFd = -1;
return OK;
diff --git a/media/libmediaplayerservice/StagefrightRecorder.h b/media/libmediaplayerservice/StagefrightRecorder.h
index 18c9256..faa2e59 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.h
+++ b/media/libmediaplayerservice/StagefrightRecorder.h
@@ -95,7 +95,7 @@
MediaAnalyticsItem *mAnalyticsItem;
bool mAnalyticsDirty;
- void resetMetrics();
+ void flushAndResetMetrics(bool reinitialize);
void updateMetrics();
audio_source_t mAudioSource;
@@ -127,6 +127,11 @@
int32_t mStartTimeOffsetMs;
int32_t mTotalBitRate;
+ int64_t mDurationRecordedUs;
+ int64_t mStartedRecordingUs;
+ int64_t mDurationPausedUs;
+ int32_t mNPauses;
+
bool mCaptureFpsEnable;
double mCaptureFps;
int64_t mTimeBetweenCaptureUs;
diff --git a/media/libmediaplayerservice/nuplayer/GenericSource.cpp b/media/libmediaplayerservice/nuplayer/GenericSource.cpp
index e7c3deb..cbc3015 100644
--- a/media/libmediaplayerservice/nuplayer/GenericSource.cpp
+++ b/media/libmediaplayerservice/nuplayer/GenericSource.cpp
@@ -1233,7 +1233,7 @@
}
if (trackType == MEDIA_TRACK_TYPE_SUBTITLE) {
- meta->setInt32("trackIndex", mSubtitleTrack.mIndex);
+ meta->setInt32("track-index", mSubtitleTrack.mIndex);
}
uint32_t dataType; // unused
@@ -1249,7 +1249,7 @@
if (mb->meta_data().findData(
kKeyMpegUserData, &dataType, &mpegUserDataPointer, &mpegUserDataLength)) {
sp<ABuffer> mpegUserData = ABuffer::CreateAsCopy(mpegUserDataPointer, mpegUserDataLength);
- meta->setBuffer("mpegUserData", mpegUserData);
+ meta->setBuffer("mpeg-user-data", mpegUserData);
}
mb->release();
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
index dce3e0a..bd83c6d 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
@@ -2702,7 +2702,7 @@
void NuPlayer::sendSubtitleData(const sp<ABuffer> &buffer, int32_t baseIndex) {
int32_t trackIndex;
int64_t timeUs, durationUs;
- CHECK(buffer->meta()->findInt32("trackIndex", &trackIndex));
+ CHECK(buffer->meta()->findInt32("track-index", &trackIndex));
CHECK(buffer->meta()->findInt64("timeUs", &timeUs));
CHECK(buffer->meta()->findInt64("durationUs", &durationUs));
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerCCDecoder.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerCCDecoder.cpp
index 0a8b97f..0402fca 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerCCDecoder.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerCCDecoder.cpp
@@ -301,7 +301,7 @@
// returns true if a new CC track is found
bool NuPlayer::CCDecoder::extractFromMPEGUserData(const sp<ABuffer> &accessUnit) {
sp<ABuffer> mpegUserData;
- if (!accessUnit->meta()->findBuffer("mpegUserData", &mpegUserData)
+ if (!accessUnit->meta()->findBuffer("mpeg-user-data", &mpegUserData)
|| mpegUserData == NULL) {
return false;
}
@@ -538,7 +538,7 @@
dumpBytePair(ccBuf);
#endif
- ccBuf->meta()->setInt32("trackIndex", mSelectedTrack);
+ ccBuf->meta()->setInt32("track-index", mSelectedTrack);
ccBuf->meta()->setInt64("timeUs", timeUs);
ccBuf->meta()->setInt64("durationUs", 0ll);
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 65348e5..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,13 +216,14 @@
"libui",
"libutils",
"libmedia_helper",
+ "libstagefright_ccodec",
"libstagefright_codec2",
"libstagefright_codec2_vndk",
+ "libstagefright_codecbase",
"libstagefright_foundation",
"libstagefright_omx",
"libstagefright_omx_utils",
"libstagefright_xmlparser",
- "libdl",
"libRScpp",
"libhidlallocatorutils",
"libhidlbase",
@@ -150,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 d2eee33..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,147 +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->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->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);
-}
-
} // namespace android
diff --git a/media/libstagefright/CCodec.cpp b/media/libstagefright/CCodec.cpp
index f62ee41..0a20d34 100644
--- a/media/libstagefright/CCodec.cpp
+++ b/media/libstagefright/CCodec.cpp
@@ -238,30 +238,47 @@
return mChannel;
}
+status_t CCodec::tryAndReportOnError(std::function<status_t()> job) {
+ status_t err = job();
+ if (err != C2_OK) {
+ mCallback->onError(err, ACTION_CODE_FATAL);
+ }
+ return err;
+}
+
void CCodec::initiateAllocateComponent(const sp<AMessage> &msg) {
- {
+ auto setAllocating = [this] {
Mutexed<State>::Locked state(mState);
if (state->get() != RELEASED) {
- mCallback->onError(INVALID_OPERATION, ACTION_CODE_FATAL);
- return;
+ return INVALID_OPERATION;
}
state->set(ALLOCATING);
+ return OK;
+ };
+ if (tryAndReportOnError(setAllocating) != OK) {
+ 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) {
- // TODO: use C2ComponentStore to create component
+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);
@@ -282,29 +299,30 @@
}
ALOGV("Success Create component: %s", componentName.c_str());
comp->setListener_vb(mListener, C2_MAY_BLOCK);
- {
+ mChannel->setComponent(comp);
+ auto setAllocated = [this, comp] {
Mutexed<State>::Locked state(mState);
if (state->get() != ALLOCATING) {
state->set(RELEASED);
- state.unlock();
- mCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
- state.lock();
- return;
+ return UNKNOWN_ERROR;
}
state->set(ALLOCATED);
state->comp = comp;
+ return OK;
+ };
+ if (tryAndReportOnError(setAllocated) != OK) {
+ return;
}
- mChannel->setComponent(comp);
mCallback->onComponentAllocated(comp->intf()->getName().c_str());
}
void CCodec::initiateConfigureComponent(const sp<AMessage> &format) {
- {
+ auto checkAllocated = [this] {
Mutexed<State>::Locked state(mState);
- if (state->get() != ALLOCATED) {
- mCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
- return;
- }
+ return (state->get() != ALLOCATED) ? UNKNOWN_ERROR : OK;
+ };
+ if (tryAndReportOnError(checkAllocated) != OK) {
+ return;
}
sp<AMessage> msg(new AMessage(kWhatConfigure, this));
@@ -314,21 +332,22 @@
void CCodec::configure(const sp<AMessage> &msg) {
std::shared_ptr<C2ComponentInterface> intf;
- {
+ auto checkAllocated = [this, &intf] {
Mutexed<State>::Locked state(mState);
if (state->get() != ALLOCATED) {
state->set(RELEASED);
- state.unlock();
- mCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
- state.lock();
- return;
+ return UNKNOWN_ERROR;
}
intf = state->comp->intf();
+ return OK;
+ };
+ if (tryAndReportOnError(checkAllocated) != OK) {
+ return;
}
sp<AMessage> inputFormat(new AMessage);
sp<AMessage> outputFormat(new AMessage);
- if (status_t err = [=] {
+ auto doConfig = [=] {
AString mime;
if (!msg->findString("mime", &mime)) {
return BAD_VALUE;
@@ -339,6 +358,11 @@
encoder = false;
}
+ // TODO: read from intf()
+ if ((!encoder) != (intf->getName().find("encoder") == std::string::npos)) {
+ return UNKNOWN_ERROR;
+ }
+
sp<RefBase> obj;
if (msg->findObject("native-window", &obj)) {
sp<Surface> surface = static_cast<Surface *>(obj.get());
@@ -386,14 +410,22 @@
if (audio) {
outputFormat->setInt32("channel-count", 2);
outputFormat->setInt32("sample-rate", 44100);
+ } else {
+ int32_t tmp;
+ if (msg->findInt32("width", &tmp)) {
+ outputFormat->setInt32("width", tmp);
+ }
+ if (msg->findInt32("height", &tmp)) {
+ outputFormat->setInt32("height", tmp);
+ }
}
}
// TODO
return OK;
- }() != OK) {
- mCallback->onError(err, ACTION_CODE_FATAL);
+ };
+ if (tryAndReportOnError(doConfig) != OK) {
return;
}
@@ -406,6 +438,22 @@
}
void CCodec::initiateCreateInputSurface() {
+ status_t err = [this] {
+ Mutexed<State>::Locked state(mState);
+ if (state->get() != ALLOCATED) {
+ return UNKNOWN_ERROR;
+ }
+ // TODO: read it from intf() properly.
+ if (state->comp->intf()->getName().find("encoder") == std::string::npos) {
+ return INVALID_OPERATION;
+ }
+ return OK;
+ }();
+ if (err != OK) {
+ mCallback->onInputSurfaceCreationFailed(err);
+ return;
+ }
+
(new AMessage(kWhatCreateInputSurface, this))->post();
}
@@ -477,15 +525,16 @@
}
void CCodec::initiateStart() {
- {
+ auto setStarting = [this] {
Mutexed<State>::Locked state(mState);
if (state->get() != ALLOCATED) {
- state.unlock();
- mCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
- state.lock();
- return;
+ return UNKNOWN_ERROR;
}
state->set(STARTING);
+ return OK;
+ };
+ if (tryAndReportOnError(setStarting) != OK) {
+ return;
}
(new AMessage(kWhatStart, this))->post();
@@ -493,16 +542,18 @@
void CCodec::start() {
std::shared_ptr<C2Component> comp;
- {
+ auto checkStarting = [this, &comp] {
Mutexed<State>::Locked state(mState);
if (state->get() != STARTING) {
- state.unlock();
- mCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
- state.lock();
- return;
+ return UNKNOWN_ERROR;
}
comp = state->comp;
+ return OK;
+ };
+ if (tryAndReportOnError(checkStarting) != OK) {
+ return;
}
+
c2_status_t err = comp->start();
if (err != C2_OK) {
// TODO: convert err into status_t
@@ -516,17 +567,22 @@
inputFormat = formats->inputFormat;
outputFormat = formats->outputFormat;
}
- mChannel->start(inputFormat, outputFormat);
+ status_t err2 = mChannel->start(inputFormat, outputFormat);
+ if (err2 != OK) {
+ mCallback->onError(err2, ACTION_CODE_FATAL);
+ return;
+ }
- {
+ auto setRunning = [this] {
Mutexed<State>::Locked state(mState);
if (state->get() != STARTING) {
- state.unlock();
- mCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
- state.lock();
- return;
+ return UNKNOWN_ERROR;
}
state->set(RUNNING);
+ return OK;
+ };
+ if (tryAndReportOnError(setRunning) != OK) {
+ return;
}
mCallback->onStartCompleted();
}
@@ -652,13 +708,26 @@
}
void CCodec::signalFlush() {
- {
+ status_t err = [this] {
Mutexed<State>::Locked state(mState);
+ if (state->get() == FLUSHED) {
+ return ALREADY_EXISTS;
+ }
if (state->get() != RUNNING) {
- mCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
- return;
+ return UNKNOWN_ERROR;
}
state->set(FLUSHING);
+ return OK;
+ }();
+ switch (err) {
+ case ALREADY_EXISTS:
+ mCallback->onFlushCompleted();
+ return;
+ case OK:
+ break;
+ default:
+ mCallback->onError(err, ACTION_CODE_FATAL);
+ return;
}
(new AMessage(kWhatFlush, this))->post();
@@ -666,15 +735,16 @@
void CCodec::flush() {
std::shared_ptr<C2Component> comp;
- {
+ auto checkFlushing = [this, &comp] {
Mutexed<State>::Locked state(mState);
if (state->get() != FLUSHING) {
- state.unlock();
- mCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
- state.lock();
- return;
+ return UNKNOWN_ERROR;
}
comp = state->comp;
+ return OK;
+ };
+ if (tryAndReportOnError(checkFlushing) != OK) {
+ return;
}
mChannel->stop();
@@ -696,18 +766,19 @@
}
void CCodec::signalResume() {
- {
+ auto setResuming = [this] {
Mutexed<State>::Locked state(mState);
if (state->get() != FLUSHED) {
- state.unlock();
- mCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
- state.lock();
- return;
+ return UNKNOWN_ERROR;
}
state->set(RESUMING);
+ return OK;
+ };
+ if (tryAndReportOnError(setResuming) != OK) {
+ return;
}
- mChannel->start(nullptr, nullptr);
+ (void)mChannel->start(nullptr, nullptr);
{
Mutexed<State>::Locked state(mState);
@@ -727,6 +798,8 @@
}
void CCodec::signalEndOfInputStream() {
+ // TODO
+ mCallback->onSignaledInputEOS(INVALID_OPERATION);
}
void CCodec::signalRequestIDRFrame() {
@@ -735,9 +808,7 @@
void CCodec::onWorkDone(std::list<std::unique_ptr<C2Work>> &workItems) {
Mutexed<std::list<std::unique_ptr<C2Work>>>::Locked queue(mWorkDoneQueue);
- for (std::unique_ptr<C2Work> &item : workItems) {
- queue->push_back(std::move(item));
- }
+ queue->splice(queue->end(), workItems);
(new AMessage(kWhatWorkDone, this))->post();
}
@@ -747,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: {
@@ -834,8 +905,8 @@
}
ALOGW("previous call to %s exceeded timeout", name.c_str());
- mCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
initiateRelease(false);
+ mCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
}
} // namespace android
diff --git a/media/libstagefright/CCodecBufferChannel.cpp b/media/libstagefright/CCodecBufferChannel.cpp
index 449c6aa..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.
*/
@@ -60,7 +62,10 @@
/**
* Set format for MediaCodec-facing buffers.
*/
- void setFormat(const sp<AMessage> &format) { mFormat = format; }
+ void setFormat(const sp<AMessage> &format) {
+ CHECK(format != nullptr);
+ mFormat = format;
+ }
/**
* Returns true if the buffers are operating under array mode.
@@ -178,20 +183,143 @@
// TODO: get this info from component
const static size_t kMinBufferArraySize = 16;
const static size_t kLinearBufferSize = 524288;
+const static size_t kMaxGraphicBufferRefCount = 4;
-sp<LinearBlockBuffer> allocateLinearBuffer(
+/**
+ * Simple local buffer pool backed by std::vector.
+ */
+class LocalBufferPool : public std::enable_shared_from_this<LocalBufferPool> {
+public:
+ /**
+ * Create a new LocalBufferPool object.
+ *
+ * \param poolCapacity max total size of buffers managed by this pool.
+ *
+ * \return a newly created pool object.
+ */
+ static std::shared_ptr<LocalBufferPool> Create(size_t poolCapacity) {
+ return std::shared_ptr<LocalBufferPool>(new LocalBufferPool(poolCapacity));
+ }
+
+ /**
+ * Return an ABuffer object whose size is at least |capacity|.
+ *
+ * \param capacity requested capacity
+ * \return nullptr if the pool capacity is reached
+ * an ABuffer object otherwise.
+ */
+ sp<ABuffer> newBuffer(size_t capacity) {
+ Mutex::Autolock lock(mMutex);
+ auto it = std::find_if(
+ mPool.begin(), mPool.end(),
+ [capacity](const std::vector<uint8_t> &vec) {
+ return vec.capacity() >= capacity;
+ });
+ if (it != mPool.end()) {
+ sp<ABuffer> buffer = new VectorBuffer(std::move(*it), shared_from_this());
+ mPool.erase(it);
+ return buffer;
+ }
+ if (mUsedSize + capacity > mPoolCapacity) {
+ while (!mPool.empty()) {
+ mUsedSize -= mPool.back().capacity();
+ mPool.pop_back();
+ }
+ if (mUsedSize + capacity > mPoolCapacity) {
+ ALOGD("mUsedSize = %zu, capacity = %zu, mPoolCapacity = %zu",
+ mUsedSize, capacity, mPoolCapacity);
+ return nullptr;
+ }
+ }
+ std::vector<uint8_t> vec(capacity);
+ mUsedSize += vec.capacity();
+ return new VectorBuffer(std::move(vec), shared_from_this());
+ }
+
+private:
+ /**
+ * ABuffer backed by std::vector.
+ */
+ class VectorBuffer : public ::android::ABuffer {
+ public:
+ /**
+ * Construct a VectorBuffer by taking the ownership of supplied vector.
+ *
+ * \param vec backing vector of the buffer. this object takes
+ * ownership at construction.
+ * \param pool a LocalBufferPool object to return the vector at
+ * destruction.
+ */
+ VectorBuffer(std::vector<uint8_t> &&vec, const std::shared_ptr<LocalBufferPool> &pool)
+ : ABuffer(vec.data(), vec.capacity()),
+ mVec(std::move(vec)),
+ mPool(pool) {
+ }
+
+ ~VectorBuffer() override {
+ std::shared_ptr<LocalBufferPool> pool = mPool.lock();
+ if (pool) {
+ // If pool is alive, return the vector back to the pool so that
+ // it can be recycled.
+ pool->returnVector(std::move(mVec));
+ }
+ }
+
+ private:
+ std::vector<uint8_t> mVec;
+ std::weak_ptr<LocalBufferPool> mPool;
+ };
+
+ Mutex mMutex;
+ size_t mPoolCapacity;
+ size_t mUsedSize;
+ std::list<std::vector<uint8_t>> mPool;
+
+ /**
+ * Private constructor to prevent constructing non-managed LocalBufferPool.
+ */
+ explicit LocalBufferPool(size_t poolCapacity)
+ : mPoolCapacity(poolCapacity), mUsedSize(0) {
+ }
+
+ /**
+ * Take back the ownership of vec from the destructed VectorBuffer and put
+ * it in front of the pool.
+ */
+ void returnVector(std::vector<uint8_t> &&vec) {
+ Mutex::Autolock lock(mMutex);
+ mPool.push_front(std::move(vec));
+ }
+
+ DISALLOW_EVIL_CONSTRUCTORS(LocalBufferPool);
+};
+
+sp<GraphicBlockBuffer> AllocateGraphicBuffer(
const std::shared_ptr<C2BlockPool> &pool,
const sp<AMessage> &format,
- size_t size,
- const C2MemoryUsage &usage) {
- std::shared_ptr<C2LinearBlock> block;
-
- status_t err = pool->fetchLinearBlock(size, usage, &block);
- if (err != OK) {
+ uint32_t pixelFormat,
+ const C2MemoryUsage &usage,
+ const std::shared_ptr<LocalBufferPool> &localBufferPool) {
+ int32_t width, height;
+ if (!format->findInt32("width", &width) || !format->findInt32("height", &height)) {
+ ALOGD("format lacks width or height");
return nullptr;
}
- return LinearBlockBuffer::Allocate(format, block);
+ std::shared_ptr<C2GraphicBlock> block;
+ c2_status_t err = pool->fetchGraphicBlock(
+ width, height, pixelFormat, usage, &block);
+ if (err != C2_OK) {
+ ALOGD("fetch graphic block failed: %d", err);
+ return nullptr;
+ }
+
+ return GraphicBlockBuffer::Allocate(
+ format,
+ block,
+ [localBufferPool](size_t capacity) {
+ return localBufferPool->newBuffer(capacity);
+ });
}
class BuffersArrayImpl;
@@ -383,6 +511,7 @@
class InputBuffersArray : public CCodecBufferChannel::InputBuffers {
public:
InputBuffersArray() = default;
+ ~InputBuffersArray() override = default;
void initialize(
const FlexBuffersImpl &impl,
@@ -431,8 +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;
}
@@ -456,20 +584,138 @@
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() = default;
+ GraphicInputBuffers() : mLocalBufferPool(LocalBufferPool::Create(1920 * 1080 * 16)) {}
+ ~GraphicInputBuffers() override = default;
+
+ 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<GraphicBlockBuffer> newBuffer = AllocateGraphicBuffer(
+ mPool, mFormat, HAL_PIXEL_FORMAT_YV12, usage, mLocalBufferPool);
+ if (newBuffer == nullptr) {
+ return false;
+ }
+ *index = mImpl.assignSlot(newBuffer);
+ *buffer = newBuffer;
+ return true;
+ }
+
+ std::shared_ptr<C2Buffer> releaseBuffer(const sp<MediaCodecBuffer> &buffer) override {
+ return mImpl.releaseSlot(buffer);
+ }
+
+ void flush() override {
+ // This is no-op by default unless we're in array mode where we need to keep
+ // track of the flushed work.
+ }
+
+ std::unique_ptr<CCodecBufferChannel::InputBuffers> toArrayMode() final {
+ std::unique_ptr<InputBuffersArray> array(new InputBuffersArray);
+ array->setFormat(mFormat);
+ array->initialize(
+ mImpl,
+ kMinBufferArraySize,
+ [pool = mPool, format = mFormat, lbp = mLocalBufferPool]() -> sp<Codec2Buffer> {
+ C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
+ return AllocateGraphicBuffer(
+ pool, format, HAL_PIXEL_FORMAT_YV12, usage, lbp);
+ });
+ return std::move(array);
+ }
+
+private:
+ FlexBuffersImpl mImpl;
+ std::shared_ptr<LocalBufferPool> mLocalBufferPool;
+};
+
+class DummyInputBuffers : public CCodecBufferChannel::InputBuffers {
+public:
+ DummyInputBuffers() = default;
bool requestNewBuffer(size_t *, sp<MediaCodecBuffer> *) override {
return false;
@@ -495,7 +741,8 @@
class OutputBuffersArray : public CCodecBufferChannel::OutputBuffers {
public:
- using CCodecBufferChannel::OutputBuffers::OutputBuffers;
+ OutputBuffersArray() = default;
+ ~OutputBuffersArray() override = default;
void initialize(
const FlexBuffersImpl &impl,
@@ -522,12 +769,14 @@
return clientBuffer->canCopy(buffer);
});
if (err != OK) {
- return false;
- }
- if (!c2Buffer->copy(buffer)) {
+ ALOGD("grabBuffer failed: %d", err);
return false;
}
c2Buffer->setFormat(mFormat);
+ if (!c2Buffer->copy(buffer)) {
+ ALOGD("copy buffer failed");
+ return false;
+ }
*clientBuffer = c2Buffer;
return true;
}
@@ -585,6 +834,7 @@
size_t *index,
sp<MediaCodecBuffer> *clientBuffer) override {
sp<Codec2Buffer> newBuffer = wrap(buffer);
+ newBuffer->setFormat(mFormat);
*index = mImpl.assignSlot(newBuffer);
*clientBuffer = newBuffer;
return true;
@@ -627,8 +877,21 @@
return std::move(array);
}
+ /**
+ * Return an appropriate Codec2Buffer object for the type of buffers.
+ *
+ * \param buffer C2Buffer object to wrap.
+ *
+ * \return appropriate Codec2Buffer object to wrap |buffer|.
+ */
virtual sp<Codec2Buffer> wrap(const std::shared_ptr<C2Buffer> &buffer) = 0;
+ /**
+ * Return an appropriate Codec2Buffer object for the type of buffers, to be
+ * used as an empty array buffer.
+ *
+ * \return appropriate Codec2Buffer object which can copy() from C2Buffers.
+ */
virtual sp<Codec2Buffer> allocateArrayBuffer() = 0;
private:
@@ -673,6 +936,34 @@
}
};
+class RawGraphicOutputBuffers : public FlexOutputBuffers {
+public:
+ RawGraphicOutputBuffers()
+ : mLocalBufferPool(LocalBufferPool::Create(1920 * 1080 * 16)) {
+ }
+ ~RawGraphicOutputBuffers() override = default;
+
+ sp<Codec2Buffer> wrap(const std::shared_ptr<C2Buffer> &buffer) override {
+ return ConstGraphicBlockBuffer::Allocate(
+ mFormat,
+ buffer,
+ [lbp = mLocalBufferPool](size_t capacity) {
+ return lbp->newBuffer(capacity);
+ });
+ }
+
+ sp<Codec2Buffer> allocateArrayBuffer() override {
+ return ConstGraphicBlockBuffer::AllocateEmpty(
+ mFormat,
+ [lbp = mLocalBufferPool](size_t capacity) {
+ return lbp->newBuffer(capacity);
+ });
+ }
+
+private:
+ std::shared_ptr<LocalBufferPool> mLocalBufferPool;
+};
+
} // namespace
CCodecBufferChannel::QueueGuard::QueueGuard(
@@ -722,7 +1013,8 @@
CCodecBufferChannel::CCodecBufferChannel(
const std::function<void(status_t, enum ActionCode)> &onError)
- : mOnError(onError),
+ : mHeapSeqNum(-1),
+ mOnError(onError),
mFrameIndex(0u),
mFirstValidFrameIndex(0u) {
}
@@ -735,56 +1027,6 @@
void CCodecBufferChannel::setComponent(const std::shared_ptr<C2Component> &component) {
mComponent = component;
-
- C2StreamFormatConfig::input inputFormat(0u);
- C2StreamFormatConfig::output outputFormat(0u);
- c2_status_t err = mComponent->intf()->query_vb(
- { &inputFormat, &outputFormat },
- {},
- C2_DONT_BLOCK,
- nullptr);
- if (err != C2_OK) {
- // TODO: error
- return;
- }
-
- {
- Mutexed<std::unique_ptr<InputBuffers>>::Locked buffers(mInputBuffers);
-
- bool graphic = (inputFormat.value == C2FormatVideo);
- if (graphic) {
- buffers->reset(new GraphicInputBuffers);
- } else {
- buffers->reset(new LinearInputBuffers);
- }
-
- ALOGV("graphic = %s", graphic ? "true" : "false");
- std::shared_ptr<C2BlockPool> pool;
- err = GetCodec2BlockPool(
- graphic ? C2BlockPool::BASIC_GRAPHIC : C2BlockPool::BASIC_LINEAR,
- component,
- &pool);
- if (err == C2_OK) {
- (*buffers)->setPool(pool);
- } else {
- // TODO: error
- }
- // TODO: remove once we switch to proper buffer pool.
- if (!graphic) {
- *buffers = (*buffers)->toArrayMode();
- }
- }
-
- {
- Mutexed<std::unique_ptr<OutputBuffers>>::Locked buffers(mOutputBuffers);
-
- bool graphic = (outputFormat.value == C2FormatVideo);
- if (graphic) {
- buffers->reset(new GraphicOutputBuffers);
- } else {
- buffers->reset(new LinearOutputBuffers);
- }
- }
}
status_t CCodecBufferChannel::setInputSurface(
@@ -794,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));
@@ -813,6 +1049,7 @@
if (buffer->meta()->findInt32("csd", &tmp) && tmp) {
flags |= C2FrameData::FLAG_CODEC_CONFIG;
}
+ ALOGV("queueInputBuffer: buffer->size() = %zu", buffer->size());
std::unique_ptr<C2Work> work(new C2Work);
work->input.flags = (C2FrameData::flags_t)flags;
work->input.ordinal.timestamp = timeUs;
@@ -820,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
@@ -832,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() {
@@ -876,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;
}
@@ -894,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,
@@ -902,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;
@@ -910,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;
}
@@ -969,13 +1300,86 @@
(*buffers)->getArray(array);
}
-void CCodecBufferChannel::start(const sp<AMessage> &inputFormat, const sp<AMessage> &outputFormat) {
+status_t CCodecBufferChannel::start(
+ const sp<AMessage> &inputFormat, const sp<AMessage> &outputFormat) {
+ C2StreamFormatConfig::input iStreamFormat(0u);
+ C2StreamFormatConfig::output oStreamFormat(0u);
+ c2_status_t err = mComponent->intf()->query_vb(
+ { &iStreamFormat, &oStreamFormat },
+ {},
+ C2_DONT_BLOCK,
+ nullptr);
+ 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);
+
+ bool graphic = (iStreamFormat.value == C2FormatVideo);
+ if (graphic) {
+ if (mInputSurface) {
+ buffers->reset(new DummyInputBuffers);
+ } else {
+ buffers->reset(new GraphicInputBuffers);
+ }
+ } else {
+ 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);
+
+ ALOGV("graphic = %s", graphic ? "true" : "false");
+ std::shared_ptr<C2BlockPool> pool;
+ if (graphic) {
+ err = GetCodec2BlockPool(C2BlockPool::BASIC_GRAPHIC, mComponent, &pool);
+ } else {
+ err = CreateCodec2BlockPool(C2PlatformAllocatorStore::ION,
+ mComponent, &pool);
+ }
+ if (err == C2_OK) {
+ (*buffers)->setPool(pool);
+ } else {
+ // TODO: error
+ }
}
+
if (outputFormat != nullptr) {
+ bool hasOutputSurface = false;
+ {
+ Mutexed<OutputSurface>::Locked output(mOutputSurface);
+ hasOutputSurface = (output->surface != nullptr);
+ }
+
Mutexed<std::unique_ptr<OutputBuffers>>::Locked buffers(mOutputBuffers);
+
+ bool graphic = (oStreamFormat.value == C2FormatVideo);
+ if (graphic) {
+ if (hasOutputSurface) {
+ buffers->reset(new GraphicOutputBuffers);
+ } else {
+ buffers->reset(new RawGraphicOutputBuffers);
+ }
+ } else {
+ buffers->reset(new LinearOutputBuffers);
+ }
(*buffers)->setFormat(outputFormat);
}
@@ -990,9 +1394,7 @@
if (!(*buffers)->requestNewBuffer(&index, &buffer)) {
if (i == 0) {
ALOGE("start: cannot allocate memory at all");
- buffers.unlock();
- mOnError(NO_MEMORY, ACTION_CODE_FATAL);
- buffers.lock();
+ return NO_MEMORY;
} else {
ALOGV("start: cannot allocate memory, only %zu buffers allocated", i);
}
@@ -1004,6 +1406,7 @@
} else {
(void)mInputSurface->connect(mComponent);
}
+ return OK;
}
void CCodecBufferChannel::stop() {
@@ -1011,6 +1414,7 @@
mFirstValidFrameIndex = mFrameIndex.load();
if (mInputSurface != nullptr) {
mInputSurface->disconnect();
+ mInputSurface.reset();
}
}
@@ -1026,8 +1430,13 @@
}
void CCodecBufferChannel::onWorkDone(const std::unique_ptr<C2Work> &work) {
- if (work->result != OK) {
- ALOGE("work failed to complete: %d", work->result);
+ if (work->result != C2_OK) {
+ if (work->result == C2_NOT_FOUND) {
+ // TODO: Define what flushed work's result is.
+ ALOGD("flushed work; ignored.");
+ return;
+ }
+ ALOGD("work failed to complete: %d", work->result);
mOnError(work->result, ACTION_CODE_FATAL);
return;
}
@@ -1136,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");
@@ -1150,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 540cf8c..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;
@@ -446,12 +455,17 @@
ssize_t NuMediaExtractor::fetchAllTrackSamples(
int64_t seekTimeUs, MediaSource::ReadOptions::SeekMode mode) {
TrackInfo *minInfo = NULL;
- ssize_t minIndex = -1;
+ ssize_t minIndex = ERROR_END_OF_STREAM;
for (size_t i = 0; i < mSelectedTracks.size(); ++i) {
TrackInfo *info = &mSelectedTracks.editItemAt(i);
fetchTrackSamples(info, seekTimeUs, mode);
+ status_t err = info->mFinalResult;
+ if (err != OK && err != ERROR_END_OF_STREAM) {
+ return err;
+ }
+
if (info->mSamples.empty()) {
continue;
}
@@ -721,7 +735,8 @@
ssize_t minIndex = fetchAllTrackSamples();
if (minIndex < 0) {
- return ERROR_END_OF_STREAM;
+ status_t err = minIndex;
+ return err;
}
TrackInfo *info = &mSelectedTracks.editItemAt(minIndex);
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/codec2/SimpleC2Component.cpp b/media/libstagefright/codec2/SimpleC2Component.cpp
index 333cfeb..f3d95f6 100644
--- a/media/libstagefright/codec2/SimpleC2Component.cpp
+++ b/media/libstagefright/codec2/SimpleC2Component.cpp
@@ -51,6 +51,8 @@
mQueue.push_back({ nullptr, drainMode });
}
+////////////////////////////////////////////////////////////////////////////////
+
SimpleC2Component::SimpleC2Component(
const std::shared_ptr<C2ComponentInterface> &intf)
: mIntf(intf) {
@@ -91,13 +93,13 @@
}
c2_status_t SimpleC2Component::announce_nb(const std::vector<C2WorkOutline> &items) {
- (void) items;
+ (void)items;
return C2_OMITTED;
}
c2_status_t SimpleC2Component::flush_sm(
- flush_mode_t flushThrough, std::list<std::unique_ptr<C2Work>>* const flushedWork) {
- (void) flushThrough;
+ flush_mode_t flushMode, std::list<std::unique_ptr<C2Work>>* const flushedWork) {
+ (void)flushMode;
{
Mutexed<ExecState>::Locked state(mExecState);
if (state->mState != RUNNING) {
@@ -107,6 +109,7 @@
{
Mutexed<WorkQueue>::Locked queue(mWorkQueue);
queue->incGeneration();
+ // TODO: queue->splicedBy(flushedWork, flushedWork->end());
while (!queue->empty()) {
std::unique_ptr<C2Work> work = queue->pop_front();
if (work) {
@@ -122,7 +125,7 @@
}
}
- return onFlush_sm();
+ return C2_OK;
}
c2_status_t SimpleC2Component::drain_nb(drain_mode_t drainMode) {
@@ -160,6 +163,10 @@
}
if (!state->mThread.joinable()) {
mExitRequested = false;
+ {
+ Mutexed<ExitMonitor>::Locked monitor(mExitMonitor);
+ monitor->mExited = false;
+ }
state->mThread = std::thread(
[](std::weak_ptr<SimpleC2Component> wp) {
while (true) {
@@ -168,6 +175,8 @@
return;
}
if (thiz->exitRequested()) {
+ ALOGV("stop processing");
+ thiz->signalExit();
return;
}
thiz->processQueue();
@@ -179,7 +188,42 @@
return C2_OK;
}
+void SimpleC2Component::signalExit() {
+ Mutexed<ExitMonitor>::Locked monitor(mExitMonitor);
+ monitor->mExited = true;
+ monitor->mCondition.broadcast();
+}
+
+void SimpleC2Component::requestExitAndWait(std::function<void()> job) {
+ {
+ Mutexed<ExecState>::Locked state(mExecState);
+ if (!state->mThread.joinable()) {
+ return;
+ }
+ }
+ mExitRequested = true;
+ {
+ Mutexed<WorkQueue>::Locked queue(mWorkQueue);
+ queue->mCondition.broadcast();
+ }
+ // TODO: timeout?
+ {
+ Mutexed<ExitMonitor>::Locked monitor(mExitMonitor);
+ while (!monitor->mExited) {
+ monitor.waitForCondition(monitor->mCondition);
+ }
+ job();
+ }
+ Mutexed<ExecState>::Locked state(mExecState);
+ if (state->mThread.joinable()) {
+ ALOGV("joining the processing thread");
+ state->mThread.join();
+ ALOGV("joined the processing thread");
+ }
+}
+
c2_status_t SimpleC2Component::stop() {
+ ALOGV("stop");
{
Mutexed<ExecState>::Locked state(mExecState);
if (state->mState != RUNNING) {
@@ -195,7 +239,8 @@
Mutexed<PendingWork>::Locked pending(mPendingWork);
pending->clear();
}
- c2_status_t err = onStop();
+ c2_status_t err;
+ requestExitAndWait([this, &err]{ err = onStop(); });
if (err != C2_OK) {
return err;
}
@@ -203,6 +248,7 @@
}
c2_status_t SimpleC2Component::reset() {
+ ALOGV("reset");
{
Mutexed<ExecState>::Locked state(mExecState);
state->mState = UNINITIALIZED;
@@ -215,21 +261,13 @@
Mutexed<PendingWork>::Locked pending(mPendingWork);
pending->clear();
}
- onReset();
+ requestExitAndWait([this]{ onReset(); });
return C2_OK;
}
c2_status_t SimpleC2Component::release() {
- std::thread releasing;
- {
- Mutexed<ExecState>::Locked state(mExecState);
- releasing = std::move(state->mThread);
- }
- mExitRequested = true;
- if (releasing.joinable()) {
- releasing.join();
- }
- onRelease();
+ ALOGV("release");
+ requestExitAndWait([this]{ onRelease(); });
return C2_OK;
}
@@ -271,10 +309,14 @@
std::unique_ptr<C2Work> work;
uint64_t generation;
int32_t drainMode;
+ bool isFlushPending = false;
{
Mutexed<WorkQueue>::Locked queue(mWorkQueue);
nsecs_t deadline = systemTime() + ms2ns(250);
while (queue->empty()) {
+ if (exitRequested()) {
+ return;
+ }
nsecs_t now = systemTime();
if (now >= deadline) {
return;
@@ -287,8 +329,17 @@
generation = queue->generation();
drainMode = queue->drainMode();
+ isFlushPending = queue->popPendingFlush();
work = queue->pop_front();
}
+ if (isFlushPending) {
+ ALOGV("processing pending flush");
+ c2_status_t err = onFlush_sm();
+ if (err != C2_OK) {
+ ALOGD("flush err: %d", err);
+ // TODO: error
+ }
+ }
if (!mOutputBlockPool) {
c2_status_t err = [this] {
@@ -302,12 +353,15 @@
if (err != C2_OK) {
return err;
}
- err = GetCodec2BlockPool(
- (outputFormat.value == C2FormatVideo)
- ? C2BlockPool::BASIC_GRAPHIC
- : C2BlockPool::BASIC_LINEAR,
- shared_from_this(),
- &mOutputBlockPool);
+ if (outputFormat.value == C2FormatVideo) {
+ err = GetCodec2BlockPool(
+ C2BlockPool::BASIC_GRAPHIC,
+ shared_from_this(), &mOutputBlockPool);
+ } else {
+ err = CreateCodec2BlockPool(
+ C2PlatformAllocatorStore::ION,
+ shared_from_this(), &mOutputBlockPool);
+ }
if (err != C2_OK) {
return err;
}
@@ -330,10 +384,12 @@
}
process(work, mOutputBlockPool);
+ ALOGV("processed frame #%" PRIu64, work->input.ordinal.frameIndex.peeku());
{
Mutexed<WorkQueue>::Locked queue(mWorkQueue);
if (queue->generation() != generation) {
- ALOGW("work form old generation: was %" PRIu64 " now %" PRIu64, queue->generation(), generation);
+ ALOGD("work form old generation: was %" PRIu64 " now %" PRIu64,
+ queue->generation(), generation);
work->result = C2_NOT_FOUND;
queue.unlock();
{
@@ -358,9 +414,10 @@
unexpected = std::move(pending->at(frameIndex));
pending->erase(frameIndex);
}
- (void) pending->insert({ frameIndex, std::move(work) });
+ (void)pending->insert({ frameIndex, std::move(work) });
}
if (unexpected) {
+ ALOGD("unexpected pending work");
unexpected->result = C2_CORRUPTED;
Mutexed<ExecState>::Locked state(mExecState);
state->mListener->onWorkDone_nb(shared_from_this(), vec(unexpected));
diff --git a/media/libstagefright/codec2/include/SimpleC2Component.h b/media/libstagefright/codec2/include/SimpleC2Component.h
index a4b6ee1..168e79f 100644
--- a/media/libstagefright/codec2/include/SimpleC2Component.h
+++ b/media/libstagefright/codec2/include/SimpleC2Component.h
@@ -52,6 +52,7 @@
// for thread
inline bool exitRequested() { return mExitRequested; }
void processQueue();
+ void signalExit();
protected:
/**
@@ -157,16 +158,21 @@
class WorkQueue {
public:
- inline WorkQueue() : mGeneration(0ul) {}
+ inline WorkQueue() : mFlush(false), mGeneration(0ul) {}
inline uint64_t generation() const { return mGeneration; }
- inline void incGeneration() { ++mGeneration; }
+ inline void incGeneration() { ++mGeneration; mFlush = true; }
std::unique_ptr<C2Work> pop_front();
void push_back(std::unique_ptr<C2Work> work);
bool empty() const;
uint32_t drainMode() const;
void markDrain(uint32_t drainMode);
+ inline bool popPendingFlush() {
+ bool flush = mFlush;
+ mFlush = false;
+ return flush;
+ }
void clear();
Condition mCondition;
@@ -177,6 +183,7 @@
uint32_t drainMode;
};
+ bool mFlush;
uint64_t mGeneration;
std::list<Entry> mQueue;
};
@@ -185,9 +192,18 @@
typedef std::unordered_map<uint64_t, std::unique_ptr<C2Work>> PendingWork;
Mutexed<PendingWork> mPendingWork;
+ struct ExitMonitor {
+ inline ExitMonitor() : mExited(false) {}
+ Condition mCondition;
+ bool mExited;
+ };
+ Mutexed<ExitMonitor> mExitMonitor;
+
std::shared_ptr<C2BlockPool> mOutputBlockPool;
SimpleC2Component() = delete;
+
+ void requestExitAndWait(std::function<void()> job);
};
} // namespace android
diff --git a/media/libstagefright/codec2/tests/vndk/C2BufferTest.cpp b/media/libstagefright/codec2/tests/vndk/C2BufferTest.cpp
index 038be48..f389abc 100644
--- a/media/libstagefright/codec2/tests/vndk/C2BufferTest.cpp
+++ b/media/libstagefright/codec2/tests/vndk/C2BufferTest.cpp
@@ -232,7 +232,8 @@
class C2BufferTest : public ::testing::Test {
public:
C2BufferTest()
- : mLinearAllocator(std::make_shared<C2AllocatorIon>('i')),
+ : mBlockPoolId(C2BlockPool::PLATFORM_START),
+ mLinearAllocator(std::make_shared<C2AllocatorIon>('i')),
mSize(0u),
mAddr(nullptr),
mGraphicAllocator(std::make_shared<C2AllocatorGralloc>('g')) {
@@ -281,7 +282,7 @@
}
std::shared_ptr<C2BlockPool> makeLinearBlockPool() {
- return std::make_shared<C2BasicLinearBlockPool>(mLinearAllocator);
+ return std::make_shared<C2PooledBlockPool>(mLinearAllocator, mBlockPoolId++);
}
void allocateGraphic(uint32_t width, uint32_t height) {
@@ -328,6 +329,7 @@
}
private:
+ C2BlockPool::local_id_t mBlockPoolId;
std::shared_ptr<C2Allocator> mLinearAllocator;
std::shared_ptr<C2LinearAllocation> mLinearAllocation;
size_t mSize;
@@ -745,4 +747,23 @@
EXPECT_FALSE(buffer->hasInfo(info2->type()));
}
+TEST_F(C2BufferTest, MultipleLinearMapTest) {
+ std::shared_ptr<C2BlockPool> pool(makeLinearBlockPool());
+ constexpr size_t kCapacity = 524288u;
+ for (int i = 0; i < 100; ++i) {
+ std::vector<C2WriteView> wViews;
+ std::vector<C2ReadView> rViews;
+ for (int j = 0; j < 16; ++j) {
+ std::shared_ptr<C2LinearBlock> block;
+ ASSERT_EQ(C2_OK, pool->fetchLinearBlock(
+ kCapacity,
+ { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE },
+ &block));
+ wViews.push_back(block->map().get());
+ C2ConstLinearBlock cBlock = block->share(0, kCapacity / 2, C2Fence());
+ rViews.push_back(cBlock.map().get());
+ }
+ }
+}
+
} // namespace android
diff --git a/media/libstagefright/codec2/vndk/Android.bp b/media/libstagefright/codec2/vndk/Android.bp
index 47afd42..95e7c39 100644
--- a/media/libstagefright/codec2/vndk/Android.bp
+++ b/media/libstagefright/codec2/vndk/Android.bp
@@ -39,14 +39,17 @@
shared_libs: [
"android.hardware.graphics.allocator@2.0",
"android.hardware.graphics.mapper@2.0",
+ "android.hardware.media.bufferpool@1.0",
"libbinder",
"libcutils",
"libdl",
"libhardware",
"libhidlbase",
"libion",
+ "libfmq",
"liblog",
"libstagefright_foundation",
+ "libstagefright_bufferpool@1.0",
"libui",
"libutils",
],
diff --git a/media/libstagefright/codec2/vndk/C2AllocatorIon.cpp b/media/libstagefright/codec2/vndk/C2AllocatorIon.cpp
index cf7658a..664c09e 100644
--- a/media/libstagefright/codec2/vndk/C2AllocatorIon.cpp
+++ b/media/libstagefright/codec2/vndk/C2AllocatorIon.cpp
@@ -18,6 +18,8 @@
#define LOG_TAG "C2AllocatorIon"
#include <utils/Log.h>
+#include <list>
+
#include <ion/ion.h>
#include <sys/mman.h>
#include <unistd.h>
@@ -148,6 +150,7 @@
* so that we can capture the error.
*
* \param ionFd ion client (ownership transferred to created object)
+ * \param owned whehter native_handle_t is owned by an allocation or not.
* \param capacity size of allocation
* \param bufferFd buffer handle (ownership transferred to created object). Must be
* invalid if err is not 0.
@@ -155,14 +158,14 @@
* invalid if err is not 0.
* \param err errno during buffer allocation or import
*/
- Impl(int ionFd, size_t capacity, int bufferFd, ion_user_handle_t buffer, C2Allocator::id_t id, int err)
+ Impl(int ionFd, bool owned, size_t capacity, int bufferFd, ion_user_handle_t buffer, C2Allocator::id_t id, int err)
: mIonFd(ionFd),
+ mHandleOwned(owned),
mHandle(bufferFd, capacity),
mBuffer(buffer),
mId(id),
mInit(c2_map_errno<ENOMEM, EACCES, EINVAL>(err)),
- mMapFd(-1),
- mMapSize(0) {
+ mMapFd(-1) {
if (mInit != C2_OK) {
// close ionFd now on error
if (mIonFd >= 0) {
@@ -188,7 +191,7 @@
static Impl *Import(int ionFd, size_t capacity, int bufferFd, C2Allocator::id_t id) {
ion_user_handle_t buffer = -1;
int ret = ion_import(ionFd, bufferFd, &buffer);
- return new Impl(ionFd, capacity, bufferFd, buffer, id, ret);
+ return new Impl(ionFd, false, capacity, bufferFd, buffer, id, ret);
}
/**
@@ -215,13 +218,14 @@
buffer = -1;
}
}
- return new Impl(ionFd, size, bufferFd, buffer, id, ret);
+ return new Impl(ionFd, true, size, bufferFd, buffer, id, ret);
}
c2_status_t map(size_t offset, size_t size, C2MemoryUsage usage, C2Fence *fence, void **addr) {
(void)fence; // TODO: wait for fence
*addr = nullptr;
- if (mMapSize > 0) {
+ if (!mMappings.empty()) {
+ ALOGV("multiple map");
// TODO: technically we should return DUPLICATE here, but our block views don't
// actually unmap, so we end up remapping an ion buffer multiple times.
//
@@ -244,54 +248,63 @@
size_t alignmentBytes = offset % PAGE_SIZE;
size_t mapOffset = offset - alignmentBytes;
size_t mapSize = size + alignmentBytes;
+ Mapping map = { nullptr, alignmentBytes, mapSize };
c2_status_t err = C2_OK;
if (mMapFd == -1) {
int ret = ion_map(mIonFd, mBuffer, mapSize, prot,
- flags, mapOffset, (unsigned char**)&mMapAddr, &mMapFd);
+ flags, mapOffset, (unsigned char**)&map.addr, &mMapFd);
if (ret) {
mMapFd = -1;
- *addr = nullptr;
+ map.addr = *addr = nullptr;
err = c2_map_errno<EINVAL>(-ret);
} else {
- *addr = (uint8_t *)mMapAddr + alignmentBytes;
- mMapAlignmentBytes = alignmentBytes;
- mMapSize = mapSize;
+ *addr = (uint8_t *)map.addr + alignmentBytes;
}
} else {
- mMapAddr = mmap(nullptr, mapSize, prot, flags, mMapFd, mapOffset);
- if (mMapAddr == MAP_FAILED) {
- mMapAddr = *addr = nullptr;
+ map.addr = mmap(nullptr, mapSize, prot, flags, mMapFd, mapOffset);
+ if (map.addr == MAP_FAILED) {
+ map.addr = *addr = nullptr;
err = c2_map_errno<EINVAL>(errno);
} else {
- *addr = (uint8_t *)mMapAddr + alignmentBytes;
- mMapAlignmentBytes = alignmentBytes;
- mMapSize = mapSize;
+ *addr = (uint8_t *)map.addr + alignmentBytes;
}
}
+ if (map.addr) {
+ mMappings.push_back(map);
+ }
return err;
}
c2_status_t unmap(void *addr, size_t size, C2Fence *fence) {
- if (mMapFd < 0 || mMapSize == 0) {
+ if (mMapFd < 0 || mMappings.empty()) {
return C2_NOT_FOUND;
}
- if (addr != (uint8_t *)mMapAddr + mMapAlignmentBytes ||
- size + mMapAlignmentBytes != mMapSize) {
- return C2_BAD_VALUE;
+ for (auto it = mMappings.begin(); it != mMappings.end(); ++it) {
+ if (addr != (uint8_t *)it->addr + it->alignmentBytes ||
+ size + it->alignmentBytes != it->size) {
+ continue;
+ }
+ int err = munmap(it->addr, it->size);
+ if (err != 0) {
+ return c2_map_errno<EINVAL>(errno);
+ }
+ if (fence) {
+ *fence = C2Fence(); // not using fences
+ }
+ (void)mMappings.erase(it);
+ return C2_OK;
}
- int err = munmap(mMapAddr, mMapSize);
- if (err != 0) {
- return c2_map_errno<EINVAL>(errno);
- }
- if (fence) {
- *fence = C2Fence(); // not using fences
- }
- mMapSize = 0;
- return C2_OK;
+ return C2_BAD_VALUE;
}
~Impl() {
+ if (!mMappings.empty()) {
+ ALOGD("Dangling mappings!");
+ for (const Mapping &map : mMappings) {
+ (void)munmap(map.addr, map.size);
+ }
+ }
if (mMapFd >= 0) {
close(mMapFd);
mMapFd = -1;
@@ -302,7 +315,9 @@
if (mIonFd >= 0) {
close(mIonFd);
}
- native_handle_close(&mHandle);
+ if (mHandleOwned) {
+ native_handle_close(&mHandle);
+ }
}
c2_status_t status() const {
@@ -323,14 +338,18 @@
private:
int mIonFd;
+ bool mHandleOwned;
C2HandleIon mHandle;
ion_user_handle_t mBuffer;
C2Allocator::id_t mId;
c2_status_t mInit;
int mMapFd; // only one for now
- void *mMapAddr;
- size_t mMapAlignmentBytes;
- size_t mMapSize;
+ struct Mapping {
+ void *addr;
+ size_t alignmentBytes;
+ size_t size;
+ };
+ std::list<Mapping> mMappings;
};
c2_status_t C2AllocationIon::map(
diff --git a/media/libstagefright/codec2/vndk/C2Buffer.cpp b/media/libstagefright/codec2/vndk/C2Buffer.cpp
index dc765f5..af2c20d 100644
--- a/media/libstagefright/codec2/vndk/C2Buffer.cpp
+++ b/media/libstagefright/codec2/vndk/C2Buffer.cpp
@@ -25,8 +25,16 @@
#include <C2BufferPriv.h>
#include <C2BlockInternal.h>
+#include <ClientManager.h>
+
namespace {
+using android::hardware::media::bufferpool::V1_0::ResultStatus;
+using android::hardware::media::bufferpool::V1_0::implementation::BufferPoolAllocation;
+using android::hardware::media::bufferpool::V1_0::implementation::BufferPoolAllocator;
+using android::hardware::media::bufferpool::V1_0::implementation::ClientManager;
+using android::hardware::media::bufferpool::V1_0::implementation::ConnectionId;
+
// This anonymous namespace contains the helper classes that allow our implementation to create
// block/buffer objects.
//
@@ -341,6 +349,257 @@
return std::shared_ptr<C2LinearBlock>(new C2LinearBlock(impl, *impl));
}
+/**
+ * Wrapped C2Allocator which is injected to buffer pool on behalf of
+ * C2BlockPool.
+ */
+class _C2BufferPoolAllocator : public BufferPoolAllocator {
+public:
+ _C2BufferPoolAllocator(const std::shared_ptr<C2Allocator> &allocator)
+ : mAllocator(allocator) {}
+
+ ~_C2BufferPoolAllocator() override {}
+
+ ResultStatus allocate(const std::vector<uint8_t> ¶ms,
+ std::shared_ptr<BufferPoolAllocation> *alloc) override;
+
+ bool compatible(const std::vector<uint8_t> &newParams,
+ const std::vector<uint8_t> &oldParams) override;
+
+ // Methods for codec2 component (C2BlockPool).
+ /**
+ * Transforms linear allocation parameters for C2Allocator to parameters
+ * for buffer pool.
+ *
+ * @param capacity size of linear allocation
+ * @param usage memory usage pattern for linear allocation
+ * @param params allocation parameters for buffer pool
+ */
+ void getLinearParams(uint32_t capacity, C2MemoryUsage usage,
+ std::vector<uint8_t> *params);
+
+ /**
+ * Transforms graphic allocation parameters for C2Allocator to parameters
+ * for buffer pool.
+ *
+ * @param width width of graphic allocation
+ * @param height height of graphic allocation
+ * @param format color format of graphic allocation
+ * @param params allocation parameter for buffer pool
+ */
+ void getGraphicParams(uint32_t width, uint32_t height,
+ uint32_t format, C2MemoryUsage usage,
+ std::vector<uint8_t> *params);
+
+ /**
+ * Transforms an existing native handle to an C2LinearAllcation.
+ * Wrapper to C2Allocator#priorLinearAllocation
+ */
+ c2_status_t priorLinearAllocation(
+ const C2Handle *handle,
+ std::shared_ptr<C2LinearAllocation> *c2Allocation);
+
+ /**
+ * Transforms an existing native handle to an C2GraphicAllcation.
+ * Wrapper to C2Allocator#priorGraphicAllocation
+ */
+ c2_status_t priorGraphicAllocation(
+ const C2Handle *handle,
+ std::shared_ptr<C2GraphicAllocation> *c2Allocation);
+
+private:
+ static constexpr int kMaxIntParams = 5; // large enough number;
+
+ enum AllocType : uint8_t {
+ ALLOC_NONE = 0,
+
+ ALLOC_LINEAR,
+ ALLOC_GRAPHIC,
+ };
+
+ union AllocParams {
+ struct {
+ AllocType allocType;
+ C2MemoryUsage usage;
+ uint32_t params[kMaxIntParams];
+ } data;
+ uint8_t array[0];
+
+ AllocParams() : data{ALLOC_NONE, {0, 0}, {0}} {}
+ AllocParams(C2MemoryUsage usage, uint32_t capacity)
+ : data{ALLOC_LINEAR, usage, {[0] = capacity}} {}
+ AllocParams(
+ C2MemoryUsage usage,
+ uint32_t width, uint32_t height, uint32_t format)
+ : data{ALLOC_GRAPHIC, usage, {width, height, format}} {}
+ };
+
+ const std::shared_ptr<C2Allocator> mAllocator;
+};
+
+struct LinearAllocationDtor {
+ LinearAllocationDtor(const std::shared_ptr<C2LinearAllocation> &alloc)
+ : mAllocation(alloc) {}
+
+ void operator()(BufferPoolAllocation *poolAlloc) { delete poolAlloc; }
+
+ const std::shared_ptr<C2LinearAllocation> mAllocation;
+};
+
+ResultStatus _C2BufferPoolAllocator::allocate(
+ const std::vector<uint8_t> ¶ms,
+ std::shared_ptr<BufferPoolAllocation> *alloc) {
+ AllocParams c2Params;
+ memcpy(&c2Params, params.data(), std::min(sizeof(AllocParams), params.size()));
+ std::shared_ptr<C2LinearAllocation> c2Linear;
+ c2_status_t status = C2_BAD_VALUE;
+ switch(c2Params.data.allocType) {
+ case ALLOC_NONE:
+ break;
+ case ALLOC_LINEAR:
+ status = mAllocator->newLinearAllocation(
+ c2Params.data.params[0], c2Params.data.usage, &c2Linear);
+ if (status == C2_OK && c2Linear) {
+ BufferPoolAllocation *ptr = new BufferPoolAllocation(c2Linear->handle());
+ if (ptr) {
+ *alloc = std::shared_ptr<BufferPoolAllocation>(
+ ptr, LinearAllocationDtor(c2Linear));
+ if (*alloc) {
+ return ResultStatus::OK;
+ }
+ delete ptr;
+ }
+ return ResultStatus::NO_MEMORY;
+ }
+ break;
+ case ALLOC_GRAPHIC:
+ // TODO
+ break;
+ default:
+ break;
+ }
+ return ResultStatus::CRITICAL_ERROR;
+}
+
+bool _C2BufferPoolAllocator::compatible(
+ const std::vector<uint8_t> &newParams,
+ const std::vector<uint8_t> &oldParams) {
+ size_t newSize = newParams.size();
+ size_t oldSize = oldParams.size();
+
+ // TODO: support not exact matching. e.g) newCapacity < oldCapacity
+ if (newSize == oldSize) {
+ for (size_t i = 0; i < newSize; ++i) {
+ if (newParams[i] != oldParams[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+}
+
+void _C2BufferPoolAllocator::getLinearParams(
+ uint32_t capacity, C2MemoryUsage usage, std::vector<uint8_t> *params) {
+ AllocParams c2Params(usage, capacity);
+ params->assign(c2Params.array, c2Params.array + sizeof(AllocParams));
+}
+
+void _C2BufferPoolAllocator::getGraphicParams(
+ uint32_t width, uint32_t height, uint32_t format, C2MemoryUsage usage,
+ std::vector<uint8_t> *params) {
+ AllocParams c2Params(usage, width, height, format);
+ params->assign(c2Params.array, c2Params.array + sizeof(AllocParams));
+}
+
+c2_status_t _C2BufferPoolAllocator::priorLinearAllocation(
+ const C2Handle *handle,
+ std::shared_ptr<C2LinearAllocation> *c2Allocation) {
+ return mAllocator->priorLinearAllocation(handle, c2Allocation);
+}
+
+c2_status_t _C2BufferPoolAllocator::priorGraphicAllocation(
+ const C2Handle *handle,
+ std::shared_ptr<C2GraphicAllocation> *c2Allocation) {
+ return mAllocator->priorGraphicAllocation(handle, c2Allocation);
+}
+
+class C2PooledBlockPool::Impl {
+public:
+ Impl(const std::shared_ptr<C2Allocator> &allocator)
+ : mInit(C2_OK),
+ mBufferPoolManager(ClientManager::getInstance()),
+ mAllocator(std::make_shared<_C2BufferPoolAllocator>(allocator)) {
+ if (mAllocator && mBufferPoolManager) {
+ if (mBufferPoolManager->create(
+ mAllocator, &mConnectionId) == ResultStatus::OK) {
+ return;
+ }
+ }
+ mInit = C2_NO_INIT;
+ }
+
+ ~Impl() {
+ if (mInit == C2_OK) {
+ mBufferPoolManager->close(mConnectionId);
+ }
+ }
+
+ c2_status_t fetchLinearBlock(
+ uint32_t capacity, C2MemoryUsage usage,
+ std::shared_ptr<C2LinearBlock> *block /* nonnull */) {
+ block->reset();
+ if (mInit != C2_OK) {
+ return mInit;
+ }
+ std::vector<uint8_t> params;
+ mAllocator->getLinearParams(capacity, usage, ¶ms);
+ std::shared_ptr<_C2BlockPoolData> poolData;
+ ResultStatus status = mBufferPoolManager->allocate(
+ mConnectionId, params, &poolData);
+ if (status == ResultStatus::OK) {
+ std::shared_ptr<C2LinearAllocation> alloc;
+ c2_status_t err = mAllocator->priorLinearAllocation(
+ poolData->mHandle, &alloc);
+ if (err == C2_OK && poolData && alloc) {
+ *block = _C2BlockFactory::CreateLinearBlock(
+ alloc, poolData, 0, capacity);
+ if (*block) {
+ return C2_OK;
+ }
+ }
+ return C2_NO_MEMORY;
+ }
+ if (status == ResultStatus::NO_MEMORY) {
+ return C2_NO_MEMORY;
+ }
+ return C2_CORRUPTED;
+ }
+
+private:
+ c2_status_t mInit;
+ const android::sp<ClientManager> mBufferPoolManager;
+ ConnectionId mConnectionId; // locally
+ const std::shared_ptr<_C2BufferPoolAllocator> mAllocator;
+};
+
+C2PooledBlockPool::C2PooledBlockPool(
+ const std::shared_ptr<C2Allocator> &allocator, const local_id_t localId)
+ : mAllocator(allocator), mLocalId(localId), mImpl(new Impl(allocator)) {}
+
+C2PooledBlockPool::~C2PooledBlockPool() {
+}
+
+c2_status_t C2PooledBlockPool::fetchLinearBlock(
+ uint32_t capacity,
+ C2MemoryUsage usage,
+ std::shared_ptr<C2LinearBlock> *block /* nonnull */) {
+ if (mImpl) {
+ return mImpl->fetchLinearBlock(capacity, usage, block);
+ }
+ return C2_CORRUPTED;
+}
+
/* ========================================== 2D BLOCK ========================================= */
/**
@@ -388,7 +647,8 @@
std::shared_ptr<_C2BlockPoolData> mPoolData;
};
-class C2_HIDE _C2MappingBlock2DImpl : public _C2Block2DImpl {
+class C2_HIDE _C2MappingBlock2DImpl
+ : public _C2Block2DImpl, public std::enable_shared_from_this<_C2MappingBlock2DImpl> {
public:
using _C2Block2DImpl::_C2Block2DImpl;
@@ -399,7 +659,7 @@
private:
friend class _C2MappingBlock2DImpl;
- Mapped(const _C2Block2DImpl *impl, bool writable, C2Fence *fence __unused)
+ Mapped(const std::shared_ptr<_C2Block2DImpl> &impl, bool writable, C2Fence *fence __unused)
: mImpl(impl), mWritable(writable) {
memset(mData, 0, sizeof(mData));
const C2Rect crop = mImpl->crop();
@@ -467,7 +727,7 @@
bool writable() const { return mWritable; }
private:
- const _C2Block2DImpl *mImpl;
+ const std::shared_ptr<_C2Block2DImpl> mImpl;
bool mWritable;
c2_status_t mError;
uint8_t *mData[C2PlanarLayout::MAX_NUM_PLANES];
@@ -485,7 +745,7 @@
std::lock_guard<std::mutex> lock(mMappedLock);
std::shared_ptr<Mapped> existing = mMapped.lock();
if (!existing) {
- existing = std::shared_ptr<Mapped>(new Mapped(this, writable, fence));
+ existing = std::shared_ptr<Mapped>(new Mapped(shared_from_this(), writable, fence));
mMapped = existing;
} else {
// if we mapped the region read-only, we cannot remap it read-write
diff --git a/media/libstagefright/codec2/vndk/C2Store.cpp b/media/libstagefright/codec2/vndk/C2Store.cpp
index 216a897..6f752ae 100644
--- a/media/libstagefright/codec2/vndk/C2Store.cpp
+++ b/media/libstagefright/codec2/vndk/C2Store.cpp
@@ -152,6 +152,39 @@
return res;
}
+c2_status_t CreateCodec2BlockPool(
+ C2PlatformAllocatorStore::id_t allocatorId,
+ std::shared_ptr<const C2Component> component,
+ std::shared_ptr<C2BlockPool> *pool) {
+ pool->reset();
+ if (!component) {
+ return C2_BAD_VALUE;
+ }
+ // TODO: support caching block pool along with GetCodec2BlockPool.
+ static std::atomic_int sBlockPoolId(C2BlockPool::PLATFORM_START);
+ std::shared_ptr<C2AllocatorStore> allocatorStore = GetCodec2PlatformAllocatorStore();
+ std::shared_ptr<C2Allocator> allocator;
+ c2_status_t res = C2_NOT_FOUND;
+
+ switch (allocatorId) {
+ case C2PlatformAllocatorStore::ION:
+ res = allocatorStore->fetchAllocator(C2AllocatorStore::DEFAULT_LINEAR, &allocator);
+ if (res == C2_OK) {
+ *pool = std::make_shared<C2PooledBlockPool>(allocator, sBlockPoolId++);
+ if (!*pool) {
+ res = C2_NO_MEMORY;
+ }
+ }
+ break;
+ case C2PlatformAllocatorStore::GRALLOC:
+ // TODO: support gralloc
+ break;
+ default:
+ break;
+ }
+ return res;
+}
+
class C2PlatformComponentStore : public C2ComponentStore {
public:
virtual std::vector<std::shared_ptr<const C2Component::Traits>> listComponents() override;
diff --git a/media/libstagefright/codec2/vndk/include/C2BufferPriv.h b/media/libstagefright/codec2/vndk/include/C2BufferPriv.h
index 977cf7b..211c13a 100644
--- a/media/libstagefright/codec2/vndk/include/C2BufferPriv.h
+++ b/media/libstagefright/codec2/vndk/include/C2BufferPriv.h
@@ -71,4 +71,32 @@
const std::shared_ptr<C2Allocator> mAllocator;
};
+class C2PooledBlockPool : public C2BlockPool {
+public:
+ C2PooledBlockPool(const std::shared_ptr<C2Allocator> &allocator, const local_id_t localId);
+
+ virtual ~C2PooledBlockPool() override;
+
+ virtual C2Allocator::id_t getAllocatorId() const override {
+ return mAllocator->getId();
+ }
+
+ virtual local_id_t getLocalId() const override {
+ return mLocalId;
+ }
+
+ virtual c2_status_t fetchLinearBlock(
+ uint32_t capacity,
+ C2MemoryUsage usage,
+ std::shared_ptr<C2LinearBlock> *block /* nonnull */) override;
+
+ // TODO: fetchGraphicBlock, fetchCircularBlock
+private:
+ const std::shared_ptr<C2Allocator> mAllocator;
+ const local_id_t mLocalId;
+
+ class Impl;
+ std::unique_ptr<Impl> mImpl;
+};
+
#endif // STAGEFRIGHT_CODEC2_BUFFER_PRIV_H_
diff --git a/media/libstagefright/codec2/vndk/include/C2PlatformSupport.h b/media/libstagefright/codec2/vndk/include/C2PlatformSupport.h
index afa51ee..211ee0c 100644
--- a/media/libstagefright/codec2/vndk/include/C2PlatformSupport.h
+++ b/media/libstagefright/codec2/vndk/include/C2PlatformSupport.h
@@ -82,6 +82,23 @@
std::shared_ptr<C2BlockPool> *pool);
/**
+ * Creates a block pool.
+ * \param allocatorId the allocator ID which is used to allocate blocks
+ * \param component the component using the block pool (must be non-null)
+ * \param pool pointer to where the created block pool shall be store on success.
+ * nullptr will be stored here on failure
+ *
+ * \retval C2_OK the operation was successful
+ * \retval C2_BAD_VALUE the component is null
+ * \retval C2_NOT_FOUND if the allocator does not exist
+ * \retval C2_NO_MEMORY not enough memory to create a block pool
+ */
+c2_status_t CreateCodec2BlockPool(
+ C2PlatformAllocatorStore::id_t allocatorId,
+ std::shared_ptr<const C2Component> component,
+ std::shared_ptr<C2BlockPool> *pool);
+
+/**
* Returns the platform component store.
* \retval nullptr if the platform component store could not be obtained
*/
diff --git a/media/libstagefright/codecs/aacdec/C2SoftAac.cpp b/media/libstagefright/codecs/aacdec/C2SoftAac.cpp
index c82ea45..6bd15a5 100644
--- a/media/libstagefright/codecs/aacdec/C2SoftAac.cpp
+++ b/media/libstagefright/codecs/aacdec/C2SoftAac.cpp
@@ -305,37 +305,57 @@
ALOGV("getting %d from ringbuffer", numSamples);
std::shared_ptr<C2LinearBlock> block;
- C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
- // TODO: error handling, proper usage, etc.
- c2_status_t err = pool->fetchLinearBlock(numSamples * sizeof(int16_t), usage, &block);
- if (err != C2_OK) {
- ALOGE("err = %d", err);
- }
+ std::function<void(const std::unique_ptr<C2Work>&)> fillWork =
+ [&block, numSamples, pool, this]()
+ -> std::function<void(const std::unique_ptr<C2Work>&)> {
+ auto fillEmptyWork = [](const std::unique_ptr<C2Work> &work, c2_status_t err) {
+ work->result = err;
+ work->worklets.front()->output.flags = work->input.flags;
+ work->worklets.front()->output.buffers.clear();
+ work->worklets.front()->output.ordinal = work->input.ordinal;
+ work->workletsProcessed = 1u;
+ };
- C2WriteView wView = block->map().get();
- // TODO
- INT_PCM *outBuffer = reinterpret_cast<INT_PCM *>(wView.data());
- int32_t ns = outputDelayRingBufferGetSamples(outBuffer, numSamples);
- if (ns != numSamples) {
- ALOGE("not a complete frame of samples available");
- mSignalledError = true;
- // TODO: notify(OMX_EventError, OMX_ErrorUndefined, 0, NULL);
- return;
- }
- auto fillWork = [buffer = createLinearBuffer(block)](const std::unique_ptr<C2Work> &work) {
- work->worklets.front()->output.flags = work->input.flags;
- work->worklets.front()->output.buffers.clear();
- work->worklets.front()->output.buffers.push_back(buffer);
- work->worklets.front()->output.ordinal = work->input.ordinal;
- work->workletsProcessed = 1u;
- };
+ using namespace std::placeholders;
+ if (numSamples == 0) {
+ return std::bind(fillEmptyWork, _1, C2_OK);
+ }
+
+ // TODO: error handling, proper usage, etc.
+ C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
+ c2_status_t err = pool->fetchLinearBlock(
+ numSamples * sizeof(int16_t), usage, &block);
+ if (err != C2_OK) {
+ ALOGD("failed to fetch a linear block (%d)", err);
+ mSignalledError = true;
+ return std::bind(fillEmptyWork, _1, C2_NO_MEMORY);
+ }
+ C2WriteView wView = block->map().get();
+ // TODO
+ INT_PCM *outBuffer = reinterpret_cast<INT_PCM *>(wView.data());
+ int32_t ns = outputDelayRingBufferGetSamples(outBuffer, numSamples);
+ if (ns != numSamples) {
+ ALOGE("not a complete frame of samples available");
+ mSignalledError = true;
+ return std::bind(fillEmptyWork, _1, C2_CORRUPTED);
+ }
+ return [buffer = createLinearBuffer(block)](const std::unique_ptr<C2Work> &work) {
+ work->result = C2_OK;
+ work->worklets.front()->output.flags = work->input.flags;
+ work->worklets.front()->output.buffers.clear();
+ work->worklets.front()->output.buffers.push_back(buffer);
+ work->worklets.front()->output.ordinal = work->input.ordinal;
+ work->workletsProcessed = 1u;
+ };
+ }();
+
if (work && work->input.ordinal.frameIndex == c2_cntr64_t(outInfo.frameIndex)) {
fillWork(work);
} else {
finish(outInfo.frameIndex, fillWork);
}
- ALOGV("out timestamp %" PRIu64 " / %u", outInfo.timestamp, block->capacity());
+ ALOGV("out timestamp %" PRIu64 " / %u", outInfo.timestamp, block ? block->capacity() : 0);
mBuffersInfo.pop_front();
}
}
diff --git a/media/libstagefright/codecs/amrnb/enc/C2SoftAmrNbEnc.cpp b/media/libstagefright/codecs/amrnb/enc/C2SoftAmrNbEnc.cpp
index 4dd0309..406d1ca 100644
--- a/media/libstagefright/codecs/amrnb/enc/C2SoftAmrNbEnc.cpp
+++ b/media/libstagefright/codecs/amrnb/enc/C2SoftAmrNbEnc.cpp
@@ -115,7 +115,7 @@
return;
}
- const C2ConstLinearBlock &inBuffer = work->input.buffers[0]->data().linearBlocks().front();
+ const C2ConstLinearBlock inBuffer = work->input.buffers[0]->data().linearBlocks().front();
bool eos = ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0);
size_t inOffset = inBuffer.offset();
size_t inSize = inBuffer.size();
diff --git a/media/libstagefright/codecs/avcdec/C2SoftAvcDec.cpp b/media/libstagefright/codecs/avcdec/C2SoftAvcDec.cpp
index b9ba251..1fc2c8c 100644
--- a/media/libstagefright/codecs/avcdec/C2SoftAvcDec.cpp
+++ b/media/libstagefright/codecs/avcdec/C2SoftAvcDec.cpp
@@ -37,15 +37,6 @@
#include "ih264d_defs.h"
-namespace {
-
-template <class T>
-inline int32_t floor32(T arg) {
- return (int32_t) std::llround(std::floor(arg));
-}
-
-} // namespace
-
namespace android {
struct iv_obj_t : public ::iv_obj_t {};
@@ -83,138 +74,19 @@
.build();
}
-#if 0
-using SupportedValuesWithFields = C2SoftAvcDecIntf::SupportedValuesWithFields;
-
-struct ValidateParam {
- explicit ValidateParam(
- const std::map<C2ParamField, SupportedValuesWithFields> &supportedValues)
- : mSupportedValues(supportedValues) {}
-
- template <class T, bool SIGNED = std::is_signed<T>::value, size_t SIZE = sizeof(T)>
- struct Getter {
- static T get(const C2Value::Primitive &) {
- static_assert(!std::is_arithmetic<T>::value, "non-arithmetic type");
- static_assert(!std::is_floating_point<T>::value || std::is_same<T, float>::value,
- "float is the only supported floating point type");
- static_assert(sizeof(T) <= 8, "type exceeds 64-bit");
+void CopyPlane(
+ uint8_t *dst, const C2PlaneInfo &plane,
+ const uint8_t *src, uint32_t width, uint32_t height) {
+ for (uint32_t row = 0; row < height; ++row) {
+ for (uint32_t col = 0; col < width; ++col) {
+ *dst = *src;
+ dst += plane.colInc;
+ ++src;
}
- };
-
- template <class T>
- bool validateField(
- const C2FieldSupportedValues &supportedValues, const T &value) {
- switch (supportedValues.type) {
- case C2FieldSupportedValues::EMPTY:
- {
- return false;
- }
- case C2FieldSupportedValues::RANGE:
- {
- // TODO: handle step, num, denom
- return Getter<T>::get(supportedValues.range.min) <= value
- && value <= Getter<T>::get(supportedValues.range.max);
- }
- case C2FieldSupportedValues::VALUES:
- {
- for (const auto &val : supportedValues.values) {
- if (Getter<T>::get(val) == value) {
- return true;
- }
- }
- return false;
- }
- case C2FieldSupportedValues::FLAGS:
- // TODO
- return false;
- }
- return false;
+ dst -= plane.colInc * width;
+ dst += plane.rowInc;
}
-
-protected:
- const std::map<C2ParamField, SupportedValuesWithFields> &mSupportedValues;
-};
-
-template <>
-struct ValidateParam::Getter<float> {
- static float get(const C2Value::Primitive &value) { return value.fp; }
-};
-template <class T>
-struct ValidateParam::Getter<T, true, 8u> {
- static int64_t get(const C2Value::Primitive &value) { return value.i64; }
-};
-template <class T>
-struct ValidateParam::Getter<T, true, 4u> {
- static int32_t get(const C2Value::Primitive &value) { return value.i32; }
-};
-template <class T>
-struct ValidateParam::Getter<T, false, 8u> {
- static uint64_t get(const C2Value::Primitive &value) { return value.u64; }
-};
-template <class T>
-struct ValidateParam::Getter<T, false, 4u> {
- static uint32_t get(const C2Value::Primitive &value) { return value.u32; }
-};
-
-template <class T>
-struct ValidateSimpleParam : public ValidateParam {
- explicit ValidateSimpleParam(
- const std::map<C2ParamField, SupportedValuesWithFields> &supportedValues)
- : ValidateParam(supportedValues) {}
-
- std::unique_ptr<C2SettingResult> operator() (C2Param *c2param) {
- T* param = (T*)c2param;
- C2ParamField field(param, &T::value);
- const C2FieldSupportedValues &supportedValues = mSupportedValues.at(field).supported;
- if (!validateField(supportedValues, param->value)) {
- return std::unique_ptr<C2SettingResult>(
- new C2SettingResult {C2SettingResult::BAD_VALUE, {field, nullptr}, {}});
- }
- return nullptr;
- }
-};
-
-template <class T>
-struct ValidateVideoSize : public ValidateParam {
- explicit ValidateVideoSize(
- const std::map<C2ParamField, SupportedValuesWithFields> &supportedValues)
- : ValidateParam(supportedValues) {}
-
- std::unique_ptr<C2SettingResult> operator() (C2Param *c2param) {
- T* param = (T*)c2param;
- C2ParamField field(param, &T::width);
- const C2FieldSupportedValues &supportedWidth = mSupportedValues.at(field).supported;
- if (!validateField(supportedWidth, param->width)) {
- return std::unique_ptr<C2SettingResult>(
- new C2SettingResult {C2SettingResult::BAD_VALUE, {field, nullptr}, {}});
- }
- field = C2ParamField(param, &T::height);
- const C2FieldSupportedValues &supportedHeight = mSupportedValues.at(field).supported;
- if (!validateField(supportedHeight, param->height)) {
- return std::unique_ptr<C2SettingResult>(
- new C2SettingResult {C2SettingResult::BAD_VALUE, {field, nullptr}, {}});
- }
- return nullptr;
- }
-};
-
-template <class T>
-struct ValidateCString {
- explicit ValidateCString(const char *expected) : mExpected(expected) {}
-
- std::unique_ptr<C2SettingResult> operator() (C2Param *c2param) {
- T* param = (T*)c2param;
- if (strncmp(param->m.value, mExpected, param->flexCount()) != 0) {
- return std::unique_ptr<C2SettingResult>(
- new C2SettingResult {C2SettingResult::BAD_VALUE, {C2ParamField(param, &T::m), nullptr}, {}});
- }
- return nullptr;
- }
-
-private:
- const char *mExpected;
-};
-#endif
+}
void fillEmptyWork(const std::unique_ptr<C2Work> &work) {
uint32_t flags = 0;
@@ -229,398 +101,6 @@
} // namespace
-#if 0
-#define CASE(member) \
- case decltype(component->member)::CORE_INDEX: \
- return std::unique_ptr<C2StructDescriptor>(new C2StructDescriptor( \
- static_cast<decltype(component->member) *>(nullptr)))
-
-class C2SoftAvcDecIntf::ParamReflector : public C2ParamReflector {
-public:
- virtual std::unique_ptr<C2StructDescriptor> describe(C2Param::CoreIndex coreIndex) override {
- constexpr C2SoftAvcDecIntf *component = nullptr;
- switch (coreIndex.coreIndex()) {
- CASE(mDomainInfo);
- CASE(mInputStreamCount);
- CASE(mInputStreamFormat);
- // Output counterparts for the above would be redundant.
- CASE(mVideoSize);
- CASE(mMaxVideoSizeHint);
-
- // port mime configs are stored as unique_ptr.
- case C2PortMimeConfig::CORE_INDEX:
- return std::unique_ptr<C2StructDescriptor>(new C2StructDescriptor(
- static_cast<C2PortMimeConfig *>(nullptr)));
- }
- return nullptr;
- }
-};
-#undef CASE
-
-// static const CodecProfileLevel kProfileLevels[] = {
-// { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel52 },
-// { OMX_VIDEO_AVCProfileMain, OMX_VIDEO_AVCLevel52 },
-// { OMX_VIDEO_AVCProfileHigh, OMX_VIDEO_AVCLevel52 },
-// };
-C2SoftAvcDecIntf::C2SoftAvcDecIntf(const char *name, c2_node_id_t id)
- : mName(name),
- mId(id),
- mDomainInfo(C2DomainVideo),
- mInputStreamCount(1u),
- mOutputStreamCount(1u),
- mInputStreamFormat(0u, C2FormatCompressed),
- mOutputStreamFormat(0u, C2FormatVideo),
- mProfile(0u, kAvcProfileUnknown),
- mLevel(0u, kAvcLevelUnknown),
- mBlockSize(0u),
- mAlignment(0u),
- mFrameRate(0u, 0),
- mBlocksPerSecond(0u, 0),
- mParamReflector(new ParamReflector) {
- ALOGV("in %s", __func__);
- mInputPortMime = C2PortMimeConfig::input::AllocUnique(strlen(CODEC_MIME_TYPE) + 1);
- strcpy(mInputPortMime->m.value, CODEC_MIME_TYPE);
- mOutputPortMime = C2PortMimeConfig::output::AllocUnique(strlen(MEDIA_MIMETYPE_VIDEO_RAW) + 1);
- strcpy(mOutputPortMime->m.value, MEDIA_MIMETYPE_VIDEO_RAW);
-
- mVideoSize.width = 320;
- mVideoSize.height = 240;
- mBlockSize.width = 16;
- mBlockSize.height = 16;
- mAlignment.width = 2;
- mAlignment.height = 2;
-
- mMaxVideoSizeHint.width = H264_MAX_FRAME_WIDTH;
- mMaxVideoSizeHint.height = H264_MAX_FRAME_HEIGHT;
-
- mOutputBlockPools = C2PortBlockPoolsTuning::output::AllocUnique({});
-
- auto insertParam = [¶ms = mParams] (C2Param *param) {
- params[param->index()] = param;
- };
-
- auto markReadOnly = [&supported = mSupportedValues] (auto *param) {
- supported.emplace(
- C2ParamField(param, &std::remove_pointer<decltype(param)>::type::value),
- C2FieldSupportedValues(false /* flags */, {}));
- };
-
- auto markReadOnlyVideoSize = [&supported = mSupportedValues] (auto *param) {
- supported.emplace(
- C2ParamField(param, &std::remove_pointer<decltype(param)>::type::width),
- C2FieldSupportedValues(false /* flags */, {}));
- supported.emplace(
- C2ParamField(param, &std::remove_pointer<decltype(param)>::type::height),
- C2FieldSupportedValues(false /* flags */, {}));
- };
-
- insertParam(&mDomainInfo);
- markReadOnly(&mDomainInfo);
- mFieldVerifiers[mDomainInfo.index()] =
- ValidateSimpleParam<decltype(mDomainInfo)>(mSupportedValues);
-
- insertParam(mInputPortMime.get());
- mFieldVerifiers[mInputPortMime->index()] =
- ValidateCString<std::remove_reference<decltype(*mInputPortMime)>::type>(CODEC_MIME_TYPE);
-
- insertParam(&mInputStreamCount);
- markReadOnly(&mInputStreamCount);
- mFieldVerifiers[mInputStreamCount.index()] =
- ValidateSimpleParam<decltype(mInputStreamCount)>(mSupportedValues);
-
- insertParam(mOutputPortMime.get());
- mFieldVerifiers[mOutputPortMime->index()] =
- ValidateCString<std::remove_reference<decltype(*mOutputPortMime)>::type>(MEDIA_MIMETYPE_VIDEO_RAW);
-
- insertParam(&mOutputStreamCount);
- markReadOnly(&mOutputStreamCount);
- mFieldVerifiers[mOutputStreamCount.index()] =
- ValidateSimpleParam<decltype(mOutputStreamCount)>(mSupportedValues);
-
- insertParam(&mInputStreamFormat);
- markReadOnly(&mInputStreamFormat);
- mFieldVerifiers[mInputStreamFormat.index()] =
- ValidateSimpleParam<decltype(mInputStreamFormat)>(mSupportedValues);
-
- insertParam(&mOutputStreamFormat);
- markReadOnly(&mOutputStreamFormat);
- mFieldVerifiers[mOutputStreamFormat.index()] =
- ValidateSimpleParam<decltype(mOutputStreamFormat)>(mSupportedValues);
-
- insertParam(&mVideoSize);
- markReadOnlyVideoSize(&mVideoSize);
- mFieldVerifiers[mVideoSize.index()] =
- ValidateVideoSize<decltype(mVideoSize)>(mSupportedValues);
-
- insertParam(&mMaxVideoSizeHint);
- mSupportedValues.emplace(
- C2ParamField(&mMaxVideoSizeHint, &C2MaxVideoSizeHintPortSetting::width),
- C2FieldSupportedValues(H264_MIN_FRAME_WIDTH, H264_MAX_FRAME_WIDTH, mAlignment.width));
- mSupportedValues.emplace(
- C2ParamField(&mMaxVideoSizeHint, &C2MaxVideoSizeHintPortSetting::height),
- C2FieldSupportedValues(H264_MIN_FRAME_HEIGHT, H264_MAX_FRAME_HEIGHT, mAlignment.height));
- mFieldVerifiers[mMaxVideoSizeHint.index()] =
- ValidateVideoSize<decltype(mMaxVideoSizeHint)>(mSupportedValues);
-
- insertParam(&mProfile);
- mSupportedValues.emplace(
- C2ParamField(&mProfile, &C2AvcProfileInfo::value),
- C2FieldSupportedValues(false /* flags */, {
- kAvcProfileUnknown,
- kAvcProfileBaseline,
- kAvcProfileMain,
- kAvcProfileHigh,
- }));
- mFieldVerifiers[mProfile.index()] =
- ValidateSimpleParam<decltype(mProfile)>(mSupportedValues);
-
- insertParam(&mLevel);
- mSupportedValues.emplace(
- C2ParamField(&mLevel, &C2AvcLevelInfo::value),
- C2FieldSupportedValues(false /* flags */, {
- kAvcLevelUnknown,
- kAvcLevel10,
- kAvcLevel1b,
- kAvcLevel11,
- kAvcLevel12,
- kAvcLevel13,
- kAvcLevel20,
- kAvcLevel21,
- kAvcLevel22,
- kAvcLevel30,
- kAvcLevel31,
- kAvcLevel32,
- kAvcLevel40,
- kAvcLevel41,
- kAvcLevel42,
- kAvcLevel50,
- kAvcLevel51,
- kAvcLevel52,
- }));
- mFieldVerifiers[mLevel.index()] =
- ValidateSimpleParam<decltype(mLevel)>(mSupportedValues);
-
- insertParam(&mBlockSize);
- markReadOnlyVideoSize(&mBlockSize);
- mFieldVerifiers[mBlockSize.index()] =
- ValidateVideoSize<decltype(mBlockSize)>(mSupportedValues);
-
- insertParam(&mAlignment);
- markReadOnlyVideoSize(&mAlignment);
- mFieldVerifiers[mAlignment.index()] =
- ValidateVideoSize<decltype(mAlignment)>(mSupportedValues);
-
- insertParam(&mFrameRate);
- mSupportedValues.emplace(
- C2ParamField(&mFrameRate, &C2FrameRateInfo::value),
- C2FieldSupportedValues(0, 240));
- mFieldVerifiers[mFrameRate.index()] =
- ValidateSimpleParam<decltype(mFrameRate)>(mSupportedValues);
-
- insertParam(&mBlocksPerSecond);
- mSupportedValues.emplace(
- C2ParamField(&mFrameRate, &C2BlocksPerSecondInfo::value),
- C2FieldSupportedValues(0, 244800));
- mFieldVerifiers[mBlocksPerSecond.index()] =
- ValidateSimpleParam<decltype(mBlocksPerSecond)>(mSupportedValues);
-
- mParamDescs.push_back(std::make_shared<C2ParamDescriptor>(
- true, "_domain", &mDomainInfo));
- mParamDescs.push_back(std::make_shared<C2ParamDescriptor>(
- true, "_input_port_mime", mInputPortMime.get()));
- mParamDescs.push_back(std::make_shared<C2ParamDescriptor>(
- true, "_input_stream_count", &mInputStreamCount));
- mParamDescs.push_back(std::make_shared<C2ParamDescriptor>(
- true, "_output_port_mime", mOutputPortMime.get()));
- mParamDescs.push_back(std::make_shared<C2ParamDescriptor>(
- true, "_output_stream_count", &mOutputStreamCount));
- mParamDescs.push_back(std::make_shared<C2ParamDescriptor>(
- true, "_input_stream_format", &mInputStreamFormat));
- mParamDescs.push_back(std::make_shared<C2ParamDescriptor>(
- true, "_output_stream_format", &mOutputStreamFormat));
- mParamDescs.push_back(std::make_shared<C2ParamDescriptor>(
- false, "_video_size", &mVideoSize));
- mParamDescs.push_back(std::make_shared<C2ParamDescriptor>(
- false, "_max_video_size_hint", &mMaxVideoSizeHint));
- mParamDescs.push_back(std::make_shared<C2ParamDescriptor>(
- false, "_output_block_pools", mOutputBlockPools.get()));
-}
-
-C2SoftAvcDecIntf::~C2SoftAvcDecIntf() {
- ALOGV("in %s", __func__);
-}
-
-C2String C2SoftAvcDecIntf::getName() const {
- return mName;
-}
-
-c2_node_id_t C2SoftAvcDecIntf::getId() const {
- return mId;
-}
-
-c2_status_t C2SoftAvcDecIntf::query_vb(
- const std::vector<C2Param*> & stackParams,
- const std::vector<C2Param::Index> & heapParamIndices,
- c2_blocking_t mayBlock,
- std::vector<std::unique_ptr<C2Param>>* const heapParams) const {
- (void)mayBlock;
- for (C2Param* const param : stackParams) {
- if (!*param) {
- continue;
- }
-
- uint32_t index = param->index();
- if (!mParams.count(index)) {
- // TODO: add support for output-block-pools (this will be done when we move all
- // config to shared ptr)
- continue;
- }
-
- C2Param *myParam = mParams.find(index)->second;
- if (myParam->size() != param->size()) {
- param->invalidate();
- continue;
- }
-
- param->updateFrom(*myParam);
- }
-
- for (const C2Param::Index index : heapParamIndices) {
- if (mParams.count(index)) {
- C2Param *myParam = mParams.find(index)->second;
- heapParams->emplace_back(C2Param::Copy(*myParam));
- }
- }
-
- return C2_OK;
-}
-
-c2_status_t C2SoftAvcDecIntf::config_vb(
- const std::vector<C2Param*> ¶ms,
- c2_blocking_t mayBlock,
- std::vector<std::unique_ptr<C2SettingResult>>* const failures) {
- (void)mayBlock;
- c2_status_t err = C2_OK;
- for (C2Param *param : params) {
- uint32_t index = param->index();
- if (param->index() == mOutputBlockPools.get()->index()) {
- // setting output block pools
- mOutputBlockPools.reset(
- (C2PortBlockPoolsTuning::output *)C2Param::Copy(*param).release());
- continue;
- }
-
- if (mParams.count(index) == 0) {
- // We can't create C2SettingResult with no field, so just skipping in this case.
- err = C2_BAD_INDEX;
- continue;
- }
- C2Param *myParam = mParams.find(index)->second;
- std::unique_ptr<C2SettingResult> result;
- if (!(result = mFieldVerifiers[index](param))) {
- myParam->updateFrom(*param);
- updateSupportedValues();
- } else {
- failures->push_back(std::move(result));
- err = C2_BAD_VALUE;
- }
- }
- return err;
-}
-
-c2_status_t C2SoftAvcDecIntf::createTunnel_sm(c2_node_id_t targetComponent) {
- // Tunneling is not supported
- (void) targetComponent;
- return C2_OMITTED;
-}
-
-c2_status_t C2SoftAvcDecIntf::releaseTunnel_sm(c2_node_id_t targetComponent) {
- // Tunneling is not supported
- (void) targetComponent;
- return C2_OMITTED;
-}
-
-std::shared_ptr<C2ParamReflector> C2SoftAvcDecIntf::getParamReflector() const {
- return mParamReflector;
-}
-
-c2_status_t C2SoftAvcDecIntf::querySupportedParams_nb(
- std::vector<std::shared_ptr<C2ParamDescriptor>> * const params) const {
- params->insert(params->begin(), mParamDescs.begin(), mParamDescs.end());
- return C2_OK;
-}
-
-c2_status_t C2SoftAvcDecIntf::querySupportedValues_vb(
- std::vector<C2FieldSupportedValuesQuery> &fields, c2_blocking_t mayBlock) const {
- (void)mayBlock;
- c2_status_t res = C2_OK;
- for (C2FieldSupportedValuesQuery &query : fields) {
- if (mSupportedValues.count(query.field) == 0) {
- query.status = C2_BAD_INDEX;
- res = C2_BAD_INDEX;
- } else {
- query.status = C2_OK;
- query.values = mSupportedValues.at(query.field).supported;
- }
- }
- return res;
-}
-
-void C2SoftAvcDecIntf::updateSupportedValues() {
- int32_t maxWidth = H264_MAX_FRAME_WIDTH;
- int32_t maxHeight = H264_MAX_FRAME_HEIGHT;
- // cf: Rec. ITU-T H.264 A.3
- int maxFrameRate = 172;
- std::vector<C2ParamField> fields;
- if (mLevel.value != kAvcLevelUnknown) {
- // cf: Rec. ITU-T H.264 Table A-1
- constexpr int MaxFS[] = {
- // 0 1 2 3 4 5 6 7 8 9
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 99,
- 99, 396, 396, 396, 0, 0, 0, 0, 0, 0,
- 396, 792, 1620, 0, 0, 0, 0, 0, 0, 0,
- 1620, 3600, 5120, 0, 0, 0, 0, 0, 0, 0,
- 8192, 8192, 8704, 0, 0, 0, 0, 0, 0, 0,
- 22080, 36864, 36864,
- };
- constexpr int MaxMBPS[] = {
- // 0 1 2 3 4 5 6 7 8 9
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 1485,
- 1485, 3000, 6000, 11880, 0, 0, 0, 0, 0, 0,
- 11880, 19800, 20250, 0, 0, 0, 0, 0, 0, 0,
- 40500, 108000, 216000, 0, 0, 0, 0, 0, 0, 0,
- 245760, 245760, 522240, 0, 0, 0, 0, 0, 0, 0,
- 589824, 983040, 2073600,
- };
-
- // cf: Rec. ITU-T H.264 A.3.1
- maxWidth = std::min(maxWidth, floor32(std::sqrt(MaxFS[mLevel.value] * 8)) * MB_SIZE);
- maxHeight = std::min(maxHeight, floor32(std::sqrt(MaxFS[mLevel.value] * 8)) * MB_SIZE);
- int32_t MBs = ((mVideoSize.width + 15) / 16) * ((mVideoSize.height + 15) / 16);
- maxFrameRate = std::min(maxFrameRate, MaxMBPS[mLevel.value] / MBs);
- fields.push_back(C2ParamField(&mLevel, &C2AvcLevelInfo::value));
- }
-
- SupportedValuesWithFields &maxWidthVals = mSupportedValues.at(
- C2ParamField(&mMaxVideoSizeHint, &C2MaxVideoSizeHintPortSetting::width));
- maxWidthVals.supported.range.max = maxWidth;
- maxWidthVals.restrictingFields.clear();
- maxWidthVals.restrictingFields.insert(fields.begin(), fields.end());
-
- SupportedValuesWithFields &maxHeightVals = mSupportedValues.at(
- C2ParamField(&mMaxVideoSizeHint, &C2MaxVideoSizeHintPortSetting::height));
- maxHeightVals.supported.range.max = maxHeight;
- maxHeightVals.restrictingFields.clear();
- maxHeightVals.restrictingFields.insert(fields.begin(), fields.end());
-
- SupportedValuesWithFields &frameRate = mSupportedValues.at(
- C2ParamField(&mFrameRate, &C2FrameRateInfo::value));
- frameRate.supported.range.max = maxFrameRate;
- frameRate.restrictingFields.clear();
- frameRate.restrictingFields.insert(fields.begin(), fields.end());
-}
-#endif
-
///////////////////////////////////////////////////////////////////////////////
C2SoftAvcDec::C2SoftAvcDec(
@@ -628,7 +108,7 @@
c2_node_id_t id)
: SimpleC2Component(BuildIntf(name, id)),
mCodecCtx(NULL),
- mFlushOutBuffer(NULL),
+ mOutBuffer(NULL),
mIvColorFormat(IV_YUV_420P),
mChangingResolution(false),
mSignalledError(false),
@@ -654,6 +134,7 @@
}
c2_status_t C2SoftAvcDec::onStop() {
+ ALOGV("onStop");
mSignalledError = false;
resetDecoder();
resetPlugin();
@@ -662,6 +143,7 @@
}
void C2SoftAvcDec::onReset() {
+ ALOGV("onReset");
(void)onStop();
}
@@ -672,17 +154,6 @@
c2_status_t C2SoftAvcDec::onFlush_sm() {
setFlushMode();
- /* Allocate a picture buffer to flushed data */
- uint32_t displayStride = mWidth;
- uint32_t displayHeight = mHeight;
-
- uint32_t bufferSize = displayStride * displayHeight * 3 / 2;
- mFlushOutBuffer = (uint8_t *)memalign(128, bufferSize);
- if (NULL == mFlushOutBuffer) {
- ALOGE("Could not allocate flushOutputBuffer of size %u", bufferSize);
- return C2_NO_MEMORY;
- }
-
while (true) {
ivd_video_decode_ip_t s_dec_ip;
ivd_video_decode_op_t s_dec_op;
@@ -698,9 +169,9 @@
}
}
- if (mFlushOutBuffer) {
- free(mFlushOutBuffer);
- mFlushOutBuffer = NULL;
+ if (mOutBuffer) {
+ free(mOutBuffer);
+ mOutBuffer = NULL;
}
return C2_OK;
}
@@ -778,6 +249,16 @@
return UNKNOWN_ERROR;
}
+
+ if (mOutBuffer != NULL) {
+ free(mOutBuffer);
+ }
+ uint32_t bufferSize = mWidth * mHeight * 3 / 2;
+ mOutBuffer = (uint8_t *)memalign(128, bufferSize);
+ if (NULL == mOutBuffer) {
+ ALOGE("Could not allocate output buffer of size %u", bufferSize);
+ return C2_NO_MEMORY;
+ }
return OK;
}
@@ -854,6 +335,7 @@
s_video_flush_op.u4_error_code);
return UNKNOWN_ERROR;
}
+
return OK;
}
@@ -993,6 +475,7 @@
ps_dec_ip->u4_size = sizeof(ivd_video_decode_ip_t);
ps_dec_op->u4_size = sizeof(ivd_video_decode_op_t);
+ ps_dec_op->u4_output_present = 0;
ps_dec_ip->e_cmd = IVD_CMD_VIDEO_DECODE;
@@ -1020,14 +503,29 @@
outBuffer->width(), outBuffer->height(), width, height);
return false;
}
+ ALOGV("width = %u, stride[0] = %u, stride[1] = %u, stride[2] = %u",
+ outBuffer->width(),
+ outBuffer->layout().planes[0].rowInc,
+ outBuffer->layout().planes[1].rowInc,
+ outBuffer->layout().planes[2].rowInc);
+ const C2PlanarLayout &layout = outBuffer->layout();
ps_dec_ip->s_out_buffer.pu1_bufs[0] = outBuffer->data()[0];
+ if (layout.planes[0].rowInc != (int32_t)mWidth || layout.planes[1].colInc != 1) {
+ ps_dec_ip->s_out_buffer.pu1_bufs[0] = mOutBuffer;
+ }
ps_dec_ip->s_out_buffer.pu1_bufs[1] = outBuffer->data()[1];
+ if (layout.planes[1].rowInc != (int32_t)mWidth / 2 || layout.planes[1].colInc != 1) {
+ ps_dec_ip->s_out_buffer.pu1_bufs[1] = mOutBuffer + sizeY;
+ }
ps_dec_ip->s_out_buffer.pu1_bufs[2] = outBuffer->data()[2];
+ if (layout.planes[2].rowInc != (int32_t)mWidth / 2 || layout.planes[2].colInc != 1) {
+ ps_dec_ip->s_out_buffer.pu1_bufs[2] = mOutBuffer + sizeY + sizeUV;
+ }
} else {
- // mFlushOutBuffer always has the right size.
- ps_dec_ip->s_out_buffer.pu1_bufs[0] = mFlushOutBuffer;
- ps_dec_ip->s_out_buffer.pu1_bufs[1] = mFlushOutBuffer + sizeY;
- ps_dec_ip->s_out_buffer.pu1_bufs[2] = mFlushOutBuffer + sizeY + sizeUV;
+ // mOutBuffer always has the right size.
+ ps_dec_ip->s_out_buffer.pu1_bufs[0] = mOutBuffer;
+ ps_dec_ip->s_out_buffer.pu1_bufs[1] = mOutBuffer + sizeY;
+ ps_dec_ip->s_out_buffer.pu1_bufs[2] = mOutBuffer + sizeY + sizeUV;
}
ps_dec_ip->s_out_buffer.u4_num_bufs = 3;
@@ -1065,7 +563,8 @@
}
void C2SoftAvcDec::finishWork(uint64_t index, const std::unique_ptr<C2Work> &work) {
- std::shared_ptr<C2Buffer> buffer = createGraphicBuffer(std::move(mAllocatedBlock));
+ std::shared_ptr<C2Buffer> buffer = createGraphicBuffer(mAllocatedBlock);
+ mAllocatedBlock.reset();
auto fillWork = [buffer](const std::unique_ptr<C2Work> &work) {
uint32_t flags = 0;
if (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) {
@@ -1093,9 +592,9 @@
work->result = C2_OK;
work->workletsProcessed = 0u;
- const C2ConstLinearBlock &buffer =
+ const C2ConstLinearBlock buffer =
work->input.buffers[0]->data().linearBlocks().front();
- if (buffer.capacity() == 0) {
+ if (buffer.size() == 0) {
ALOGV("empty input: %llu", work->input.ordinal.frameIndex.peekull());
// TODO: result?
fillEmptyWork(work);
@@ -1109,6 +608,13 @@
}
C2ReadView input = work->input.buffers[0]->data().linearBlocks().front().map().get();
+ if (input.error() != C2_OK) {
+ work->result = input.error();
+ fillEmptyWork(work);
+ ALOGD("map error: %d", input.error());
+ return;
+ }
+ ALOGV("buffer.size() = %u, input.capacity() = %u", buffer.size(), input.capacity());
uint32_t workIndex = work->input.ordinal.frameIndex.peeku() & 0xFFFFFFFF;
size_t inOffset = 0u;
@@ -1117,34 +623,54 @@
break;
}
(void)ensureDecoderState(pool);
- C2GraphicView output = mAllocatedBlock->map().get();
- if (output.error() != OK) {
- ALOGE("mapped err = %d", output.error());
- }
-
ivd_video_decode_ip_t s_dec_ip;
ivd_video_decode_op_t s_dec_op;
WORD32 timeDelay, timeTaken;
//size_t sizeY, sizeUV;
- if (!setDecodeArgs(&s_dec_ip, &s_dec_op, &input, &output, workIndex, inOffset)) {
- ALOGE("Decoder arg setup failed");
- // TODO: notify(OMX_EventError, OMX_ErrorUndefined, 0, NULL);
- mSignalledError = true;
- break;
+ {
+ C2GraphicView output = mAllocatedBlock->map().get();
+ if (output.error() != C2_OK) {
+ ALOGE("mapped err = %d", output.error());
+ work->result = output.error();
+ fillEmptyWork(work);
+ return;
+ }
+ if (!setDecodeArgs(&s_dec_ip, &s_dec_op, &input, &output, workIndex, inOffset)) {
+ ALOGE("Decoder arg setup failed");
+ // TODO: notify(OMX_EventError, OMX_ErrorUndefined, 0, NULL);
+ mSignalledError = true;
+ break;
+ }
+ ALOGV("Decoder arg setup succeeded");
+ // If input dump is enabled, then write to file
+ DUMP_TO_FILE(mInFile, s_dec_ip.pv_stream_buffer, s_dec_ip.u4_num_Bytes, mInputOffset);
+
+ GETTIME(&mTimeStart, NULL);
+ /* Compute time elapsed between end of previous decode()
+ * to start of current decode() */
+ TIME_DIFF(mTimeEnd, mTimeStart, timeDelay);
+
+ IV_API_CALL_STATUS_T status;
+ status = ivdec_api_function(mCodecCtx, (void *)&s_dec_ip, (void *)&s_dec_op);
+ ALOGV("status = %d, error_code = %d", status, (s_dec_op.u4_error_code & 0xFF));
+ if (s_dec_op.u4_output_present) {
+ const C2PlanarLayout &layout = output.layout();
+ if (layout.planes[0].rowInc != (int32_t)mWidth || layout.planes[1].colInc != 1) {
+ CopyPlane(output.data()[0], layout.planes[0], mOutBuffer, mWidth, mHeight);
+ }
+ if (layout.planes[1].rowInc != (int32_t)mWidth / 2 || layout.planes[1].colInc != 1) {
+ CopyPlane(
+ output.data()[1], layout.planes[1],
+ mOutBuffer + (mWidth * mHeight), mWidth / 2, mHeight / 2);
+ }
+ if (layout.planes[2].rowInc != (int32_t)mWidth / 2 || layout.planes[2].colInc != 1) {
+ CopyPlane(
+ output.data()[2], layout.planes[2],
+ mOutBuffer + (mWidth * mHeight * 5 / 4), mWidth / 2, mHeight / 2);
+ }
+ }
}
- ALOGV("Decoder arg setup succeeded");
- // If input dump is enabled, then write to file
- DUMP_TO_FILE(mInFile, s_dec_ip.pv_stream_buffer, s_dec_ip.u4_num_Bytes, mInputOffset);
-
- GETTIME(&mTimeStart, NULL);
- /* Compute time elapsed between end of previous decode()
- * to start of current decode() */
- TIME_DIFF(mTimeEnd, mTimeStart, timeDelay);
-
- IV_API_CALL_STATUS_T status;
- status = ivdec_api_function(mCodecCtx, (void *)&s_dec_ip, (void *)&s_dec_op);
- ALOGV("status = %d, error_code = %d", status, (s_dec_op.u4_error_code & 0xFF));
bool unsupportedResolution =
(IVD_STREAM_WIDTH_HEIGHT_NOT_SUPPORTED == (s_dec_op.u4_error_code & 0xFF));
diff --git a/media/libstagefright/codecs/avcdec/C2SoftAvcDec.h b/media/libstagefright/codecs/avcdec/C2SoftAvcDec.h
index 6632bf3..d324a0f 100644
--- a/media/libstagefright/codecs/avcdec/C2SoftAvcDec.h
+++ b/media/libstagefright/codecs/avcdec/C2SoftAvcDec.h
@@ -189,7 +189,7 @@
struct timeval mTimeEnd; // Time at the end of decode()
// Internal buffer to be used to flush out the buffers from decoder
- uint8_t *mFlushOutBuffer;
+ uint8_t *mOutBuffer;
#ifdef FILE_DUMP_ENABLE
char mInFile[200];
diff --git a/media/libstagefright/codecs/avcenc/C2SoftAvcEnc.cpp b/media/libstagefright/codecs/avcenc/C2SoftAvcEnc.cpp
index 8fb8122..d75e4c1 100644
--- a/media/libstagefright/codecs/avcenc/C2SoftAvcEnc.cpp
+++ b/media/libstagefright/codecs/avcenc/C2SoftAvcEnc.cpp
@@ -909,6 +909,9 @@
}
ALOGV("width = %d, height = %d", input->width(), input->height());
+ if (mWidth != input->width() || mHeight != input->height()) {
+ return C2_BAD_VALUE;
+ }
const C2PlanarLayout &layout = input->layout();
uint8_t *yPlane = const_cast<uint8_t *>(input->data()[C2PlanarLayout::PLANE_Y]);
uint8_t *uPlane = const_cast<uint8_t *>(input->data()[C2PlanarLayout::PLANE_U]);
diff --git a/media/libstagefright/codecs/cmds/codec2.cpp b/media/libstagefright/codecs/cmds/codec2.cpp
index 0ec1a77..295a5b0 100644
--- a/media/libstagefright/codecs/cmds/codec2.cpp
+++ b/media/libstagefright/codecs/cmds/codec2.cpp
@@ -93,6 +93,8 @@
sp<IProducerListener> mProducerListener;
+ std::atomic_int mLinearPoolId;
+
std::shared_ptr<C2Allocator> mAllocIon;
std::shared_ptr<C2BlockPool> mLinearPool;
@@ -137,12 +139,13 @@
SimplePlayer::SimplePlayer()
: mListener(new Listener(this)),
mProducerListener(new DummyProducerListener),
+ mLinearPoolId(C2BlockPool::PLATFORM_START),
mComposerClient(new SurfaceComposerClient) {
CHECK_EQ(mComposerClient->initCheck(), (status_t)OK);
std::shared_ptr<C2AllocatorStore> store = GetCodec2PlatformAllocatorStore();
CHECK_EQ(store->fetchAllocator(C2AllocatorStore::DEFAULT_LINEAR, &mAllocIon), C2_OK);
- mLinearPool = std::make_shared<C2BasicLinearBlockPool>(mAllocIon);
+ mLinearPool = std::make_shared<C2PooledBlockPool>(mAllocIon, mLinearPoolId++);
mControl = mComposerClient->createSurface(
String8("A Surface"),
@@ -245,7 +248,7 @@
ALOGV("Render: Frame #%lld", work->worklets.front()->output.ordinal.frameIndex.peekll());
const std::shared_ptr<C2Buffer> &output = work->worklets.front()->output.buffers[0];
if (output) {
- const C2ConstGraphicBlock &block = output->data().graphicBlocks().front();
+ const C2ConstGraphicBlock block = output->data().graphicBlocks().front();
native_handle_t *grallocHandle = UnwrapNativeCodec2GrallocHandle(block.handle());
sp<GraphicBuffer> buffer(new GraphicBuffer(
grallocHandle,
@@ -284,7 +287,7 @@
});
long numFrames = 0;
- mLinearPool.reset(new C2BasicLinearBlockPool(mAllocIon));
+ mLinearPool.reset(new C2PooledBlockPool(mAllocIon, mLinearPoolId++));
for (;;) {
size_t size = 0u;
diff --git a/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.cpp b/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.cpp
index ce40d6b..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");
@@ -111,7 +115,7 @@
return;
}
- const C2ConstLinearBlock &inBuffer = work->input.buffers[0]->data().linearBlocks().front();
+ const C2ConstLinearBlock inBuffer = work->input.buffers[0]->data().linearBlocks().front();
size_t inOffset = inBuffer.offset();
size_t inSize = inBuffer.size();
C2ReadView rView = work->input.buffers[0]->data().linearBlocks().front().map().get();
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/codecs/flac/enc/C2SoftFlacEnc.cpp b/media/libstagefright/codecs/flac/enc/C2SoftFlacEnc.cpp
index 6c147ad..fa93fe5 100644
--- a/media/libstagefright/codecs/flac/enc/C2SoftFlacEnc.cpp
+++ b/media/libstagefright/codecs/flac/enc/C2SoftFlacEnc.cpp
@@ -140,7 +140,7 @@
return;
}
- const C2ConstLinearBlock &inBuffer = work->input.buffers[0]->data().linearBlocks().front();
+ const C2ConstLinearBlock inBuffer = work->input.buffers[0]->data().linearBlocks().front();
bool eos = ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0);
size_t inOffset = inBuffer.offset();
size_t inSize = inBuffer.size();
diff --git a/media/libstagefright/codecs/g711/dec/C2SoftG711.cpp b/media/libstagefright/codecs/g711/dec/C2SoftG711.cpp
index d296a3d..1049420 100644
--- a/media/libstagefright/codecs/g711/dec/C2SoftG711.cpp
+++ b/media/libstagefright/codecs/g711/dec/C2SoftG711.cpp
@@ -92,8 +92,8 @@
return;
}
- const C2ConstLinearBlock &inBuffer =
- work->input.buffers[0]->data().linearBlocks().front();
+ const C2ConstLinearBlock inBuffer =
+ work->input.buffers[0]->data().linearBlocks().front();
C2ReadView rView = inBuffer.map().get();
size_t inOffset = inBuffer.offset();
size_t inSize = inBuffer.size();
diff --git a/media/libstagefright/codecs/m4v_h263/dec/C2SoftMpeg4Dec.cpp b/media/libstagefright/codecs/m4v_h263/dec/C2SoftMpeg4Dec.cpp
index 2a3239f..641c342 100644
--- a/media/libstagefright/codecs/m4v_h263/dec/C2SoftMpeg4Dec.cpp
+++ b/media/libstagefright/codecs/m4v_h263/dec/C2SoftMpeg4Dec.cpp
@@ -304,7 +304,7 @@
return;
}
- const C2ConstLinearBlock &inBuffer = work->input.buffers[0]->data().linearBlocks().front();
+ const C2ConstLinearBlock inBuffer = work->input.buffers[0]->data().linearBlocks().front();
size_t inOffset = inBuffer.offset();
size_t inSize = inBuffer.size();
uint32_t workIndex = work->input.ordinal.frameIndex.peeku() & 0xFFFFFFFF;
diff --git a/media/libstagefright/codecs/mp3dec/C2SoftMP3.cpp b/media/libstagefright/codecs/mp3dec/C2SoftMP3.cpp
index 9cac87e..0a8891e 100644
--- a/media/libstagefright/codecs/mp3dec/C2SoftMP3.cpp
+++ b/media/libstagefright/codecs/mp3dec/C2SoftMP3.cpp
@@ -288,7 +288,7 @@
return;
}
- const C2ConstLinearBlock &inBuffer = work->input.buffers[0]->data().linearBlocks().front();
+ const C2ConstLinearBlock inBuffer = work->input.buffers[0]->data().linearBlocks().front();
bool eos = ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0);
size_t inOffset = inBuffer.offset();
size_t inSize = inBuffer.size();
diff --git a/media/libstagefright/codecs/mp3dec/src/pvmp3_decode_header.cpp b/media/libstagefright/codecs/mp3dec/src/pvmp3_decode_header.cpp
index d443b7c..bc5fd79 100644
--- a/media/libstagefright/codecs/mp3dec/src/pvmp3_decode_header.cpp
+++ b/media/libstagefright/codecs/mp3dec/src/pvmp3_decode_header.cpp
@@ -184,7 +184,7 @@
info->emphasis = (temp << 30) >> 30; /* 2 */
- if (!info->bitrate_index || info->sampling_frequency == 3)
+ if (!info->bitrate_index || info->bitrate_index == 15 || info->sampling_frequency == 3)
{
err = UNSUPPORTED_FREE_BITRATE;
}
diff --git a/media/libstagefright/codecs/mpeg2dec/C2SoftMpeg2Dec.cpp b/media/libstagefright/codecs/mpeg2dec/C2SoftMpeg2Dec.cpp
index 0ebe7d6..74ea340 100644
--- a/media/libstagefright/codecs/mpeg2dec/C2SoftMpeg2Dec.cpp
+++ b/media/libstagefright/codecs/mpeg2dec/C2SoftMpeg2Dec.cpp
@@ -614,7 +614,7 @@
return;
}
- const C2ConstLinearBlock &inBuffer = work->input.buffers[0]->data().linearBlocks().front();
+ const C2ConstLinearBlock inBuffer = work->input.buffers[0]->data().linearBlocks().front();
size_t inOffset = inBuffer.offset();
size_t inSize = inBuffer.size();
uint32_t workIndex = work->input.ordinal.frameIndex.peeku() & 0xFFFFFFFF;
diff --git a/media/libstagefright/codecs/on2/dec/C2SoftVpx.cpp b/media/libstagefright/codecs/on2/dec/C2SoftVpx.cpp
index 96b303c..8528f26 100644
--- a/media/libstagefright/codecs/on2/dec/C2SoftVpx.cpp
+++ b/media/libstagefright/codecs/on2/dec/C2SoftVpx.cpp
@@ -209,7 +209,7 @@
return;
}
- const C2ConstLinearBlock &inBuffer = work->input.buffers[0]->data().linearBlocks().front();
+ const C2ConstLinearBlock inBuffer = work->input.buffers[0]->data().linearBlocks().front();
size_t inOffset = inBuffer.offset();
size_t inSize = inBuffer.size();
C2ReadView rView = work->input.buffers[0]->data().linearBlocks().front().map().get();
diff --git a/media/libstagefright/codecs/opus/dec/C2SoftOpus.cpp b/media/libstagefright/codecs/opus/dec/C2SoftOpus.cpp
index 4eec362..47fb6de 100644
--- a/media/libstagefright/codecs/opus/dec/C2SoftOpus.cpp
+++ b/media/libstagefright/codecs/opus/dec/C2SoftOpus.cpp
@@ -251,7 +251,7 @@
return;
}
- const C2ConstLinearBlock &inBuffer = work->input.buffers[0]->data().linearBlocks().front();
+ const C2ConstLinearBlock inBuffer = work->input.buffers[0]->data().linearBlocks().front();
bool eos = ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0);
size_t inOffset = inBuffer.offset();
size_t inSize = inBuffer.size();
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 eb3255f..c8618db 100644
--- a/media/libstagefright/include/CCodecBufferChannel.h
+++ b/media/libstagefright/include/CCodecBufferChannel.h
@@ -88,7 +88,7 @@
* Start queueing buffers to the component. This object should never queue
* buffers before this call.
*/
- void start(const sp<AMessage> &inputFormat, const sp<AMessage> &outputFormat);
+ status_t start(const sp<AMessage> &inputFormat, const sp<AMessage> &outputFormat);
/**
* Stop queueing buffers to the component. This object should never queue
@@ -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 9766b41..b2e3b1b 100644
--- a/media/libstagefright/include/Codec2Buffer.h
+++ b/media/libstagefright/include/Codec2Buffer.h
@@ -20,7 +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 {
@@ -109,6 +113,11 @@
public:
/**
* Allocate a new LinearBufferBlock wrapping around C2LinearBlock object.
+ *
+ * \param format mandatory buffer format for MediaCodecBuffer
+ * \param block C2LinearBlock object to wrap around.
+ * \return LinearBlockBuffer object with writable mapping.
+ * nullptr if unsuccessful.
*/
static sp<LinearBlockBuffer> Allocate(
const sp<AMessage> &format, const std::shared_ptr<C2LinearBlock> &block);
@@ -137,6 +146,11 @@
public:
/**
* Allocate a new ConstLinearBlockBuffer wrapping around C2Buffer object.
+ *
+ * \param format mandatory buffer format for MediaCodecBuffer
+ * \param buffer linear C2Buffer object to wrap around.
+ * \return ConstLinearBlockBuffer object with readable mapping.
+ * nullptr if unsuccessful.
*/
static sp<ConstLinearBlockBuffer> Allocate(
const sp<AMessage> &format, const std::shared_ptr<C2Buffer> &buffer);
@@ -156,6 +170,183 @@
std::shared_ptr<C2Buffer> mBufferRef;
};
+/**
+ * MediaCodecBuffer implementation wraps around C2GraphicBlock.
+ *
+ * This object exposes the underlying bits via accessor APIs and "image-data"
+ * metadata, created automatically at allocation time.
+ */
+class GraphicBlockBuffer : public Codec2Buffer {
+public:
+ /**
+ * Allocate a new GraphicBlockBuffer wrapping around C2GraphicBlock object.
+ * If |block| is not in good color formats, it allocates YV12 local buffer
+ * and copies the content over at asC2Buffer().
+ *
+ * \param format mandatory buffer format for MediaCodecBuffer
+ * \param block C2GraphicBlock object to wrap around.
+ * \param alloc a function to allocate backing ABuffer if needed.
+ * \return GraphicBlockBuffer object with writable mapping.
+ * nullptr if unsuccessful.
+ */
+ static sp<GraphicBlockBuffer> Allocate(
+ const sp<AMessage> &format,
+ const std::shared_ptr<C2GraphicBlock> &block,
+ std::function<sp<ABuffer>(size_t)> alloc);
+
+ std::shared_ptr<C2Buffer> asC2Buffer() override;
+
+ virtual ~GraphicBlockBuffer() = default;
+
+private:
+ GraphicBlockBuffer(
+ const sp<AMessage> &format,
+ const sp<ABuffer> &buffer,
+ C2GraphicView &&view,
+ const std::shared_ptr<C2GraphicBlock> &block,
+ const sp<ABuffer> &imageData,
+ bool wrapped);
+ GraphicBlockBuffer() = delete;
+
+ inline MediaImage2 *imageData() { return (MediaImage2 *)mImageData->data(); }
+
+ C2GraphicView mView;
+ std::shared_ptr<C2GraphicBlock> mBlock;
+ sp<ABuffer> mImageData;
+ const bool mWrapped;
+};
+
+/**
+ * MediaCodecBuffer implementation wraps around graphic C2Buffer object.
+ *
+ * This object exposes the underlying bits via accessor APIs and "image-data"
+ * metadata, created automatically at allocation time.
+ */
+class ConstGraphicBlockBuffer : public Codec2Buffer {
+public:
+ /**
+ * Allocate a new ConstGraphicBlockBuffer wrapping around C2Buffer object.
+ * If |buffer| is not in good color formats, it allocates YV12 local buffer
+ * and copies the content of |buffer| over to expose.
+ *
+ * \param format mandatory buffer format for MediaCodecBuffer
+ * \param buffer graphic C2Buffer object to wrap around.
+ * \param alloc a function to allocate backing ABuffer if needed.
+ * \return ConstGraphicBlockBuffer object with readable mapping.
+ * nullptr if unsuccessful.
+ */
+ static sp<ConstGraphicBlockBuffer> Allocate(
+ const sp<AMessage> &format,
+ const std::shared_ptr<C2Buffer> &buffer,
+ std::function<sp<ABuffer>(size_t)> alloc);
+
+ /**
+ * Allocate a new ConstGraphicBlockBuffer which allocates YV12 local buffer
+ * and copies the content of |buffer| over to expose.
+ *
+ * \param format mandatory buffer format for MediaCodecBuffer
+ * \param alloc a function to allocate backing ABuffer if needed.
+ * \return ConstGraphicBlockBuffer object with no wrapping buffer.
+ */
+ static sp<ConstGraphicBlockBuffer> AllocateEmpty(
+ const sp<AMessage> &format,
+ std::function<sp<ABuffer>(size_t)> alloc);
+
+ std::shared_ptr<C2Buffer> asC2Buffer() override;
+ bool canCopy(const std::shared_ptr<C2Buffer> &buffer) const override;
+ bool copy(const std::shared_ptr<C2Buffer> &buffer) override;
+
+ virtual ~ConstGraphicBlockBuffer() = default;
+
+private:
+ 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);
+ ConstGraphicBlockBuffer() = delete;
+
+ sp<ABuffer> mImageData;
+ std::unique_ptr<const C2GraphicView> mView;
+ std::shared_ptr<C2Buffer> mBufferRef;
+ 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 3a2670d..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:
@@ -69,10 +70,12 @@
private:
typedef std::chrono::time_point<std::chrono::steady_clock> TimePoint;
+ status_t tryAndReportOnError(std::function<status_t()> job);
+
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/AnotherPacketSource.cpp b/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp
index 8488d10..ece0692 100644
--- a/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp
+++ b/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp
@@ -218,7 +218,7 @@
}
sp<ABuffer> mpegUserData;
- if (buffer->meta()->findBuffer("mpegUserData", &mpegUserData) && mpegUserData != NULL) {
+ if (buffer->meta()->findBuffer("mpeg-user-data", &mpegUserData) && mpegUserData != NULL) {
bufmeta.setData(
kKeyMpegUserData, 0, mpegUserData->data(), mpegUserData->size());
}
diff --git a/media/libstagefright/mpeg2ts/ESQueue.cpp b/media/libstagefright/mpeg2ts/ESQueue.cpp
index 9e18fd3..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();
}
}
@@ -1475,7 +1477,7 @@
mpegUserData->data() + i * sizeof(size_t),
&userDataPositions[i], sizeof(size_t));
}
- accessUnit->meta()->setBuffer("mpegUserData", mpegUserData);
+ accessUnit->meta()->setBuffer("mpeg-user-data", mpegUserData);
}
}
@@ -1493,7 +1495,9 @@
const uint8_t *data, size_t size) {
static const char kStartCode[] = "\x00\x00\x01";
- if (size < 3) {
+ // per ISO/IEC 14496-2 6.2.1, a chunk has a 3-byte prefix + 1-byte start code
+ // we need at least <prefix><start><next prefix> to successfully scan
+ if (size < 3 + 1 + 3) {
return -EAGAIN;
}
@@ -1501,7 +1505,7 @@
return -EAGAIN;
}
- size_t offset = 3;
+ size_t offset = 4;
while (offset + 2 < size) {
if (!memcmp(&data[offset], kStartCode, 3)) {
return offset;
@@ -1552,6 +1556,9 @@
state = EXPECT_VISUAL_OBJECT_START;
} else {
discard = true;
+ offset += chunkSize;
+ ALOGW("b/74114680, advance to next chunk");
+ android_errorWriteLog(0x534e4554, "74114680");
}
break;
}
diff --git a/media/ndk/NdkMediaExtractor.cpp b/media/ndk/NdkMediaExtractor.cpp
index ea43d2e..b5e60a4 100644
--- a/media/ndk/NdkMediaExtractor.cpp
+++ b/media/ndk/NdkMediaExtractor.cpp
@@ -43,7 +43,12 @@
static media_status_t translate_error(status_t err) {
if (err == OK) {
return AMEDIA_OK;
+ } else if (err == ERROR_END_OF_STREAM) {
+ return AMEDIA_ERROR_END_OF_STREAM;
+ } else if (err == ERROR_IO) {
+ return AMEDIA_ERROR_IO;
}
+
ALOGE("sf error code: %d", err);
return AMEDIA_ERROR_UNKNOWN;
}
@@ -411,5 +416,70 @@
return -1;
}
+EXPORT
+media_status_t AMediaExtractor_getSampleFormat(AMediaExtractor *ex, AMediaFormat *fmt) {
+ if (fmt == NULL) {
+ return AMEDIA_ERROR_INVALID_PARAMETER;
+ }
+
+ sp<MetaData> sampleMeta;
+ status_t err = ex->mImpl->getSampleMeta(&sampleMeta);
+ if (err != OK) {
+ return translate_error(err);
+ }
+
+ sp<AMessage> meta;
+ AMediaFormat_getFormat(fmt, &meta);
+ meta->clear();
+
+ int32_t layerId;
+ if (sampleMeta->findInt32(kKeyTemporalLayerId, &layerId)) {
+ meta->setInt32(AMEDIAFORMAT_KEY_TEMPORAL_LAYER_ID, layerId);
+ }
+
+ size_t trackIndex;
+ err = ex->mImpl->getSampleTrackIndex(&trackIndex);
+ if (err == OK) {
+ meta->setInt32(AMEDIAFORMAT_KEY_TRACK_INDEX, trackIndex);
+ sp<AMessage> trackFormat;
+ AString mime;
+ err = ex->mImpl->getTrackFormat(trackIndex, &trackFormat);
+ if (err == OK
+ && trackFormat != NULL
+ && trackFormat->findString(AMEDIAFORMAT_KEY_MIME, &mime)) {
+ meta->setString(AMEDIAFORMAT_KEY_MIME, mime);
+ }
+ }
+
+ int64_t durationUs;
+ if (sampleMeta->findInt64(kKeyDuration, &durationUs)) {
+ meta->setInt64(AMEDIAFORMAT_KEY_DURATION, durationUs);
+ }
+
+ uint32_t dataType; // unused
+ const void *seiData;
+ size_t seiLength;
+ if (sampleMeta->findData(kKeySEI, &dataType, &seiData, &seiLength)) {
+ sp<ABuffer> sei = ABuffer::CreateAsCopy(seiData, seiLength);;
+ meta->setBuffer(AMEDIAFORMAT_KEY_SEI, sei);
+ }
+
+ const void *mpegUserDataPointer;
+ size_t mpegUserDataLength;
+ if (sampleMeta->findData(
+ kKeyMpegUserData, &dataType, &mpegUserDataPointer, &mpegUserDataLength)) {
+ sp<ABuffer> mpegUserData = ABuffer::CreateAsCopy(mpegUserDataPointer, mpegUserDataLength);
+ meta->setBuffer(AMEDIAFORMAT_KEY_MPEG_USER_DATA, mpegUserData);
+ }
+
+ return AMEDIA_OK;
+}
+
+EXPORT
+media_status_t AMediaExtractor_disconnect(AMediaExtractor * ex) {
+ ex->mImpl->disconnect();
+ return AMEDIA_OK;
+}
+
} // extern "C"
diff --git a/media/ndk/NdkMediaFormat.cpp b/media/ndk/NdkMediaFormat.cpp
index b86876b..9bf450c 100644
--- a/media/ndk/NdkMediaFormat.cpp
+++ b/media/ndk/NdkMediaFormat.cpp
@@ -286,7 +286,13 @@
EXPORT const char* AMEDIAFORMAT_KEY_COLOR_STANDARD = "color-standard";
EXPORT const char* AMEDIAFORMAT_KEY_COLOR_TRANSFER = "color-transfer";
EXPORT const char* AMEDIAFORMAT_KEY_COMPLEXITY = "complexity";
+EXPORT const char* AMEDIAFORMAT_KEY_CSD = "csd";
+EXPORT const char* AMEDIAFORMAT_KEY_CSD_0 = "csd-0";
+EXPORT const char* AMEDIAFORMAT_KEY_CSD_1 = "csd-1";
+EXPORT const char* AMEDIAFORMAT_KEY_CSD_2 = "csd-2";
EXPORT const char* AMEDIAFORMAT_KEY_DISPLAY_CROP = "crop";
+EXPORT const char* AMEDIAFORMAT_KEY_DISPLAY_HEIGHT = "display-height";
+EXPORT const char* AMEDIAFORMAT_KEY_DISPLAY_WIDTH = "display-width";
EXPORT const char* AMEDIAFORMAT_KEY_DURATION = "durationUs";
EXPORT const char* AMEDIAFORMAT_KEY_FLAC_COMPRESSION_LEVEL = "flac-compression-level";
EXPORT const char* AMEDIAFORMAT_KEY_FRAME_RATE = "frame-rate";
@@ -309,6 +315,7 @@
EXPORT const char* AMEDIAFORMAT_KEY_MAX_INPUT_SIZE = "max-input-size";
EXPORT const char* AMEDIAFORMAT_KEY_MAX_WIDTH = "max-width";
EXPORT const char* AMEDIAFORMAT_KEY_MIME = "mime";
+EXPORT const char* AMEDIAFORMAT_KEY_MPEG_USER_DATA = "mpeg-user-data";
EXPORT const char* AMEDIAFORMAT_KEY_OPERATING_RATE = "operating-rate";
EXPORT const char* AMEDIAFORMAT_KEY_PCM_ENCODING = "pcm-encoding";
EXPORT const char* AMEDIAFORMAT_KEY_PRIORITY = "priority";
@@ -317,10 +324,14 @@
EXPORT const char* AMEDIAFORMAT_KEY_REPEAT_PREVIOUS_FRAME_AFTER = "repeat-previous-frame-after";
EXPORT const char* AMEDIAFORMAT_KEY_ROTATION = "rotation-degrees";
EXPORT const char* AMEDIAFORMAT_KEY_SAMPLE_RATE = "sample-rate";
+EXPORT const char* AMEDIAFORMAT_KEY_SEI = "sei";
EXPORT const char* AMEDIAFORMAT_KEY_SLICE_HEIGHT = "slice-height";
EXPORT const char* AMEDIAFORMAT_KEY_STRIDE = "stride";
+EXPORT const char* AMEDIAFORMAT_KEY_TEMPORAL_LAYER_ID = "temporal-layer-id";
EXPORT const char* AMEDIAFORMAT_KEY_TEMPORAL_LAYERING = "ts-schema";
+EXPORT const char* AMEDIAFORMAT_KEY_TIME_US = "timeUs";
EXPORT const char* AMEDIAFORMAT_KEY_TRACK_ID = "track-id";
+EXPORT const char* AMEDIAFORMAT_KEY_TRACK_INDEX = "track-index";
EXPORT const char* AMEDIAFORMAT_KEY_WIDTH = "width";
diff --git a/media/ndk/include/media/NdkMediaError.h b/media/ndk/include/media/NdkMediaError.h
index e48fcbe..13aacc9 100644
--- a/media/ndk/include/media/NdkMediaError.h
+++ b/media/ndk/include/media/NdkMediaError.h
@@ -53,6 +53,8 @@
AMEDIA_ERROR_INVALID_OBJECT = AMEDIA_ERROR_BASE - 3,
AMEDIA_ERROR_INVALID_PARAMETER = AMEDIA_ERROR_BASE - 4,
AMEDIA_ERROR_INVALID_OPERATION = AMEDIA_ERROR_BASE - 5,
+ AMEDIA_ERROR_END_OF_STREAM = AMEDIA_ERROR_BASE - 6,
+ AMEDIA_ERROR_IO = AMEDIA_ERROR_BASE - 7,
AMEDIA_DRM_ERROR_BASE = -20000,
AMEDIA_DRM_NOT_PROVISIONED = AMEDIA_DRM_ERROR_BASE - 1,
diff --git a/media/ndk/include/media/NdkMediaExtractor.h b/media/ndk/include/media/NdkMediaExtractor.h
index 3c9e23d..1d295e4 100644
--- a/media/ndk/include/media/NdkMediaExtractor.h
+++ b/media/ndk/include/media/NdkMediaExtractor.h
@@ -203,8 +203,25 @@
*/
int64_t AMediaExtractor_getCachedDuration(AMediaExtractor *);
+/**
+ * Read the current sample's metadata format into |fmt|. Examples of sample metadata are
+ * SEI (supplemental enhancement information) and MPEG user data, both of which can embed
+ * closed-caption data.
+ *
+ * Returns AMEDIA_OK on success or AMEDIA_ERROR_* to indicate failure reason.
+ * Existing key-value pairs in |fmt| would be removed if this API returns AMEDIA_OK.
+ * The contents of |fmt| is undefined if this API returns AMEDIA_ERROR_*.
+ */
+media_status_t AMediaExtractor_getSampleFormat(AMediaExtractor *ex, AMediaFormat *fmt);
+
#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/media/ndk/include/media/NdkMediaFormat.h b/media/ndk/include/media/NdkMediaFormat.h
index b6489c7..1da9197 100644
--- a/media/ndk/include/media/NdkMediaFormat.h
+++ b/media/ndk/include/media/NdkMediaFormat.h
@@ -100,7 +100,13 @@
extern const char* AMEDIAFORMAT_KEY_COLOR_STANDARD;
extern const char* AMEDIAFORMAT_KEY_COLOR_TRANSFER;
extern const char* AMEDIAFORMAT_KEY_COMPLEXITY;
+extern const char* AMEDIAFORMAT_KEY_CSD;
+extern const char* AMEDIAFORMAT_KEY_CSD_0;
+extern const char* AMEDIAFORMAT_KEY_CSD_1;
+extern const char* AMEDIAFORMAT_KEY_CSD_2;
extern const char* AMEDIAFORMAT_KEY_DISPLAY_CROP;
+extern const char* AMEDIAFORMAT_KEY_DISPLAY_HEIGHT;
+extern const char* AMEDIAFORMAT_KEY_DISPLAY_WIDTH;
extern const char* AMEDIAFORMAT_KEY_DURATION;
extern const char* AMEDIAFORMAT_KEY_FLAC_COMPRESSION_LEVEL;
extern const char* AMEDIAFORMAT_KEY_FRAME_RATE;
@@ -123,6 +129,7 @@
extern const char* AMEDIAFORMAT_KEY_MAX_INPUT_SIZE;
extern const char* AMEDIAFORMAT_KEY_MAX_WIDTH;
extern const char* AMEDIAFORMAT_KEY_MIME;
+extern const char* AMEDIAFORMAT_KEY_MPEG_USER_DATA;
extern const char* AMEDIAFORMAT_KEY_OPERATING_RATE;
extern const char* AMEDIAFORMAT_KEY_PCM_ENCODING;
extern const char* AMEDIAFORMAT_KEY_PRIORITY;
@@ -131,10 +138,14 @@
extern const char* AMEDIAFORMAT_KEY_REPEAT_PREVIOUS_FRAME_AFTER;
extern const char* AMEDIAFORMAT_KEY_ROTATION;
extern const char* AMEDIAFORMAT_KEY_SAMPLE_RATE;
+extern const char* AMEDIAFORMAT_KEY_SEI;
extern const char* AMEDIAFORMAT_KEY_SLICE_HEIGHT;
extern const char* AMEDIAFORMAT_KEY_STRIDE;
+extern const char* AMEDIAFORMAT_KEY_TEMPORAL_LAYER_ID;
extern const char* AMEDIAFORMAT_KEY_TEMPORAL_LAYERING;
+extern const char* AMEDIAFORMAT_KEY_TIME_US;
extern const char* AMEDIAFORMAT_KEY_TRACK_ID;
+extern const char* AMEDIAFORMAT_KEY_TRACK_INDEX;
extern const char* AMEDIAFORMAT_KEY_WIDTH;
#endif /* __ANDROID_API__ >= 21 */
diff --git a/media/ndk/libmediandk.map.txt b/media/ndk/libmediandk.map.txt
index 17c1a0d..d8c41d2 100644
--- a/media/ndk/libmediandk.map.txt
+++ b/media/ndk/libmediandk.map.txt
@@ -159,6 +159,7 @@
AMediaExtractor_getPsshInfo;
AMediaExtractor_getSampleCryptoInfo;
AMediaExtractor_getSampleFlags;
+ AMediaExtractor_getSampleFormat; # introduced=28
AMediaExtractor_getSampleSize; # introduced=28
AMediaExtractor_getSampleTime;
AMediaExtractor_getSampleTrackIndex;
diff --git a/packages/MediaComponents/Android.mk b/packages/MediaComponents/Android.mk
index 22eea25..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)
@@ -32,6 +32,7 @@
# TODO: Use System SDK once public APIs are approved
# LOCAL_SDK_VERSION := system_current
+LOCAL_PRIVATE_PLATFORM_APIS := true
LOCAL_SRC_FILES := \
$(call all-java-files-under, src) \
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 aafe8b0..eab2309 100644
--- a/packages/MediaComponents/res/layout/media_controller.xml
+++ b/packages/MediaComponents/res/layout/media_controller.xml
@@ -104,7 +104,7 @@
<view class="com.android.support.mediarouter.app.MediaRouteButton"
android:id="@+id/cast"
android:layout_centerVertical="true"
- android:visibility="visible"
+ android:visibility="gone"
style="@style/TitleBarButton" />
</LinearLayout>
@@ -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/test/runtest.sh b/packages/MediaComponents/runcts.sh
similarity index 62%
rename from packages/MediaComponents/test/runtest.sh
rename to packages/MediaComponents/runcts.sh
index edce230..61b1a1e 100644
--- a/packages/MediaComponents/test/runtest.sh
+++ b/packages/MediaComponents/runcts.sh
@@ -13,13 +13,17 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-# Usage '. runtest.sh'
+# Usage '. runcts.sh'
-function _runtest_mediacomponent_usage() {
- echo 'runtest-MediaComponents [option]: Run MediaComponents test'
+function _runtest_cts_mediacomponent_usage() {
+ echo 'runtest-cts-MediaComponents [option]: Build, flash device,'
+ echo ' and run subset of CtsMediaTestCases that MediaComponents covers.'
+ echo ' *Warning* This bypasses CTS setup (e.g. download media contents from server)'
+ echo ' For running CTS in official way, use atest or cts-tradefed '
echo ' -h|--help: This help'
- echo ' --skip: Skip build. Just rerun-tests.'
- echo ' --min: Only rebuild test apk and updatable library.'
+ 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.'
@@ -27,15 +31,25 @@
echo ' -t [test]: Only run the specific test. Can be either a class or a method.'
}
-function runtest-MediaComponents() {
+function runtest-cts-MediaComponents() {
# Edit here if you want to support other tests.
# List up libs and apks in the media_api needed for tests, and place test target at the last.
local TEST_PACKAGE_DIR=("frameworks/av/packages/MediaComponents/test")
- local BUILD_TARGETS=("MediaComponents" "MediaComponentsTest")
- local INSTALL_TARGETS=("MediaComponentsTest")
+ local TEST_PACKAGE=("android.media.cts")
+ local BUILD_TARGETS=("MediaComponents" "CtsMediaTestCases")
+ # Don't include MediaComponents -- if we simply install it, system server
+ # wouldn't use the installed one.
+ local INSTALL_TARGETS=("CtsMediaTestCases")
local TEST_RUNNER="android.support.test.runner.AndroidJUnitRunner"
local DEPENDENCIES=("mockito-target-minus-junit4" "android-support-test" "compatibility-device-util")
-
+ local DEFAULT_TEST_TARGET=""
+ DEFAULT_TEST_TARGET+="android.media.cts.MediaBrowser2Test"
+ DEFAULT_TEST_TARGET+=",android.media.cts.MediaController2Test"
+ DEFAULT_TEST_TARGET+=",android.media.cts.MediaMetadata2Test"
+ DEFAULT_TEST_TARGET+=",android.media.cts.MediaSession2Test"
+ DEFAULT_TEST_TARGET+=",android.media.cts.MediaSession2_PermissionTest"
+ DEFAULT_TEST_TARGET+=",android.media.cts.MediaSessionManager_MediaSession2Test"
+ DEFAULT_TEST_TARGET+=",android.media.cts.SessionToken2Test"
if [[ -z "${ANDROID_BUILD_TOP}" ]]; then
echo "Needs to lunch a target first"
return
@@ -45,14 +59,15 @@
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=""
+ local OPTION_TEST_TARGET="${DEFAULT_TEST_TARGET}"
local adbhost_local
while (( "$#" )); do
case "${1}" in
-h|--help)
- _runtest_mediacomponent_usage
+ _runtest_cts_mediacomponent_usage
return
;;
--skip)
@@ -61,6 +76,9 @@
--min)
OPTION_MIN="true"
;;
+ --test)
+ OPTION_TEST="true"
+ ;;
-s)
shift
adbhost_local=${1}
@@ -95,7 +113,7 @@
fi
local target_dir="${ANDROID_BUILD_TOP}/${TEST_PACKAGE_DIR}"
- local TEST_PACKAGE=$(sed -n 's/^.*\bpackage\b="\([a-z0-9\.]*\)".*$/\1/p' ${target_dir}/AndroidManifest.xml)
+ #local TEST_PACKAGE=$(sed -n 's/^.*\bpackage\b="\([a-z0-9\.]*\)".*$/\1/p' ${target_dir}/AndroidManifest.xml)
if [[ "${OPTION_SKIP}" != "true" ]]; then
# Build dependencies if needed.
@@ -120,32 +138,47 @@
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
- ${adb} root
- ${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.
- sleep 15
+ 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} wait-for-device || break
+ # Ensure package manager is loaded.
+ # TODO(jaewan): Find better way to wait
+ sleep 15
+ fi
# Install apks
local install_failed="false"
for target in ${INSTALL_TARGETS[@]}; do
- echo "${target}"
- local target_dir=$(mgrep -l -e '^LOCAL_PACKAGE_NAME.*'"${target}$")
- if [[ -z ${target_dir} ]]; then
- continue
- fi
- target_dir=$(dirname ${target_dir})
- local package=$(sed -n 's/^.*\bpackage\b="\([a-z0-9\._]*\)".*$/\1/p' ${target_dir}/AndroidManifest.xml)
local apk_path=$(find ${OUT}/system ${OUT}/data -name ${target}.apk)
local apk_num=$(find ${OUT}/system ${OUT}/data -name ${target}.apk | wc -l)
if [[ "${apk_num}" != "1" ]]; then
@@ -188,4 +221,4 @@
}
echo "Following functions are added to your environment:"
-_runtest_mediacomponent_usage
+_runtest_cts_mediacomponent_usage
diff --git a/packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl b/packages/MediaComponents/src/com/android/media/IMediaController2.aidl
similarity index 69%
rename from packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl
rename to packages/MediaComponents/src/com/android/media/IMediaController2.aidl
index 9ee4928..d6c8e21 100644
--- a/packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl
+++ b/packages/MediaComponents/src/com/android/media/IMediaController2.aidl
@@ -23,24 +23,31 @@
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);
+ void onAllowedCommandsChanged(in Bundle commands);
void onCustomCommand(in Bundle command, in Bundle args, in ResultReceiver receiver);
diff --git a/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl b/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl
index 34251fb..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 getBrowserRoot(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/MediaBrowser2Impl.java b/packages/MediaComponents/src/com/android/media/MediaBrowser2Impl.java
index 122ebe7..c909099 100644
--- a/packages/MediaComponents/src/com/android/media/MediaBrowser2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaBrowser2Impl.java
@@ -54,7 +54,7 @@
final IMediaSession2 binder = getSessionBinder();
if (binder != null) {
try {
- binder.getBrowserRoot(getControllerStub(), rootHints);
+ binder.getLibraryRoot(getControllerStub(), rootHints);
} catch (RemoteException e) {
// TODO(jaewan): Handle disconnect.
if (DEBUG) {
diff --git a/packages/MediaComponents/src/com/android/media/MediaController2Impl.java b/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
index 4a4f250..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() {
@@ -314,12 +335,12 @@
}
@Override
- public void skipToPrevious_impl() {
+ public void skipToPreviousItem_impl() {
sendTransportControlCommand(MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_PREV_ITEM);
}
@Override
- public void skipToNext_impl() {
+ public void skipToNextItem_impl() {
sendTransportControlCommand(MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_NEXT_ITEM);
}
@@ -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);
}
@@ -351,10 +372,10 @@
@Override
public void setVolumeTo_impl(int value, int flags) {
// TODO(hdmoon): sanity check
- final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_SET_VOLUME);
+ 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);
}
@@ -366,10 +387,10 @@
@Override
public void adjustVolume_impl(int direction, int flags) {
// TODO(hdmoon): sanity check
- final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_SET_VOLUME);
+ 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);
}
@@ -561,45 +627,65 @@
Bundle args = new Bundle();
args.putInt(MediaSession2Stub.ARGUMENT_KEY_ITEM_INDEX, item);
sendTransportControlCommand(
- MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_TO_PLAYLIST_ITEM, args);
+ MediaSession2.COMMAND_CODE_PLAYLIST_SKIP_TO_PLAYLIST_ITEM, args);
*/
}
@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,55 +702,101 @@
}
}
+ // 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
- public MediaItem2 getCurrentPlaylistItem_impl() {
+ public MediaItem2 getCurrentMediaItem_impl() {
// TODO(jaewan): Implement
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;
@@ -776,6 +933,12 @@
});
}
+ void onAllowedCommandsChanged(final CommandGroup commands) {
+ mCallbackExecutor.execute(() -> {
+ mCallback.onAllowedCommandsChanged(mInstance, commands);
+ });
+ }
+
void onCustomLayoutChanged(final List<CommandButton> layout) {
mCallbackExecutor.execute(() -> {
mCallback.onCustomLayoutChanged(mInstance, layout);
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2CallbackStub.java b/packages/MediaComponents/src/com/android/media/MediaController2Stub.java
similarity index 76%
rename from packages/MediaComponents/src/com/android/media/MediaSession2CallbackStub.java
rename to packages/MediaComponents/src/com/android/media/MediaController2Stub.java
index b8e651e..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);
@@ -215,6 +279,27 @@
}
@Override
+ public void onAllowedCommandsChanged(Bundle commandsBundle) {
+ final MediaController2Impl controller;
+ try {
+ controller = getController();
+ } catch (IllegalStateException e) {
+ Log.w(TAG, "Don't fail silently here. Highly likely a bug");
+ return;
+ }
+ if (controller == null) {
+ // TODO(jaewan): Revisit here. Could be a bug
+ return;
+ }
+ CommandGroup commands = CommandGroup.fromBundle(controller.getContext(), commandsBundle);
+ if (commands == null) {
+ Log.w(TAG, "onAllowedCommandsChanged(): Ignoring null commands");
+ return;
+ }
+ controller.onAllowedCommandsChanged(commands);
+ }
+
+ @Override
public void onCustomCommand(Bundle commandBundle, Bundle args, ResultReceiver receiver) {
final MediaController2Impl controller;
try {
diff --git a/packages/MediaComponents/src/com/android/media/MediaItem2Impl.java b/packages/MediaComponents/src/com/android/media/MediaItem2Impl.java
index 252512c..c95b43f 100644
--- a/packages/MediaComponents/src/com/android/media/MediaItem2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaItem2Impl.java
@@ -31,27 +31,34 @@
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(Context context, String mediaId,
- DataSourceDesc dsd, MediaMetadata2 metadata, @Flags int flags) {
+ 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");
}
- if (dsd == null) {
- throw new IllegalArgumentException("dsd shouldn't be null");
- }
if (metadata != null && !TextUtils.equals(mediaId, metadata.getMediaId())) {
throw new IllegalArgumentException("metadata's id should be matched with the mediaid");
}
@@ -61,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);
}
/**
@@ -93,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;
}
@@ -105,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() {
@@ -186,10 +214,7 @@
}
@Override
- public Builder setDataSourceDesc_impl(@NonNull DataSourceDesc dataSourceDesc) {
- if (dataSourceDesc == null) {
- throw new IllegalArgumentException("dataSourceDesc shouldn't be null");
- }
+ public Builder setDataSourceDesc_impl(@Nullable DataSourceDesc dataSourceDesc) {
mDataSourceDesc = dataSourceDesc;
return mInstance;
}
diff --git a/packages/MediaComponents/src/com/android/media/MediaLibraryService2Impl.java b/packages/MediaComponents/src/com/android/media/MediaLibraryService2Impl.java
index a21fda6..b3512cc 100644
--- a/packages/MediaComponents/src/com/android/media/MediaLibraryService2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaLibraryService2Impl.java
@@ -24,6 +24,7 @@
import android.media.MediaLibraryService2.MediaLibrarySession.Builder;
import android.media.MediaLibraryService2.MediaLibrarySession.MediaLibrarySessionCallback;
import android.media.MediaPlayerBase;
+import android.media.MediaPlaylistAgent;
import android.media.MediaSession2;
import android.media.MediaSession2.ControllerInfo;
import android.media.MediaSessionService2;
@@ -67,10 +68,12 @@
public static class MediaLibrarySessionImpl extends MediaSession2Impl
implements MediaLibrarySessionProvider {
public MediaLibrarySessionImpl(Context context,
- MediaPlayerBase player, String id, VolumeProvider2 volumeProvider,
+ MediaPlayerBase player, String id, MediaPlaylistAgent playlistAgent,
+ VolumeProvider2 volumeProvider,
PendingIntent sessionActivity, Executor callbackExecutor,
MediaLibrarySessionCallback callback) {
- super(context, player, id, volumeProvider, sessionActivity, callbackExecutor, callback);
+ super(context, player, id, playlistAgent, volumeProvider, sessionActivity,
+ callbackExecutor, callback);
// Don't put any extra initialization here. Here's the reason.
// System service will recognize this session inside of the super constructor and would
// connect to this session assuming that initialization is finished. However, if any
@@ -138,8 +141,8 @@
@Override
public MediaLibrarySession build_impl() {
- return new MediaLibrarySessionImpl(mContext, mPlayer, mId, mVolumeProvider,
- mSessionActivity, mCallbackExecutor, mCallback).getInstance();
+ return new MediaLibrarySessionImpl(mContext, mPlayer, mId, mPlaylistAgent,
+ mVolumeProvider, mSessionActivity, mCallbackExecutor, mCallback).getInstance();
}
}
@@ -151,8 +154,7 @@
public LibraryRootImpl(Context context, LibraryRoot instance, String rootId,
Bundle extras) {
if (rootId == null) {
- throw new IllegalArgumentException("The root id in BrowserRoot cannot be null. " +
- "Use null for BrowserRoot instead.");
+ throw new IllegalArgumentException("rootId shouldn't be null.");
}
mInstance = instance;
mRootId = rootId;
diff --git a/packages/MediaComponents/src/com/android/media/MediaMetadata2Impl.java b/packages/MediaComponents/src/com/android/media/MediaMetadata2Impl.java
index 7b2ee32..286f5ee 100644
--- a/packages/MediaComponents/src/com/android/media/MediaMetadata2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaMetadata2Impl.java
@@ -123,11 +123,17 @@
@Override
public boolean containsKey_impl(String key) {
+ if (key == null) {
+ throw new IllegalArgumentException("key shouldn't be null");
+ }
return mBundle.containsKey(key);
}
@Override
public CharSequence getText_impl(@TextKey String key) {
+ if (key == null) {
+ throw new IllegalArgumentException("key shouldn't be null");
+ }
return mBundle.getCharSequence(key);
}
@@ -138,6 +144,9 @@
@Override
public String getString_impl(@TextKey String key) {
+ if (key == null) {
+ throw new IllegalArgumentException("key shouldn't be null");
+ }
CharSequence text = mBundle.getCharSequence(key);
if (text != null) {
return text.toString();
@@ -147,11 +156,17 @@
@Override
public long getLong_impl(@LongKey String key) {
+ if (key == null) {
+ throw new IllegalArgumentException("key shouldn't be null");
+ }
return mBundle.getLong(key, 0);
}
@Override
public Rating2 getRating_impl(@RatingKey String key) {
+ if (key == null) {
+ throw new IllegalArgumentException("key shouldn't be null");
+ }
// TODO(jaewan): Add backward compatibility
Rating2 rating = null;
try {
@@ -165,11 +180,17 @@
@Override
public float getFloat_impl(@FloatKey String key) {
+ if (key == null) {
+ throw new IllegalArgumentException("key shouldn't be null");
+ }
return mBundle.getFloat(key);
}
@Override
public Bitmap getBitmap_impl(@BitmapKey String key) {
+ if (key == null) {
+ throw new IllegalArgumentException("key shouldn't be null");
+ }
Bitmap bmp = null;
try {
bmp = mBundle.getParcelable(key);
@@ -207,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 {
@@ -221,7 +242,8 @@
mBundle = new Bundle();
}
- public BuilderImpl(Context context, MediaMetadata2.Builder instance, MediaMetadata2 source) {
+ public BuilderImpl(Context context, MediaMetadata2.Builder instance,
+ MediaMetadata2 source) {
if (source == null) {
throw new IllegalArgumentException("source shouldn't be null");
}
@@ -248,6 +270,9 @@
@Override
public Builder putText_impl(@TextKey String key, CharSequence value) {
+ if (key == null) {
+ throw new IllegalArgumentException("key shouldn't be null");
+ }
if (METADATA_KEYS_TYPE.containsKey(key)) {
if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_TEXT) {
throw new IllegalArgumentException("The " + key
@@ -260,6 +285,9 @@
@Override
public Builder putString_impl(@TextKey String key, String value) {
+ if (key == null) {
+ throw new IllegalArgumentException("key shouldn't be null");
+ }
if (METADATA_KEYS_TYPE.containsKey(key)) {
if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_TEXT) {
throw new IllegalArgumentException("The " + key
@@ -272,6 +300,9 @@
@Override
public Builder putLong_impl(@LongKey String key, long value) {
+ if (key == null) {
+ throw new IllegalArgumentException("key shouldn't be null");
+ }
if (METADATA_KEYS_TYPE.containsKey(key)) {
if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_LONG) {
throw new IllegalArgumentException("The " + key
@@ -284,6 +315,9 @@
@Override
public Builder putRating_impl(@RatingKey String key, Rating2 value) {
+ if (key == null) {
+ throw new IllegalArgumentException("key shouldn't be null");
+ }
if (METADATA_KEYS_TYPE.containsKey(key)) {
if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_RATING) {
throw new IllegalArgumentException("The " + key
@@ -296,6 +330,9 @@
@Override
public Builder putBitmap_impl(@BitmapKey String key, Bitmap value) {
+ if (key == null) {
+ throw new IllegalArgumentException("key shouldn't be null");
+ }
if (METADATA_KEYS_TYPE.containsKey(key)) {
if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_BITMAP) {
throw new IllegalArgumentException("The " + key
@@ -308,6 +345,9 @@
@Override
public Builder putFloat_impl(@FloatKey String key, float value) {
+ if (key == null) {
+ throw new IllegalArgumentException("key shouldn't be null");
+ }
if (METADATA_KEYS_TYPE.containsKey(key)) {
if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_FLOAT) {
throw new IllegalArgumentException("The " + key
@@ -329,7 +369,8 @@
return new MediaMetadata2Impl(mContext, mBundle).getInstance();
}
- private Bitmap scaleBitmap(Bitmap bmp, int maxSize) { float maxSizeF = maxSize;
+ private Bitmap scaleBitmap(Bitmap bmp, int maxSize) {
+ float maxSizeF = maxSize;
float widthScale = maxSizeF / bmp.getWidth();
float heightScale = maxSizeF / bmp.getHeight();
float scale = Math.min(widthScale, heightScale);
diff --git a/packages/MediaComponents/src/com/android/media/MediaPlaylistAgentImpl.java b/packages/MediaComponents/src/com/android/media/MediaPlaylistAgentImpl.java
new file mode 100644
index 0000000..bab29b7
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/media/MediaPlaylistAgentImpl.java
@@ -0,0 +1,188 @@
+/*
+ * 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.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.media.MediaItem2;
+import android.media.MediaMetadata2;
+import android.media.MediaPlaylistAgent;
+import android.media.MediaPlaylistAgent.PlaylistEventCallback;
+import android.media.update.MediaPlaylistAgentProvider;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+
+public class MediaPlaylistAgentImpl implements MediaPlaylistAgentProvider {
+ private static final String TAG = "MediaPlaylistAgent";
+
+ private final Context mContext;
+ private final MediaPlaylistAgent mInstance;
+
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
+ private final ArrayMap<PlaylistEventCallback, Executor> mCallbacks = new ArrayMap<>();
+
+ public MediaPlaylistAgentImpl(Context context, MediaPlaylistAgent instance) {
+ mContext = context;
+ mInstance = instance;
+ }
+
+ final public void registerPlaylistEventCallback_impl(
+ @NonNull @CallbackExecutor Executor executor, @NonNull PlaylistEventCallback callback) {
+ if (executor == null) {
+ throw new IllegalArgumentException("executor shouldn't be null");
+ }
+ if (callback == null) {
+ throw new IllegalArgumentException("callback shouldn't be null");
+ }
+
+ synchronized (mLock) {
+ if (mCallbacks.get(callback) != null) {
+ Log.w(TAG, "callback is already added. Ignoring.");
+ return;
+ }
+ mCallbacks.put(callback, executor);
+ }
+ }
+
+ final public void unregisterPlaylistEventCallback_impl(
+ @NonNull PlaylistEventCallback callback) {
+ if (callback == null) {
+ throw new IllegalArgumentException("callback shouldn't be null");
+ }
+ synchronized (mLock) {
+ mCallbacks.remove(callback);
+ }
+ }
+
+ final public void notifyPlaylistChanged_impl() {
+ ArrayMap<PlaylistEventCallback, Executor> callbacks = getCallbacks();
+ List<MediaItem2> playlist= mInstance.getPlaylist();
+ MediaMetadata2 metadata = mInstance.getPlaylistMetadata();
+ for (int i = 0; i < callbacks.size(); i++) {
+ final PlaylistEventCallback callback = callbacks.keyAt(i);
+ final Executor executor = callbacks.valueAt(i);
+ executor.execute(() -> callback.onPlaylistChanged(
+ mInstance, playlist, metadata));
+ }
+ }
+
+ final public void notifyPlaylistMetadataChanged_impl() {
+ ArrayMap<PlaylistEventCallback, Executor> callbacks = getCallbacks();
+ for (int i = 0; i < callbacks.size(); i++) {
+ final PlaylistEventCallback callback = callbacks.keyAt(i);
+ final Executor executor = callbacks.valueAt(i);
+ executor.execute(() -> callback.onPlaylistMetadataChanged(
+ mInstance, mInstance.getPlaylistMetadata()));
+ }
+ }
+
+ final public void notifyShuffleModeChanged_impl() {
+ ArrayMap<PlaylistEventCallback, Executor> callbacks = getCallbacks();
+ for (int i = 0; i < callbacks.size(); i++) {
+ final PlaylistEventCallback callback = callbacks.keyAt(i);
+ final Executor executor = callbacks.valueAt(i);
+ executor.execute(() -> callback.onShuffleModeChanged(
+ mInstance, mInstance.getShuffleMode()));
+ }
+ }
+
+ final public void notifyRepeatModeChanged_impl() {
+ ArrayMap<PlaylistEventCallback, Executor> callbacks = getCallbacks();
+ for (int i = 0; i < callbacks.size(); i++) {
+ final PlaylistEventCallback callback = callbacks.keyAt(i);
+ final Executor executor = callbacks.valueAt(i);
+ executor.execute(() -> callback.onRepeatModeChanged(
+ mInstance, mInstance.getRepeatMode()));
+ }
+ }
+
+ public @Nullable List<MediaItem2> getPlaylist_impl() {
+ // empty implementation
+ return null;
+ }
+
+ public void setPlaylist_impl(@NonNull List<MediaItem2> list,
+ @Nullable MediaMetadata2 metadata) {
+ // empty implementation
+ }
+
+ public @Nullable MediaMetadata2 getPlaylistMetadata_impl() {
+ // empty implementation
+ return null;
+ }
+
+ public void updatePlaylistMetadata_impl(@Nullable MediaMetadata2 metadata) {
+ // empty implementation
+ }
+
+ public void addPlaylistItem_impl(int index, @NonNull MediaItem2 item) {
+ // empty implementation
+ }
+
+ public void removePlaylistItem_impl(@NonNull MediaItem2 item) {
+ // empty implementation
+ }
+
+ public void replacePlaylistItem_impl(int index, @NonNull MediaItem2 item) {
+ // empty implementation
+ }
+
+ public void skipToPlaylistItem_impl(@NonNull MediaItem2 item) {
+ // empty implementation
+ }
+
+ public void skipToPreviousItem_impl() {
+ // empty implementation
+ }
+
+ public void skipToNextItem_impl() {
+ // empty implementation
+ }
+
+ public int getRepeatMode_impl() {
+ return MediaPlaylistAgent.REPEAT_MODE_NONE;
+ }
+
+ public void setRepeatMode_impl(int repeatMode) {
+ // empty implementation
+ }
+
+ public int getShuffleMode_impl() {
+ // empty implementation
+ return MediaPlaylistAgent.SHUFFLE_MODE_NONE;
+ }
+
+ public void setShuffleMode_impl(int shuffleMode) {
+ // empty implementation
+ }
+
+ private ArrayMap<PlaylistEventCallback, Executor> getCallbacks() {
+ ArrayMap<PlaylistEventCallback, Executor> callbacks = new ArrayMap<>();
+ synchronized (mLock) {
+ callbacks.putAll(mCallbacks);
+ }
+ return callbacks;
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java b/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
index c407e5a..b8c185a 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
@@ -21,17 +21,17 @@
import static android.media.SessionToken2.TYPE_SESSION;
import static android.media.SessionToken2.TYPE_SESSION_SERVICE;
-import android.Manifest.permission;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.media.AudioAttributes;
+import android.media.AudioFocusRequest;
import android.media.AudioManager;
+import android.media.DataSourceDesc;
import android.media.MediaController2;
import android.media.MediaController2.PlaybackInfo;
import android.media.MediaItem2;
@@ -39,7 +39,8 @@
import android.media.MediaMetadata2;
import android.media.MediaPlayerBase;
import android.media.MediaPlayerBase.PlayerEventCallback;
-import android.media.MediaPlaylistController;
+import android.media.MediaPlaylistAgent;
+import android.media.MediaPlaylistAgent.PlaylistEventCallback;
import android.media.MediaSession2;
import android.media.MediaSession2.Builder;
import android.media.MediaSession2.Command;
@@ -51,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;
@@ -64,11 +64,12 @@
import android.support.annotation.GuardedBy;
import android.text.TextUtils;
import android.util.ArrayMap;
-import android.util.ArraySet;
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;
@@ -88,6 +89,8 @@
private final AudioManager mAudioManager;
private final ArrayMap<PlayerEventCallback, Executor> mCallbacks = new ArrayMap<>();
private final PendingIntent mSessionActivity;
+ private final PlayerEventCallback mPlayerEventCallback;
+ private final PlaylistEventCallback mPlaylistEventCallback;
// mPlayer is set to null when the session is closed, and we shouldn't throw an exception
// nor leave log always for using mPlayer when it's null. Here's the reason.
@@ -108,25 +111,26 @@
@GuardedBy("mLock")
private MediaPlayerBase mPlayer;
@GuardedBy("mLock")
+ private MediaPlaylistAgent mPlaylistAgent;
+ @GuardedBy("mLock")
private VolumeProvider2 mVolumeProvider;
@GuardedBy("mLock")
private PlaybackInfo mPlaybackInfo;
- @GuardedBy("mLock")
- private MyEventCallback mEventCallback;
/**
* Can be only called by the {@link Builder#build()}.
- *
* @param context
* @param player
* @param id
+ * @param playlistAgent
* @param volumeProvider
* @param sessionActivity
* @param callbackExecutor
* @param callback
*/
public MediaSession2Impl(Context context, MediaPlayerBase player, String id,
- VolumeProvider2 volumeProvider, PendingIntent sessionActivity,
+ MediaPlaylistAgent playlistAgent, VolumeProvider2 volumeProvider,
+ PendingIntent sessionActivity,
Executor callbackExecutor, SessionCallback callback) {
// TODO(jaewan): Keep other params.
mInstance = createInstance();
@@ -140,6 +144,8 @@
mSessionActivity = sessionActivity;
mSessionStub = new MediaSession2Stub(this);
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ mPlayerEventCallback = new MyPlayerEventCallback(this);
+ mPlaylistEventCallback = new MyPlaylistEventCallback(this);
// Infer type from the id and package name.
String libraryService = getServiceName(context, MediaLibraryService2.SERVICE_INTERFACE, id);
@@ -158,7 +164,7 @@
mContext.getPackageName(), null, id, mSessionStub).getInstance();
}
- setPlayer(player, volumeProvider);
+ updatePlayer(player, playlistAgent, volumeProvider);
// Ask server for the sanity check, and starts
// Sanity check for making session ID unique 'per package' cannot be done in here.
@@ -203,31 +209,51 @@
}
@Override
- public void updatePlayer_impl(MediaPlayerBase player, MediaPlaylistController mpcl,
+ public void updatePlayer_impl(MediaPlayerBase player, MediaPlaylistAgent playlistAgent,
VolumeProvider2 volumeProvider) throws IllegalArgumentException {
ensureCallingThread();
if (player == null) {
throw new IllegalArgumentException("player shouldn't be null");
}
- // TODO(jaewan): Handle mpcl
- setPlayer(player, volumeProvider);
+ updatePlayer(player, playlistAgent, volumeProvider);
}
- private void setPlayer(MediaPlayerBase player, VolumeProvider2 volumeProvider) {
+ private void updatePlayer(MediaPlayerBase player, MediaPlaylistAgent agent,
+ VolumeProvider2 volumeProvider) {
+ final MediaPlayerBase oldPlayer;
+ final MediaPlaylistAgent oldAgent;
final PlaybackInfo info = createPlaybackInfo(volumeProvider, player.getAudioAttributes());
synchronized (mLock) {
- if (mPlayer != null && mEventCallback != null) {
- // This might not work for a poorly implemented player.
- mPlayer.unregisterPlayerEventCallback(mEventCallback);
- }
+ oldPlayer = mPlayer;
+ oldAgent = mPlaylistAgent;
mPlayer = player;
- mEventCallback = new MyEventCallback(this, player);
- player.registerPlayerEventCallback(mCallbackExecutor, mEventCallback);
+ // TODO(jaewan): Replace this with the proper default agent (b/74090741)
+ if (agent == null) {
+ agent = new MediaPlaylistAgent(mContext) {};
+ }
+ mPlaylistAgent = agent;
mVolumeProvider = volumeProvider;
mPlaybackInfo = info;
}
- mSessionStub.notifyPlaybackInfoChanged(info);
- notifyPlaybackStateChangedNotLocked(mInstance.getPlaybackState());
+ if (player != oldPlayer) {
+ player.registerPlayerEventCallback(mCallbackExecutor, mPlayerEventCallback);
+ if (oldPlayer != null) {
+ // Warning: Poorly implement player may ignore this
+ oldPlayer.unregisterPlayerEventCallback(mPlayerEventCallback);
+ }
+ }
+ if (agent != oldAgent) {
+ agent.registerPlaylistEventCallback(mCallbackExecutor, mPlaylistEventCallback);
+ if (oldAgent != null) {
+ // Warning: Poorly implement player may ignore this
+ oldAgent.unregisterPlaylistEventCallback(mPlaylistEventCallback);
+ }
+ }
+
+ if (oldPlayer != null) {
+ mSessionStub.notifyPlaybackInfoChanged(info);
+ notifyPlayerUpdatedNotLocked(oldPlayer);
+ }
}
private PlaybackInfo createPlaybackInfo(VolumeProvider2 volumeProvider, AudioAttributes attrs) {
@@ -280,12 +306,19 @@
// Invalidate previously published session stub.
mSessionStub.destroyNotLocked();
}
+ final MediaPlayerBase player;
+ final MediaPlaylistAgent agent;
synchronized (mLock) {
- if (mPlayer != null) {
- // close can be called multiple times
- mPlayer.unregisterPlayerEventCallback(mEventCallback);
- mPlayer = null;
- }
+ player = mPlayer;
+ mPlayer = null;
+ agent = mPlaylistAgent;
+ mPlaylistAgent = null;
+ }
+ if (player != null) {
+ player.unregisterPlayerEventCallback(mPlayerEventCallback);
+ }
+ if (agent != null) {
+ agent.unregisterPlaylistEventCallback(mPlaylistEventCallback);
}
}
@@ -295,6 +328,11 @@
}
@Override
+ public MediaPlaylistAgent getPlaylistAgent_impl() {
+ return mPlaylistAgent;
+ }
+
+ @Override
public VolumeProvider2 getVolumeProvider_impl() {
return mVolumeProvider;
}
@@ -310,7 +348,7 @@
}
@Override
- public void setAudioFocusRequest_impl(int focusGain) {
+ public void setAudioFocusRequest_impl(AudioFocusRequest afr) {
// implement
}
@@ -341,18 +379,16 @@
ensureCallingThread();
final MediaPlayerBase player = mPlayer;
if (player != null) {
- // TODO: Uncomment or remove
- //player.stop();
- player.pause();
+ player.reset();
} else if (DEBUG) {
Log.d(TAG, "API calls after the close()", new IllegalStateException());
}
}
@Override
- public void skipToPrevious_impl() {
+ public void skipToPreviousItem_impl() {
ensureCallingThread();
- // TODO: Uncomment or remove
+ // TODO(jaewan): Implement this (b/74175632)
/*
final MediaPlayerBase player = mPlayer;
if (player != null) {
@@ -365,14 +401,17 @@
}
@Override
- public void skipToNext_impl() {
+ public void skipToNextItem_impl() {
ensureCallingThread();
+ // TODO(jaewan): Implement this (b/74175632)
+ /*
final MediaPlayerBase player = mPlayer;
if (player != null) {
player.skipToNext();
} else if (DEBUG) {
Log.d(TAG, "API calls after the close()", new IllegalStateException());
}
+ */
}
@Override
@@ -428,74 +467,123 @@
@Override
public void setAllowedCommands_impl(ControllerInfo controller, CommandGroup commands) {
- // TODO(jaewan): Implement
+ if (controller == null) {
+ throw new IllegalArgumentException("controller shouldn't be null");
+ }
+ if (commands == null) {
+ throw new IllegalArgumentException("commands shouldn't be null");
+ }
+ mSessionStub.setAllowedCommands(controller, commands);
}
@Override
public void sendCustomCommand_impl(ControllerInfo controller, Command command, Bundle args,
ResultReceiver receiver) {
+ if (controller == null) {
+ throw new IllegalArgumentException("controller shouldn't be null");
+ }
+ if (command == null) {
+ throw new IllegalArgumentException("command shouldn't be null");
+ }
mSessionStub.sendCustomCommand(controller, command, args, receiver);
}
@Override
public void sendCustomCommand_impl(Command command, Bundle args) {
+ if (command == null) {
+ throw new IllegalArgumentException("command shouldn't be null");
+ }
mSessionStub.sendCustomCommand(command, args);
}
@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, use the SessionPlaylistController itf
- //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
public void addPlaylistItem_impl(int index, MediaItem2 item) {
- // TODO(jaewan): Implement
+ if (index < 0) {
+ throw new IllegalArgumentException("index shouldn't be negative");
+ }
+ if (item == null) {
+ throw new IllegalArgumentException("item shouldn't be null");
+ }
+ 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
public void removePlaylistItem_impl(MediaItem2 item) {
- // TODO(jaewan): Implement
- }
-
- @Override
- public void editPlaylistItem_impl(MediaItem2 item) {
- // TODO(jaewan): Implement
+ 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());
+ }
}
@Override
public void replacePlaylistItem_impl(int index, MediaItem2 item) {
- // TODO(jaewan): Implement
+ if (index < 0) {
+ throw new IllegalArgumentException("index shouldn't be negative");
+ }
+ if (item == null) {
+ throw new IllegalArgumentException("item shouldn't be null");
+ }
+ 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;
}
@@ -560,6 +648,9 @@
@Override
public void skipToPlaylistItem_impl(MediaItem2 item) {
ensureCallingThread();
+ if (item == null) {
+ throw new IllegalArgumentException("item shouldn't be null");
+ }
// TODO: Uncomment or remove
/*
final MediaPlayerBase player = mPlayer;
@@ -586,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
@@ -604,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
}
@@ -651,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
@@ -664,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) {
@@ -694,6 +801,10 @@
return mPlayer;
}
+ MediaPlaylistAgent getPlaylistAgent() {
+ return mPlaylistAgent;
+ }
+
Executor getCallbackExecutor() {
return mCallbackExecutor;
}
@@ -720,51 +831,114 @@
return mSessionActivity;
}
- private static class MyEventCallback extends PlayerEventCallback {
+ private static class MyPlayerEventCallback extends PlayerEventCallback {
private final WeakReference<MediaSession2Impl> mSession;
- private final MediaPlayerBase mPlayer;
- private MyEventCallback(MediaSession2Impl session, MediaPlayerBase player) {
+ private MyPlayerEventCallback(MediaSession2Impl session) {
mSession = new WeakReference<>(session);
- mPlayer = player;
}
- // TODO: Uncomment or remove
- /*
@Override
- public void onPlaybackStateChanged(PlaybackState2 state) {
- MediaSession2Impl session = mSession.get();
- if (mPlayer != session.mInstance.getPlayer()) {
- Log.w(TAG, "Unexpected playback state change notifications. Ignoring.",
- new IllegalStateException());
+ public void onCurrentDataSourceChanged(MediaPlayerBase mpb, DataSourceDesc dsd) {
+ MediaSession2Impl session = getSession();
+ if (session == null) {
return;
}
- if (DEBUG) {
- Log.d(TAG, "onPlaybackStateChanged from player, state=" + state);
- }
- session.notifyPlaybackStateChangedNotLocked(state);
+ 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 */);
+ });
}
- */
- // TODO: Uncomment or remove
- /*
@Override
- public void onError(String mediaId, int what, int extra) {
- MediaSession2Impl session = mSession.get();
- if (mPlayer != session.mInstance.getPlayer()) {
- Log.w(TAG, "Unexpected playback state change notifications. Ignoring.",
- new IllegalStateException());
+ public void onMediaPrepared(MediaPlayerBase mpb, DataSourceDesc dsd) {
+ MediaSession2Impl session = getSession();
+ if (session == null) {
return;
}
- if (DEBUG) {
- Log.d(TAG, "onError from player, mediaId=" + mediaId + ", what=" + what
- + ", extra=" + extra);
- }
- session.notifyErrorNotLocked(mediaId, what, extra);
+ 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 */);
+ });
}
- */
- //TODO implement the real PlayerEventCallback methods
+ @Override
+ public void onPlayerStateChanged(MediaPlayerBase mpb, int state) {
+ 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) {
+ 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;
+ }
+ }
+
+ private static class MyPlaylistEventCallback extends PlaylistEventCallback {
+ private final WeakReference<MediaSession2Impl> mSession;
+
+ private MyPlaylistEventCallback(MediaSession2Impl session) {
+ mSession = new WeakReference<>(session);
+ }
+
+ @Override
+ public void onPlaylistChanged(MediaPlaylistAgent playlistAgent, List<MediaItem2> list,
+ MediaMetadata2 metadata) {
+ final MediaSession2Impl session = mSession.get();
+ if (session == null) {
+ return;
+ }
+ session.notifyPlaylistChangedOnExecutor(playlistAgent, list, metadata);
+ }
+
+ @Override
+ public void onPlaylistMetadataChanged(MediaPlaylistAgent playlistAgent,
+ MediaMetadata2 metadata) {
+ final MediaSession2Impl session = mSession.get();
+ if (session == null) {
+ return;
+ }
+ session.notifyPlaylistMetadataChangedOnExecutor(playlistAgent, metadata);
+ }
+
+ @Override
+ public void onShuffleModeChanged(MediaPlaylistAgent playlistAgent, int shuffleMode) {
+ super.onShuffleModeChanged(playlistAgent, shuffleMode);
+ // TODO(jaewan): Handle this (b/74118768)
+ }
+
+ @Override
+ public void onRepeatModeChanged(MediaPlaylistAgent playlistAgent, int repeatMode) {
+ super.onRepeatModeChanged(playlistAgent, repeatMode);
+ // TODO(jaewan): Handle this (b/74118768)
+ }
}
public static final class CommandImpl implements CommandProvider {
@@ -798,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;
}
@@ -813,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);
@@ -825,6 +1003,9 @@
* @return a new Command instance from the Bundle
*/
public static Command fromBundle_impl(Context context, Bundle command) {
+ if (command == null) {
+ throw new IllegalArgumentException("command shouldn't be null");
+ }
int code = command.getInt(KEY_COMMAND_CODE);
if (code != COMMAND_CODE_CUSTOM) {
return new Command(context, code);
@@ -863,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;
@@ -875,26 +1066,61 @@
}
}
+ public CommandGroupImpl(Context context) {
+ mContext = context;
+ mInstance = new CommandGroup(this);
+ }
+
@Override
public void addCommand_impl(Command command) {
+ if (command == null) {
+ throw new IllegalArgumentException("command shouldn't be null");
+ }
mCommands.add(command);
}
@Override
public void addAllPredefinedCommands_impl() {
- final int COMMAND_CODE_MAX = 22;
- for (int i = 1; i <= 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");
+ }
+ }
+ }
}
}
@Override
public void removeCommand_impl(Command command) {
+ if (command == null) {
+ throw new IllegalArgumentException("command shouldn't be null");
+ }
mCommands.remove(command);
}
@Override
public boolean hasCommand_impl(Command command) {
+ if (command == null) {
+ throw new IllegalArgumentException("command shouldn't be null");
+ }
return mCommands.contains(command);
}
@@ -913,7 +1139,11 @@
@Override
public List<Command> getCommands_impl() {
- return mCommands;
+ return getCommands();
+ }
+
+ public List<Command> getCommands() {
+ return Collections.unmodifiableList(mCommands);
}
/**
@@ -964,38 +1194,27 @@
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");
+ }
+ if (callback == null) {
+ throw new IllegalArgumentException("callback shouldn't be null");
+ }
+
mInstance = instance;
mUid = uid;
mPackageName = packageName;
-
- // TODO(jaewan): Remove this workaround
- if ("com.android.server.media".equals(packageName)) {
- mIsTrusted = true;
- } else if (context.checkPermission(permission.MEDIA_CONTENT_CONTROL, pid, uid) ==
- PackageManager.PERMISSION_GRANTED) {
- mIsTrusted = true;
- } else {
- // TODO(jaewan): Also consider enabled notification listener.
- mIsTrusted = false;
- // System apps may bind across the user so uid can be differ.
- // Skip sanity check for the system app.
- try {
- int uidForPackage = context.getPackageManager().getPackageUid(packageName, 0);
- if (uid != uidForPackage) {
- throw new IllegalArgumentException("Illegal call from uid=" + uid +
- ", pkg=" + packageName + ". Expected uid" + uidForPackage);
- }
- } catch (NameNotFoundException e) {
- // Rethrow exception with different name because binder methods only accept
- // RemoteException.
- throw new IllegalArgumentException(e);
- }
- }
mControllerBinder = callback;
+ MediaSessionManager manager =
+ (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);
+ // Ask server whether the controller is trusted.
+ // App cannot know this because apps cannot query enabled notification listener for
+ // another package, but system server can do.
+ mIsTrusted = manager.isTrusted(uid, packageName);
}
@Override
@@ -1054,7 +1273,7 @@
return mControllerBinder.asBinder();
}
- public IMediaSession2Callback getControllerBinder() {
+ public IMediaController2 getControllerBinder() {
return mControllerBinder;
}
@@ -1287,6 +1506,7 @@
String mId;
Executor mCallbackExecutor;
C mCallback;
+ MediaPlaylistAgent mPlaylistAgent;
VolumeProvider2 mVolumeProvider;
PendingIntent mSessionActivity;
@@ -1307,24 +1527,33 @@
mId = "";
}
- public void setPlayer_impl(MediaPlayerBase player, MediaPlaylistController mplc,
- VolumeProvider2 volumeProvider) {
- // TODO: Use MediaPlaylistController
+ @Override
+ public void setPlayer_impl(MediaPlayerBase player) {
if (player == null) {
throw new IllegalArgumentException("player shouldn't be null");
}
mPlayer = player;
- mVolumeProvider = volumeProvider;
}
+ @Override
+ public void setPlaylistAgent_impl(MediaPlaylistAgent playlistAgent) {
+ if (playlistAgent == null) {
+ throw new IllegalArgumentException("playlistAgent shouldn't be null");
+ }
+ mPlaylistAgent = playlistAgent;
+ }
+
+ @Override
public void setVolumeProvider_impl(VolumeProvider2 volumeProvider) {
mVolumeProvider = volumeProvider;
}
+ @Override
public void setSessionActivity_impl(PendingIntent pi) {
mSessionActivity = pi;
}
+ @Override
public void setId_impl(String id) {
if (id == null) {
throw new IllegalArgumentException("id shouldn't be null");
@@ -1332,6 +1561,7 @@
mId = id;
}
+ @Override
public void setSessionCallback_impl(Executor executor, C callback) {
if (executor == null) {
throw new IllegalArgumentException("executor shouldn't be null");
@@ -1343,6 +1573,7 @@
mCallback = callback;
}
+ @Override
public abstract T build_impl();
}
@@ -1359,9 +1590,8 @@
if (mCallback == null) {
mCallback = new SessionCallback(mContext) {};
}
-
- return new MediaSession2Impl(mContext, mPlayer, mId, mVolumeProvider,
- mSessionActivity, mCallbackExecutor, mCallback).getInstance();
+ return new MediaSession2Impl(mContext, mPlayer, mId, mPlaylistAgent,
+ mVolumeProvider, mSessionActivity, mCallbackExecutor, mCallback).getInstance();
}
}
}
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java b/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
index 785248c..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,18 +319,20 @@
// 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();
final Bundle paramsBundle = (params != null) ? params.toBundle() : null;
final PendingIntent sessionActivity = session.getSessionActivity();
final List<MediaItem2> playlist =
- allowedCommands.hasCommand(MediaSession2.COMMAND_CODE_PLAYLIST_GET)
+ allowedCommands.hasCommand(MediaSession2.COMMAND_CODE_PLAYLIST_GET_LIST)
? session.getInstance().getPlaylist() : null;
final List<Bundle> playlistBundle;
if (playlist != null) {
@@ -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_SET_VOLUME);
if (session == null || controller == null) {
return;
}
session.getCallbackExecutor().execute(() -> {
- if (getControllerIfAble(caller, MediaSession2.COMMAND_CODE_SET_VOLUME) == null) {
- return;
- }
- // TODO(jaewan): Sanity check.
- Command command = new Command(
- session.getContext(), MediaSession2.COMMAND_CODE_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_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_SET_VOLUME);
- if (session == null || controller == null) {
- return;
- }
- session.getCallbackExecutor().execute(() -> {
- if (getControllerIfAble(caller, MediaSession2.COMMAND_CODE_SET_VOLUME) == null) {
- return;
- }
- // TODO(jaewan): Sanity check.
- Command command = new Command(
- session.getContext(), MediaSession2.COMMAND_CODE_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_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();
@@ -435,10 +442,10 @@
session.getInstance().stop();
break;
case MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_PREV_ITEM:
- session.getInstance().skipToPrevious();
+ session.getInstance().skipToPreviousItem();
break;
case MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_NEXT_ITEM:
- session.getInstance().skipToNext();
+ session.getInstance().skipToNextItem();
break;
case MediaSession2.COMMAND_CODE_PLAYBACK_PREPARE:
session.getInstance().prepare();
@@ -452,18 +459,21 @@
case MediaSession2.COMMAND_CODE_PLAYBACK_SEEK_TO:
session.getInstance().seekTo(args.getLong(ARGUMENT_KEY_POSITION));
break;
- case MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_TO_PLAYLIST_ITEM:
+ case MediaSession2.COMMAND_CODE_PLAYLIST_SKIP_TO_PLAYLIST_ITEM:
// TODO(jaewan): Implement
/*
session.getInstance().skipToPlaylistItem(
args.getInt(ARGUMENT_KEY_ITEM_INDEX));
*/
break;
+ // TODO(jaewan): Remove (b/74116823)
+ /*
case MediaSession2.COMMAND_CODE_PLAYBACK_SET_PLAYLIST_PARAMS:
session.getInstance().setPlaylistParams(
PlaylistParams.fromBundle(session.getContext(),
args.getBundle(ARGUMENT_KEY_PLAYLIST_PARAMS)));
break;
+ */
default:
// TODO(jaewan): Resend unknown (new) commands through the custom command.
}
@@ -471,13 +481,18 @@
}
@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) {
return;
}
final Command command = Command.fromBundle(session.getContext(), commandBundle);
+ if (command == null) {
+ Log.w(TAG, "sendCustomCommand(): Ignoring null command from "
+ + getControllerIfAble(caller));
+ return;
+ }
final ControllerInfo controller = getControllerIfAble(caller, command);
if (controller == null) {
return;
@@ -492,36 +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;
- }
- 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;
- }
- 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(),
@@ -530,17 +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;
- }
- 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(),
@@ -549,17 +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;
- }
- 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);
@@ -567,17 +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;
- }
- 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(),
@@ -586,16 +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;
- }
- 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(),
@@ -604,44 +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;
- }
- 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);
- session.getCallback().onSetRating(session.getInstance(),
- controller, mediaId, rating);
+ 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 getBrowserRoot(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 {
@@ -656,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(),
@@ -686,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(),
@@ -734,45 +753,27 @@
}
@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;
- }
- 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);
+ session.getCallback().onSearch(session.getInstance(), controller, query, extras);
});
}
@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) {
- if (TextUtils.isEmpty(query)) {
- if (DEBUG) {
- Log.d(TAG, "query shouldn't be empty");
+ onBrowserCommand(caller, (session, controller) -> {
+ if (TextUtils.isEmpty(query)) {
+ Log.w(TAG, "getSearchResult(): Ignoring empty query from " + controller);
+ 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) {
+ Log.w(TAG, "getSearchResult(): Ignoring negative page / pageSize."
+ + " page=" + page + " pageSize=" + pageSize + " from " + controller);
return;
}
List<MediaItem2> result = session.getCallback().onGetSearchResult(session.getInstance(),
@@ -789,7 +790,6 @@
bundleList.add(item == null ? null : item.toBundle());
}
}
-
try {
caller.onGetSearchResultDone(query, page, pageSize, bundleList, extras);
} catch (RemoteException e) {
@@ -800,16 +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;
- }
- 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(),
@@ -826,15 +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;
- }
- 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);
@@ -860,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?
@@ -878,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;
}
@@ -897,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 IMediaController2 controllerBinder = getControllerBinderIfAble(
+ list.get(i), MediaSession2.COMMAND_CODE_PLAYLIST_GET_LIST);
+ if (controllerBinder != null) {
+ try {
+ 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 IMediaSession2Callback controllerBinder = getControllerBinderIfAble(
- list.get(i), MediaSession2.COMMAND_CODE_PLAYLIST_GET);
+ final IMediaController2 controllerBinder = getControllerBinderIfAble(
+ list.get(i), MediaSession2.COMMAND_CODE_PLAYLIST_GET_LIST_METADATA);
if (controllerBinder != null) {
try {
- controllerBinder.onPlaylistChanged(bundleList);
+ controllerBinder.onPlaylistMetadataChanged(metadataBundle);
} catch (RemoteException e) {
Log.w(TAG, "Controller is gone", e);
// TODO(jaewan): What to do when the controller is gone?
@@ -925,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;
}
@@ -941,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;
}
@@ -955,6 +1026,22 @@
}
}
+ public void setAllowedCommands(ControllerInfo controller, CommandGroup commands) {
+ synchronized (mLock) {
+ mAllowedCommandGroupMap.put(controller, commands);
+ }
+ final IMediaController2 controllerBinder = getControllerBinderIfAble(controller);
+ if (controllerBinder == null) {
+ return;
+ }
+ try {
+ controllerBinder.onAllowedCommandsChanged(commands.toBundle());
+ } catch (RemoteException e) {
+ Log.w(TAG, "Controller is gone", e);
+ // TODO(jaewan): What to do when the controller is gone?
+ }
+ }
+
public void sendCustomCommand(ControllerInfo controller, Command command, Bundle args,
ResultReceiver receiver) {
if (receiver != null && controller == null) {
@@ -979,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;
}
@@ -998,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;
}
@@ -1032,7 +1119,7 @@
return;
}
}
- final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(controller);
+ final IMediaController2 controllerBinder = getControllerBinderIfAble(controller);
if (controller == null) {
return;
}
@@ -1042,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 4537d5f..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);
@@ -138,19 +137,10 @@
private class SessionServiceEventCallback extends PlayerEventCallback {
@Override
- public void onPlayerStateChanged(MediaPlayerBase mpb, int state) {
+ public void onPlayerStateChanged(MediaPlayerBase player, int state) {
// 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/RoutePlayer.java b/packages/MediaComponents/src/com/android/media/RoutePlayer.java
new file mode 100644
index 0000000..9450d34
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/media/RoutePlayer.java
@@ -0,0 +1,169 @@
+/*
+ * 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.DataSourceDesc;
+import android.media.session.MediaSession;
+import android.media.session.PlaybackState;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.annotation.RequiresApi;
+
+import com.android.support.mediarouter.media.MediaItemStatus;
+import com.android.support.mediarouter.media.MediaRouter;
+import com.android.support.mediarouter.media.MediaSessionStatus;
+import com.android.support.mediarouter.media.RemotePlaybackClient;
+import com.android.support.mediarouter.media.RemotePlaybackClient.ItemActionCallback;
+import com.android.support.mediarouter.media.RemotePlaybackClient.SessionActionCallback;
+import com.android.support.mediarouter.media.RemotePlaybackClient.StatusCallback;
+
+import java.util.Map;
+
+@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+public class RoutePlayer extends MediaSession.Callback {
+ public static final long PLAYBACK_ACTIONS = PlaybackState.ACTION_PAUSE
+ | PlaybackState.ACTION_PLAY | PlaybackState.ACTION_SEEK_TO
+ | PlaybackState.ACTION_FAST_FORWARD | PlaybackState.ACTION_REWIND;
+
+ private RemotePlaybackClient mClient;
+ private String mSessionId;
+ private String mItemId;
+ private PlayerEventCallback mCallback;
+
+ private StatusCallback mStatusCallback = new StatusCallback() {
+ @Override
+ public void onItemStatusChanged(Bundle data,
+ String sessionId, MediaSessionStatus sessionStatus,
+ String itemId, MediaItemStatus itemStatus) {
+ updateSessionStatus(sessionId, sessionStatus);
+ updateItemStatus(itemId, itemStatus);
+ }
+ };
+
+ public RoutePlayer(Context context, MediaRouter.RouteInfo route) {
+ mClient = new RemotePlaybackClient(context, route);
+ mClient.setStatusCallback(mStatusCallback);
+ mClient.startSession(null, new SessionActionCallback() {
+ @Override
+ public void onResult(Bundle data,
+ String sessionId, MediaSessionStatus sessionStatus) {
+ updateSessionStatus(sessionId, sessionStatus);
+ }
+ });
+ }
+
+ @Override
+ public void onPlay() {
+ mClient.resume(null, new SessionActionCallback() {
+ @Override
+ public void onResult(Bundle data,
+ String sessionId, MediaSessionStatus sessionStatus) {
+ updateSessionStatus(sessionId, sessionStatus);
+ }
+ });
+ }
+
+ @Override
+ public void onPause() {
+ mClient.pause(null, new SessionActionCallback() {
+ @Override
+ public void onResult(Bundle data,
+ String sessionId, MediaSessionStatus sessionStatus) {
+ updateSessionStatus(sessionId, sessionStatus);
+ }
+ });
+ }
+
+ @Override
+ public void onSeekTo(long pos) {
+ mClient.seek(mItemId, pos, null, new ItemActionCallback() {
+ @Override
+ public void onResult(Bundle data,
+ String sessionId, MediaSessionStatus sessionStatus,
+ String itemId, MediaItemStatus itemStatus) {
+ updateSessionStatus(sessionId, sessionStatus);
+ updateItemStatus(itemId, itemStatus);
+ }
+ });
+ }
+
+ @Override
+ public void onStop() {
+ mClient.stop(null, new SessionActionCallback() {
+ @Override
+ public void onResult(Bundle data,
+ String sessionId, MediaSessionStatus sessionStatus) {
+ updateSessionStatus(sessionId, sessionStatus);
+ }
+ });
+ }
+
+ public void setPlayerEventCallback(PlayerEventCallback callback) {
+ mCallback = callback;
+ }
+
+ public void openVideo(DataSourceDesc dsd) {
+ mClient.play(dsd.getUri(), "video/mp4", null, 0, null, new ItemActionCallback() {
+ @Override
+ public void onResult(Bundle data,
+ String sessionId, MediaSessionStatus sessionStatus,
+ String itemId, MediaItemStatus itemStatus) {
+ updateSessionStatus(sessionId, sessionStatus);
+ updateItemStatus(itemId, itemStatus);
+ playInternal(dsd.getUri());
+ }
+ });
+ }
+
+ public void release() {
+ if (mClient != null) {
+ mClient.release();
+ mClient = null;
+ }
+ if (mCallback != null) {
+ mCallback = null;
+ }
+ }
+
+ private void playInternal(Uri uri) {
+ mClient.play(uri, "video/mp4", null, 0, null, new ItemActionCallback() {
+ @Override
+ public void onResult(Bundle data,
+ String sessionId, MediaSessionStatus sessionStatus,
+ String itemId, MediaItemStatus itemStatus) {
+ updateSessionStatus(sessionId, sessionStatus);
+ updateItemStatus(itemId, itemStatus);
+ }
+ });
+ }
+
+ private void updateSessionStatus(String sessionId, MediaSessionStatus sessionStatus) {
+ mSessionId = sessionId;
+ }
+
+ private void updateItemStatus(String itemId, MediaItemStatus itemStatus) {
+ mItemId = itemId;
+ if (itemStatus == null || mCallback == null) return;
+ mCallback.onPlayerStateChanged(itemStatus);
+ }
+
+ public static abstract class PlayerEventCallback {
+ public void onPlayerStateChanged(MediaItemStatus itemStatus) { }
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/media/SessionToken2Impl.java b/packages/MediaComponents/src/com/android/media/SessionToken2Impl.java
index 3965ccb..723622e 100644
--- a/packages/MediaComponents/src/com/android/media/SessionToken2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/SessionToken2Impl.java
@@ -59,10 +59,10 @@
public SessionToken2Impl(Context context, SessionToken2 instance,
String packageName, String serviceName, int uid) {
if (TextUtils.isEmpty(packageName)) {
- throw new IllegalArgumentException("package name shouldn't be null");
+ throw new IllegalArgumentException("packageName shouldn't be empty");
}
if (TextUtils.isEmpty(serviceName)) {
- throw new IllegalArgumentException("service name shouldn't be null");
+ throw new IllegalArgumentException("serviceName shouldn't be empty");
}
mInstance = instance;
// Calculate uid if it's not specified.
diff --git a/packages/MediaComponents/src/com/android/media/VolumeProvider2Impl.java b/packages/MediaComponents/src/com/android/media/VolumeProvider2Impl.java
index 5af3ddf..156bcae 100644
--- a/packages/MediaComponents/src/com/android/media/VolumeProvider2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/VolumeProvider2Impl.java
@@ -15,6 +15,10 @@
*/
package com.android.media;
+import static android.media.VolumeProvider2.VOLUME_CONTROL_ABSOLUTE;
+import static android.media.VolumeProvider2.VOLUME_CONTROL_FIXED;
+import static android.media.VolumeProvider2.VOLUME_CONTROL_RELATIVE;
+
import android.content.Context;
import android.media.VolumeProvider2;
import android.media.update.VolumeProvider2Provider;
@@ -31,6 +35,18 @@
public VolumeProvider2Impl(Context context, VolumeProvider2 instance,
@VolumeProvider2.ControlType int controlType, int maxVolume, int currentVolume) {
+ if (controlType != VOLUME_CONTROL_FIXED && controlType != VOLUME_CONTROL_RELATIVE
+ && controlType != VOLUME_CONTROL_ABSOLUTE) {
+ throw new IllegalArgumentException("wrong controlType " + controlType);
+ }
+ if (maxVolume < 0 || currentVolume < 0) {
+ throw new IllegalArgumentException("volume shouldn't be negative"
+ + ", maxVolume=" + maxVolume + ", currentVolume=" + currentVolume);
+ }
+ if (currentVolume > maxVolume) {
+ throw new IllegalArgumentException("currentVolume shouldn't be greater than maxVolume"
+ + ", maxVolume=" + maxVolume + ", currentVolume=" + currentVolume);
+ }
mContext = context;
mInstance = instance;
mControlType = controlType;
@@ -55,6 +71,10 @@
@Override
public void setCurrentVolume_impl(int currentVolume) {
+ if (currentVolume < 0) {
+ throw new IllegalArgumentException("currentVolume shouldn't be negative"
+ + ", currentVolume=" + currentVolume);
+ }
mCurrentVolume = currentVolume;
if (mCallback != null) {
mCallback.onVolumeChanged(mInstance);
diff --git a/packages/MediaComponents/src/com/android/media/update/ApiFactory.java b/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
index 9a01ade..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,7 @@
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;
import android.media.MediaSession2.CommandGroup;
@@ -40,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;
@@ -50,13 +48,13 @@
import android.media.update.MediaItem2Provider;
import android.media.update.MediaLibraryService2Provider.LibraryRootProvider;
import android.media.update.MediaMetadata2Provider;
+import android.media.update.MediaPlaylistAgentProvider;
import android.media.update.MediaSession2Provider;
import android.media.update.MediaSession2Provider.BuilderBaseProvider;
import android.media.update.MediaSession2Provider.CommandButtonProvider.BuilderProvider;
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;
@@ -69,17 +67,17 @@
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;
import com.android.media.MediaLibraryService2Impl;
import com.android.media.MediaLibraryService2Impl.LibraryRootImpl;
import com.android.media.MediaMetadata2Impl;
+import com.android.media.MediaPlaylistAgentImpl;
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;
@@ -139,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
@@ -290,15 +288,8 @@
}
@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);
+ 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 1940953..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,12 +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 sending command to show subtitle to MediaSession.
+ static final String COMMAND_SHOW_SUBTITLE = "showSubtitle";
+ // 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;
@@ -93,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;
@@ -100,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;
@@ -108,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;
@@ -123,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;
@@ -136,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;
@@ -159,7 +197,6 @@
mResources = ApiHelper.getLibResources();
// Inflate MediaControlView2 from XML
mRoot = makeControllerView();
- mRoot.addOnLayoutChangeListener(mTitleBarLayoutChangeListener);
mInstance.addView(mRoot);
}
@@ -210,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);
}
@@ -243,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);
@@ -356,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;
}
@@ -408,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) {
@@ -431,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) {
@@ -457,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);
@@ -487,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);
@@ -567,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) {
@@ -737,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(MediaControlView2.COMMAND_SHOW_SUBTITLE, null, null);
- mSubtitleIsEnabled = true;
- } else {
- mSubtitleButton.setImageDrawable(
- mResources.getDrawable(R.drawable.ic_media_subtitle_disabled, null));
- mController.sendCommand(MediaControlView2.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);
}
};
@@ -768,7 +827,7 @@
}
Bundle args = new Bundle();
args.putBoolean(ARGUMENT_KEY_FULLSCREEN, isEnteringFullScreen);
- mController.sendCommand(MediaControlView2.COMMAND_SET_FULLSCREEN, args, null);
+ mController.sendCommand(MediaControlView2Impl.COMMAND_SET_FULLSCREEN, args, null);
mIsFullScreen = isEnteringFullScreen;
}
@@ -790,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);
}
};
@@ -811,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);
}
};
@@ -946,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) {
@@ -1058,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;
@@ -1115,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);
@@ -1138,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
@@ -1177,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));
@@ -1199,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/VideoSurfaceView.java b/packages/MediaComponents/src/com/android/widget/VideoSurfaceView.java
index 8577123..fc92e85 100644
--- a/packages/MediaComponents/src/com/android/widget/VideoSurfaceView.java
+++ b/packages/MediaComponents/src/com/android/widget/VideoSurfaceView.java
@@ -18,7 +18,7 @@
import android.content.Context;
import android.graphics.Rect;
-import android.media.MediaPlayer;
+import android.media.MediaPlayer2;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.util.Log;
@@ -33,7 +33,7 @@
private static final boolean DEBUG = true; // STOPSHIP: Log.isLoggable(TAG, Log.DEBUG);
private SurfaceHolder mSurfaceHolder = null;
private SurfaceListener mSurfaceListener = null;
- private MediaPlayer mMediaPlayer;
+ private MediaPlayer2 mMediaPlayer;
// A flag to indicate taking over other view should be proceed.
private boolean mIsTakingOverOldView;
private VideoViewInterface mOldView;
@@ -62,7 +62,7 @@
////////////////////////////////////////////////////
@Override
- public boolean assignSurfaceToMediaPlayer(MediaPlayer mp) {
+ public boolean assignSurfaceToMediaPlayer(MediaPlayer2 mp) {
Log.d(TAG, "assignSurfaceToMediaPlayer(): mSurfaceHolder: " + mSurfaceHolder);
if (mp == null || !hasAvailableSurface()) {
return false;
@@ -82,7 +82,7 @@
}
@Override
- public void setMediaPlayer(MediaPlayer mp) {
+ public void setMediaPlayer(MediaPlayer2 mp) {
mMediaPlayer = mp;
if (mIsTakingOverOldView) {
takeOver(mOldView);
diff --git a/packages/MediaComponents/src/com/android/widget/VideoTextureView.java b/packages/MediaComponents/src/com/android/widget/VideoTextureView.java
index 69a4ebe..024a3aa 100644
--- a/packages/MediaComponents/src/com/android/widget/VideoTextureView.java
+++ b/packages/MediaComponents/src/com/android/widget/VideoTextureView.java
@@ -18,7 +18,7 @@
import android.content.Context;
import android.graphics.SurfaceTexture;
-import android.media.MediaPlayer;
+import android.media.MediaPlayer2;
import android.support.annotation.NonNull;
import android.support.annotation.RequiresApi;
import android.util.AttributeSet;
@@ -38,7 +38,7 @@
private SurfaceTexture mSurfaceTexture;
private Surface mSurface;
private SurfaceListener mSurfaceListener;
- private MediaPlayer mMediaPlayer;
+ private MediaPlayer2 mMediaPlayer;
// A flag to indicate taking over other view should be proceed.
private boolean mIsTakingOverOldView;
private VideoViewInterface mOldView;
@@ -66,7 +66,7 @@
////////////////////////////////////////////////////
@Override
- public boolean assignSurfaceToMediaPlayer(MediaPlayer mp) {
+ public boolean assignSurfaceToMediaPlayer(MediaPlayer2 mp) {
Log.d(TAG, "assignSurfaceToMediaPlayer(): mSurfaceTexture: " + mSurfaceTexture);
if (mp == null || !hasAvailableSurface()) {
// Surface is not ready.
@@ -87,7 +87,7 @@
}
@Override
- public void setMediaPlayer(MediaPlayer mp) {
+ public void setMediaPlayer(MediaPlayer2 mp) {
mMediaPlayer = mp;
if (mIsTakingOverOldView) {
takeOver(mOldView);
diff --git a/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java b/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
index d23395c..264f632 100644
--- a/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
+++ b/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
@@ -16,16 +16,15 @@
package com.android.widget;
-import android.annotation.NonNull;
import android.content.Context;
-import android.content.res.Resources;
import android.media.AudioAttributes;
import android.media.AudioFocusRequest;
import android.media.AudioManager;
import android.media.DataSourceDesc;
import android.media.MediaMetadata;
-import android.media.MediaPlayer;
-import android.media.MediaPlayerBase;
+import android.media.MediaPlayer2;
+import android.media.MediaPlayer2.MediaPlayer2EventCallback;
+import android.media.MediaPlayer2Impl;
import android.media.Cea708CaptionRenderer;
import android.media.ClosedCaptionRenderer;
import android.media.MediaItem2;
@@ -45,6 +44,7 @@
import android.media.update.VideoView2Provider;
import android.media.update.ViewGroupProvider;
import android.net.Uri;
+import android.os.AsyncTask;
import android.os.Bundle;
import android.os.ResultReceiver;
import android.support.annotation.Nullable;
@@ -58,15 +58,12 @@
import android.widget.MediaControlView2;
import android.widget.VideoView2;
-import com.android.media.update.ApiHelper;
-import com.android.media.update.R;
+import com.android.media.RoutePlayer;
+import com.android.support.mediarouter.media.MediaItemStatus;
+import com.android.support.mediarouter.media.MediaControlIntent;
import com.android.support.mediarouter.media.MediaRouter;
import com.android.support.mediarouter.media.MediaRouteSelector;
-import com.android.support.mediarouter.media.RemotePlaybackClient;
-import com.android.support.mediarouter.media.MediaItemStatus;
-import com.android.support.mediarouter.media.MediaSessionStatus;
-import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -89,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;
@@ -103,13 +101,11 @@
private VideoTextureView mTextureView;
private VideoSurfaceView mSurfaceView;
- private MediaPlayer mMediaPlayer;
+ private MediaPlayer2 mMediaPlayer;
+ private DataSourceDesc mDsd;
private MediaControlView2 mMediaControlView;
private MediaSession mMediaSession;
private MediaController mMediaController;
- private MediaSession.Callback mRouteSessionCallback = new RouteSessionCallback();
- private MediaRouter mMediaRouter;
- private MediaRouteSelector mRouteSelector;
private Metadata mMetadata;
private MediaMetadata2 mMediaMetadata;
private boolean mNeedUpdateMediaType;
@@ -142,9 +138,83 @@
// 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;
+ private MediaRouteSelector mRouteSelector;
+ private MediaRouter.RouteInfo mRoute;
+ private RoutePlayer mRoutePlayer;
+
+ private final MediaRouter.Callback mRouterCallback = new MediaRouter.Callback() {
+ @Override
+ public void onRouteSelected(MediaRouter router, MediaRouter.RouteInfo route) {
+ if (route.supportsControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) {
+ // Stop local playback (if necessary)
+ resetPlayer();
+ mRoute = route;
+ mRoutePlayer = new RoutePlayer(mInstance.getContext(), route);
+ mRoutePlayer.setPlayerEventCallback(new RoutePlayer.PlayerEventCallback() {
+ @Override
+ public void onPlayerStateChanged(MediaItemStatus itemStatus) {
+ PlaybackState.Builder psBuilder = new PlaybackState.Builder();
+ psBuilder.setActions(RoutePlayer.PLAYBACK_ACTIONS);
+ long position = itemStatus.getContentPosition();
+ switch (itemStatus.getPlaybackState()) {
+ case MediaItemStatus.PLAYBACK_STATE_PENDING:
+ psBuilder.setState(PlaybackState.STATE_NONE, position, 0);
+ mCurrentState = STATE_IDLE;
+ break;
+ case MediaItemStatus.PLAYBACK_STATE_PLAYING:
+ psBuilder.setState(PlaybackState.STATE_PLAYING, position, 1);
+ mCurrentState = STATE_PLAYING;
+ break;
+ case MediaItemStatus.PLAYBACK_STATE_PAUSED:
+ psBuilder.setState(PlaybackState.STATE_PAUSED, position, 0);
+ mCurrentState = STATE_PAUSED;
+ break;
+ case MediaItemStatus.PLAYBACK_STATE_BUFFERING:
+ psBuilder.setState(PlaybackState.STATE_BUFFERING, position, 0);
+ mCurrentState = STATE_PAUSED;
+ break;
+ case MediaItemStatus.PLAYBACK_STATE_FINISHED:
+ psBuilder.setState(PlaybackState.STATE_STOPPED, position, 0);
+ mCurrentState = STATE_PLAYBACK_COMPLETED;
+ break;
+ }
+
+ PlaybackState pbState = psBuilder.build();
+ mMediaSession.setPlaybackState(pbState);
+
+ MediaMetadata.Builder mmBuilder = new MediaMetadata.Builder();
+ mmBuilder.putLong(MediaMetadata.METADATA_KEY_DURATION,
+ itemStatus.getContentDuration());
+ mMediaSession.setMetadata(mmBuilder.build());
+ }
+ });
+ // Start remote playback (if necessary)
+ mRoutePlayer.openVideo(mDsd);
+ }
+ }
+
+ @Override
+ public void onRouteUnselected(MediaRouter router, MediaRouter.RouteInfo route, int reason) {
+ if (mRoute != null && mRoutePlayer != null) {
+ mRoutePlayer.release();
+ mRoutePlayer = null;
+ }
+ if (mRoute == route) {
+ mRoute = null;
+ }
+ if (reason != MediaRouter.UNSELECT_REASON_ROUTE_CHANGED) {
+ // TODO: Resume local playback (if necessary)
+ openVideo(mDsd);
+ }
+ }
+ };
+
public VideoView2Impl(VideoView2 instance,
ViewGroupProvider superProvider, ViewGroupProvider privateProvider) {
super(instance, superProvider, privateProvider);
@@ -196,7 +266,6 @@
mSubtitleView.setBackgroundColor(0);
mInstance.addView(mSubtitleView);
- // TODO: Need a common namespace for attributes those are defined in updatable library.
boolean enableControlView = (attrs == null) || attrs.getAttributeBooleanValue(
"http://schemas.android.com/apk/res/android",
"enableControlView", true);
@@ -219,16 +288,20 @@
Log.d(TAG, "viewType attribute is textureView.");
// TODO: implement
}
+
+ MediaRouteSelector.Builder builder = new MediaRouteSelector.Builder();
+ builder.addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
+ builder.addControlCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO);
+ builder.addControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO);
+ mRouteSelector = builder.build();
}
@Override
public void setMediaControlView2_impl(MediaControlView2 mediaControlView, long intervalMs) {
mMediaControlView = mediaControlView;
mShowControllerIntervalMs = intervalMs;
- if (mRouteSelector != null) {
- ((MediaControlView2Impl) mMediaControlView.getProvider())
- .setRouteSelector(mRouteSelector);
- }
+ // TODO: Call MediaControlView2.setRouteSelector only when cast availalbe.
+ ((MediaControlView2Impl) mMediaControlView.getProvider()).setRouteSelector(mRouteSelector);
if (mInstance.isAttachedToWindow()) {
attachMediaControlView();
@@ -328,30 +401,6 @@
}
@Override
- public void setRouteAttributes_impl(List<String> routeCategories, MediaPlayerBase player) {
- // TODO: implement this.
- }
-
- @Override
- public void setRouteAttributes_impl(@NonNull List<String> routeCategories,
- MediaSession.Callback sessionPlayer) {
- MediaRouteSelector.Builder builder = new MediaRouteSelector.Builder();
- for (String category : routeCategories) {
- builder.addControlCategory(category);
- }
- mRouteSelector = builder.build();
- if (mMediaControlView != null) {
- ((MediaControlView2Impl) mMediaControlView.getProvider())
- .setRouteSelector(mRouteSelector);
- }
- mMediaRouter = MediaRouter.getInstance(mInstance.getContext());
- mRouteSessionCallback = sessionPlayer;
- if (mMediaSession != null) {
- mMediaRouter.setMediaSession(mMediaSession);
- }
- }
-
- @Override
public void setVideoPath_impl(String path) {
mInstance.setVideoUri(Uri.parse(path));
}
@@ -363,8 +412,9 @@
@Override
public void setVideoUri_impl(Uri uri, Map<String, String> headers) {
- mSeekWhenPrepared = 0;
- openVideo(uri, headers);
+ DataSourceDesc.Builder builder = new DataSourceDesc.Builder();
+ builder.setDataSource(mInstance.getContext(), uri, headers, null);
+ mInstance.setDataSource(builder.build());
}
@Override
@@ -374,7 +424,9 @@
@Override
public void setDataSource_impl(DataSourceDesc dsd) {
- // TODO: implement this
+ mDsd = dsd;
+ mSeekWhenPrepared = 0;
+ openVideo(dsd);
}
@Override
@@ -431,10 +483,11 @@
// Create MediaSession
mMediaSession = new MediaSession(mInstance.getContext(), "VideoView2MediaSession");
mMediaSession.setCallback(new MediaSessionCallback());
+ mMediaSession.setActive(true);
mMediaController = mMediaSession.getController();
- if (mMediaRouter != null) {
- mMediaRouter.setMediaSession(mMediaSession);
- }
+ mMediaRouter = MediaRouter.getInstance(mInstance.getContext());
+ mMediaRouter.setMediaSession(mMediaSession);
+ mMediaRouter.addCallback(mRouteSelector, mRouterCallback);
attachMediaControlView();
// TODO: remove this after moving MediaSession creating code inside initializing VideoView2
if (mCurrentState == STATE_PREPARED) {
@@ -545,21 +598,27 @@
}
private boolean isInPlaybackState() {
- return (mMediaPlayer != null
+ return (mMediaPlayer != null || mRoutePlayer != null)
&& mCurrentState != STATE_ERROR
&& mCurrentState != STATE_IDLE
- && mCurrentState != STATE_PREPARING);
+ && mCurrentState != STATE_PREPARING;
}
private boolean needToStart() {
- return (mMediaPlayer != null
+ return (mMediaPlayer != null || mRoutePlayer != null)
&& mCurrentState != STATE_PLAYING
- && mTargetState == STATE_PLAYING);
+ && mTargetState == STATE_PLAYING;
}
- // Creates a MediaPlayer instance and prepare playback.
- private void openVideo(Uri uri, Map<String, String> headers) {
+ // Creates a MediaPlayer2 instance and prepare playback.
+ private void openVideo(DataSourceDesc dsd) {
+ Uri uri = dsd.getUri();
+ Map<String, String> headers = dsd.getUriHeaders();
resetPlayer();
+ if (isRemotePlayback()) {
+ mRoutePlayer.openVideo(dsd);
+ return;
+ }
if (mAudioFocusType != AudioManager.AUDIOFOCUS_NONE) {
// TODO this should have a focus listener
AudioFocusRequest focusRequest;
@@ -570,13 +629,13 @@
}
try {
- Log.d(TAG, "openVideo(): creating new MediaPlayer instance.");
- mMediaPlayer = new MediaPlayer();
+ Log.d(TAG, "openVideo(): creating new MediaPlayer2 instance.");
+ mMediaPlayer = new MediaPlayer2Impl();
mSurfaceView.setMediaPlayer(mMediaPlayer);
mTextureView.setMediaPlayer(mMediaPlayer);
mCurrentView.assignSurfaceToMediaPlayer(mMediaPlayer);
- // TODO: create SubtitleController in MediaPlayer, but we need
+ // TODO: create SubtitleController in MediaPlayer2, but we need
// a context for the subtitle renderers
final Context context = mInstance.getContext();
final SubtitleController controller = new SubtitleController(
@@ -588,23 +647,21 @@
controller.registerRenderer(new SRTRenderer(context));
mMediaPlayer.setSubtitleAnchor(
controller, (SubtitleController.Anchor) mSubtitleView);
- // TODO: Remove timed text related code later once relevant Renderer is defined.
- // This is just for debugging purpose.
- mMediaPlayer.setOnTimedTextListener(mTimedTextListener);
+ Executor executor = new Executor() {
+ @Override
+ public void execute(Runnable runnable) {
+ runnable.run();
+ }
+ };
+ mMediaPlayer.setMediaPlayer2EventCallback(executor, mMediaPlayer2Callback);
- mMediaPlayer.setOnPreparedListener(mPreparedListener);
- mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener);
- mMediaPlayer.setOnCompletionListener(mCompletionListener);
- mMediaPlayer.setOnErrorListener(mErrorListener);
- mMediaPlayer.setOnInfoListener(mInfoListener);
- mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener);
- mCurrentBufferPercentage = 0;
- mMediaPlayer.setDataSource(mInstance.getContext(), uri, headers);
+ mCurrentBufferPercentage = -1;
+ mMediaPlayer.setDataSource(dsd);
mMediaPlayer.setAudioAttributes(mAudioAttributes);
// we don't set the target state here either, but preserve the
// target state that was there before.
mCurrentState = STATE_PREPARING;
- mMediaPlayer.prepareAsync();
+ mMediaPlayer.prepare();
// Save file name as title since the file may not have a title Metadata.
mTitle = uri.getPath();
@@ -617,24 +674,12 @@
Log.d(TAG, "openVideo(). mCurrentState=" + mCurrentState
+ ", mTargetState=" + mTargetState);
}
- /*
- for (Pair<InputStream, MediaFormat> pending: mPendingSubtitleTracks) {
- try {
- mMediaPlayer.addSubtitleSource(pending.first, pending.second);
- } catch (IllegalStateException e) {
- mInfoListener.onInfo(
- mMediaPlayer, MediaPlayer.MEDIA_INFO_UNSUPPORTED_SUBTITLE, 0);
- }
- }
- */
- } catch (IOException | IllegalArgumentException ex) {
+ } catch (IllegalArgumentException ex) {
Log.w(TAG, "Unable to open content: " + uri, ex);
mCurrentState = STATE_ERROR;
mTargetState = STATE_ERROR;
- mErrorListener.onError(mMediaPlayer,
- MediaPlayer.MEDIA_ERROR_UNKNOWN, MediaPlayer.MEDIA_ERROR_IO);
- } finally {
- //mPendingSubtitleTracks.clear();
+ mMediaPlayer2Callback.onError(mMediaPlayer, dsd,
+ MediaPlayer2.MEDIA_ERROR_UNKNOWN, MediaPlayer2.MEDIA_ERROR_IO);
}
}
@@ -643,9 +688,18 @@
*/
private void resetPlayer() {
if (mMediaPlayer != null) {
- mMediaPlayer.reset();
- mMediaPlayer.release();
+ final MediaPlayer2 player = mMediaPlayer;
+ new AsyncTask<MediaPlayer2, Void, Void>() {
+ @Override
+ protected Void doInBackground(MediaPlayer2... players) {
+ // TODO: Fix NPE while MediaPlayer2.close()
+ //players[0].close();
+ return null;
+ }
+ }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, player);
mMediaPlayer = null;
+ mTextureView.setMediaPlayer(null);
+ mSurfaceView.setMediaPlayer(null);
//mPendingSubtitleTracks.clear();
mCurrentState = STATE_IDLE;
mTargetState = STATE_IDLE;
@@ -660,8 +714,8 @@
private void updatePlaybackState() {
if (mStateBuilder == null) {
// Get the capabilities of the player for this stream
- mMetadata = mMediaPlayer.getMetadata(MediaPlayer.METADATA_ALL,
- MediaPlayer.BYPASS_METADATA_FILTER);
+ mMetadata = mMediaPlayer.getMetadata(MediaPlayer2.METADATA_ALL,
+ MediaPlayer2.BYPASS_METADATA_FILTER);
// Add Play action as default
long playbackActions = PlaybackState.ACTION_PLAY;
@@ -701,8 +755,14 @@
if (mCurrentState != STATE_ERROR
&& mCurrentState != STATE_IDLE
&& mCurrentState != STATE_PREPARING) {
- mStateBuilder.setBufferedPosition(
- (long) (mCurrentBufferPercentage / 100.0) * mMediaPlayer.getDuration());
+ // TODO: this should be replaced with MediaPlayer2.getBufferedPosition() once it is
+ // implemented.
+ if (mCurrentBufferPercentage == -1) {
+ mStateBuilder.setBufferedPosition(-1);
+ } else {
+ mStateBuilder.setBufferedPosition(
+ (long) (mCurrentBufferPercentage / 100.0 * mMediaPlayer.getDuration()));
+ }
}
// Set PlaybackState for MediaSession
@@ -792,8 +852,8 @@
return false;
}
PlaybackInfo playbackInfo = mMediaController.getPlaybackInfo();
- return (playbackInfo != null)
- && (playbackInfo.getPlaybackType() == PlaybackInfo.PLAYBACK_TYPE_REMOTE);
+ return playbackInfo != null
+ && playbackInfo.getPlaybackType() == PlaybackInfo.PLAYBACK_TYPE_REMOTE;
}
private void selectOrDeselectSubtitle(boolean select) {
@@ -802,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);
}
@@ -817,21 +875,29 @@
}
private void extractTracks() {
- MediaPlayer.TrackInfo[] trackInfos = mMediaPlayer.getTrackInfo();
+ List<MediaPlayer2.TrackInfo> trackInfos = mMediaPlayer.getTrackInfo();
mVideoTrackIndices = new ArrayList<>();
mAudioTrackIndices = new ArrayList<>();
mSubtitleTrackIndices = new ArrayList<>();
- for (int i = 0; i < trackInfos.length; ++i) {
- int trackType = trackInfos[i].getTrackType();
- if (trackType == MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_VIDEO) {
+ for (int i = 0; i < trackInfos.size(); ++i) {
+ int trackType = trackInfos.get(i).getTrackType();
+ if (trackType == MediaPlayer2.TrackInfo.MEDIA_TRACK_TYPE_VIDEO) {
mVideoTrackIndices.add(i);
- } else if (trackType == MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_AUDIO) {
+ } else if (trackType == MediaPlayer2.TrackInfo.MEDIA_TRACK_TYPE_AUDIO) {
mAudioTrackIndices.add(i);
- } else if (trackType == MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE
- || trackType == MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT) {
- mSubtitleTrackIndices.add(i);
+ } else if (trackType == MediaPlayer2.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE
+ || trackType == MediaPlayer2.TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT) {
+ 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());
@@ -842,127 +908,50 @@
mMediaSession.sendSessionEvent(MediaControlView2Impl.EVENT_UPDATE_TRACK_STATUS, data);
}
- MediaPlayer.OnVideoSizeChangedListener mSizeChangedListener =
- new MediaPlayer.OnVideoSizeChangedListener() {
- public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
+ MediaPlayer2EventCallback mMediaPlayer2Callback =
+ new MediaPlayer2EventCallback() {
+ @Override
+ public void onVideoSizeChanged(
+ MediaPlayer2 mp, DataSourceDesc dsd, int width, int height) {
if (DEBUG) {
- Log.d(TAG, "OnVideoSizeChanged(): size: " + width + "/" + height);
+ Log.d(TAG, "onVideoSizeChanged(): size: " + width + "/" + height);
}
mVideoWidth = mp.getVideoWidth();
mVideoHeight = mp.getVideoHeight();
if (DEBUG) {
- Log.d(TAG, "OnVideoSizeChanged(): mVideoSize:" + mVideoWidth + "/"
+ Log.d(TAG, "onVideoSizeChanged(): mVideoSize:" + mVideoWidth + "/"
+ mVideoHeight);
}
-
if (mVideoWidth != 0 && mVideoHeight != 0) {
mInstance.requestLayout();
}
}
- };
- MediaPlayer.OnPreparedListener mPreparedListener = new MediaPlayer.OnPreparedListener() {
- public void onPrepared(MediaPlayer mp) {
- if (DEBUG) {
- Log.d(TAG, "OnPreparedListener(). mCurrentState=" + mCurrentState
- + ", mTargetState=" + mTargetState);
- }
- mCurrentState = STATE_PREPARED;
- // Create and set playback state for MediaControlView2
- updatePlaybackState();
-
- // TODO: change this to send TrackInfos to MediaControlView2
- // TODO: create MediaSession when initializing VideoView2
- if (mMediaSession != null) {
- extractTracks();
- }
-
- if (mMediaControlView != null) {
- mMediaControlView.setEnabled(true);
- }
- int videoWidth = mp.getVideoWidth();
- int videoHeight = mp.getVideoHeight();
-
- // mSeekWhenPrepared may be changed after seekTo() call
- long seekToPosition = mSeekWhenPrepared;
- if (seekToPosition != 0) {
- mMediaController.getTransportControls().seekTo(seekToPosition);
- }
-
- if (videoWidth != 0 && videoHeight != 0) {
- if (videoWidth != mVideoWidth || videoHeight != mVideoHeight) {
- if (DEBUG) {
- Log.i(TAG, "OnPreparedListener() : ");
- Log.i(TAG, " video size: " + videoWidth + "/" + videoHeight);
- Log.i(TAG, " measuredSize: " + mInstance.getMeasuredWidth() + "/"
- + mInstance.getMeasuredHeight());
- Log.i(TAG, " viewSize: " + mInstance.getWidth() + "/"
- + mInstance.getHeight());
- }
-
- mVideoWidth = videoWidth;
- mVideoHeight = videoHeight;
- mInstance.requestLayout();
+ // TODO: Remove timed text related code later once relevant Renderer is defined.
+ // This is just for debugging purpose.
+ @Override
+ public void onTimedText(
+ MediaPlayer2 mp, DataSourceDesc dsd, TimedText text) {
+ Log.d(TAG, "TimedText: " + text.getText());
}
- if (needToStart()) {
- mMediaController.getTransportControls().play();
- }
- } else {
- // We don't know the video size yet, but should start anyway.
- // The video size might be reported to us later.
- if (needToStart()) {
- mMediaController.getTransportControls().play();
- }
- }
-
- // Get and set duration and title values as MediaMetadata for MediaControlView2
- MediaMetadata.Builder builder = new MediaMetadata.Builder();
- if (mMetadata != null && mMetadata.has(Metadata.TITLE)) {
- mTitle = mMetadata.getString(Metadata.TITLE);
- }
- builder.putString(MediaMetadata.METADATA_KEY_TITLE, mTitle);
- builder.putLong(MediaMetadata.METADATA_KEY_DURATION, mMediaPlayer.getDuration());
-
- if (mMediaSession != null) {
- mMediaSession.setMetadata(builder.build());
-
- // TODO: merge this code with the above code when integrating with MediaSession2.
- if (mNeedUpdateMediaType) {
- mMediaSession.sendSessionEvent(
- MediaControlView2Impl.EVENT_UPDATE_MEDIA_TYPE_STATUS, mMediaTypeData);
- mNeedUpdateMediaType = false;
- }
- }
- }
- };
-
- private MediaPlayer.OnCompletionListener mCompletionListener =
- new MediaPlayer.OnCompletionListener() {
- public void onCompletion(MediaPlayer mp) {
- mCurrentState = STATE_PLAYBACK_COMPLETED;
- mTargetState = STATE_PLAYBACK_COMPLETED;
- updatePlaybackState();
-
- if (mAudioFocusType != AudioManager.AUDIOFOCUS_NONE) {
- mAudioManager.abandonAudioFocus(null);
- }
- }
- };
-
- private MediaPlayer.OnInfoListener mInfoListener =
- new MediaPlayer.OnInfoListener() {
- public boolean onInfo(MediaPlayer mp, int what, int extra) {
- if (what == MediaPlayer.MEDIA_INFO_METADATA_UPDATE) {
+ @Override
+ public void onInfo(
+ MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
+ if (what == MediaPlayer2.MEDIA_INFO_METADATA_UPDATE) {
extractTracks();
+ } else if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
+ this.onPrepared(mp, dsd);
+ } else if (what == MediaPlayer2.MEDIA_INFO_PLAYBACK_COMPLETE) {
+ this.onCompletion(mp, dsd);
+ } else if (what == MediaPlayer2.MEDIA_INFO_BUFFERING_UPDATE) {
+ this.onBufferingUpdate(mp, dsd, extra);
}
- return true;
}
- };
- private MediaPlayer.OnErrorListener mErrorListener =
- new MediaPlayer.OnErrorListener() {
- public boolean onError(MediaPlayer mp, int frameworkErr, int implErr) {
+ @Override
+ public void onError(
+ MediaPlayer2 mp, DataSourceDesc dsd, int frameworkErr, int implErr) {
if (DEBUG) {
Log.d(TAG, "Error: " + frameworkErr + "," + implErr);
}
@@ -973,47 +962,153 @@
if (mMediaControlView != null) {
mMediaControlView.setVisibility(View.GONE);
}
- return true;
}
- };
- private MediaPlayer.OnBufferingUpdateListener mBufferingUpdateListener =
- new MediaPlayer.OnBufferingUpdateListener() {
- public void onBufferingUpdate(MediaPlayer mp, int percent) {
+ private void onPrepared(MediaPlayer2 mp, DataSourceDesc dsd) {
+ if (DEBUG) {
+ Log.d(TAG, "OnPreparedListener(). mCurrentState=" + mCurrentState
+ + ", mTargetState=" + mTargetState);
+ }
+ mCurrentState = STATE_PREPARED;
+ // Create and set playback state for MediaControlView2
+ updatePlaybackState();
+
+ // TODO: change this to send TrackInfos to MediaControlView2
+ // TODO: create MediaSession when initializing VideoView2
+ if (mMediaSession != null) {
+ extractTracks();
+ }
+
+ if (mMediaControlView != null) {
+ mMediaControlView.setEnabled(true);
+ }
+ int videoWidth = mp.getVideoWidth();
+ int videoHeight = mp.getVideoHeight();
+
+ // mSeekWhenPrepared may be changed after seekTo() call
+ long seekToPosition = mSeekWhenPrepared;
+ if (seekToPosition != 0) {
+ mMediaController.getTransportControls().seekTo(seekToPosition);
+ }
+
+ if (videoWidth != 0 && videoHeight != 0) {
+ if (videoWidth != mVideoWidth || videoHeight != mVideoHeight) {
+ if (DEBUG) {
+ Log.i(TAG, "OnPreparedListener() : ");
+ Log.i(TAG, " video size: " + videoWidth + "/" + videoHeight);
+ Log.i(TAG, " measuredSize: " + mInstance.getMeasuredWidth() + "/"
+ + mInstance.getMeasuredHeight());
+ Log.i(TAG, " viewSize: " + mInstance.getWidth() + "/"
+ + mInstance.getHeight());
+ }
+ mVideoWidth = videoWidth;
+ mVideoHeight = videoHeight;
+ mInstance.requestLayout();
+ }
+
+ if (needToStart()) {
+ mMediaController.getTransportControls().play();
+ }
+ } else {
+ // We don't know the video size yet, but should start anyway.
+ // The video size might be reported to us later.
+ if (needToStart()) {
+ mMediaController.getTransportControls().play();
+ }
+ }
+ // Get and set duration and title values as MediaMetadata for MediaControlView2
+ MediaMetadata.Builder builder = new MediaMetadata.Builder();
+ if (mMetadata != null && mMetadata.has(Metadata.TITLE)) {
+ mTitle = mMetadata.getString(Metadata.TITLE);
+ }
+ builder.putString(MediaMetadata.METADATA_KEY_TITLE, mTitle);
+ builder.putLong(
+ MediaMetadata.METADATA_KEY_DURATION, mMediaPlayer.getDuration());
+
+ if (mMediaSession != null) {
+ mMediaSession.setMetadata(builder.build());
+
+ // TODO: merge this code with the above code when integrating with
+ // MediaSession2.
+ if (mNeedUpdateMediaType) {
+ mMediaSession.sendSessionEvent(
+ MediaControlView2Impl.EVENT_UPDATE_MEDIA_TYPE_STATUS,
+ mMediaTypeData);
+ mNeedUpdateMediaType = false;
+ }
+ }
+ }
+
+ private void onCompletion(MediaPlayer2 mp, DataSourceDesc dsd) {
+ mCurrentState = STATE_PLAYBACK_COMPLETED;
+ mTargetState = STATE_PLAYBACK_COMPLETED;
+ updatePlaybackState();
+ if (mAudioFocusType != AudioManager.AUDIOFOCUS_NONE) {
+ mAudioManager.abandonAudioFocus(null);
+ }
+ }
+
+ private void onBufferingUpdate(MediaPlayer2 mp, DataSourceDesc dsd, int percent) {
mCurrentBufferPercentage = percent;
updatePlaybackState();
}
};
- // TODO: Remove timed text related code later once relevant Renderer is defined.
- // This is just for debugging purpose.
- private MediaPlayer.OnTimedTextListener mTimedTextListener =
- new MediaPlayer.OnTimedTextListener() {
- public void onTimedText(MediaPlayer mp, TimedText text) {
- Log.d(TAG, "TimedText: " + text.getText());
- }
- };
-
private class MediaSessionCallback extends MediaSession.Callback {
@Override
public void onCommand(String command, Bundle args, ResultReceiver receiver) {
if (isRemotePlayback()) {
- mRouteSessionCallback.onCommand(command, args, receiver);
+ mRoutePlayer.onCommand(command, args, receiver);
} else {
switch (command) {
- case MediaControlView2.COMMAND_SHOW_SUBTITLE:
- mInstance.setSubtitleEnabled(true);
+ case MediaControlView2Impl.COMMAND_SHOW_SUBTITLE:
+ 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 MediaControlView2.COMMAND_HIDE_SUBTITLE:
- mInstance.setSubtitleEnabled(false);
+ case MediaControlView2Impl.COMMAND_HIDE_SUBTITLE:
+ selectOrDeselectSubtitle(false);
break;
- case MediaControlView2.COMMAND_SET_FULLSCREEN:
+ case MediaControlView2Impl.COMMAND_SET_FULLSCREEN:
if (mFullScreenRequestListener != null) {
mFullScreenRequestListener.onFullScreenRequest(
mInstance,
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();
@@ -1028,57 +1123,57 @@
@Override
public void onPlay() {
- if (isRemotePlayback()) {
- mRouteSessionCallback.onPlay();
- } else {
- if (isInPlaybackState() && mCurrentView.hasAvailableSurface()) {
+ if (isInPlaybackState() && mCurrentView.hasAvailableSurface()) {
+ if (isRemotePlayback()) {
+ mRoutePlayer.onPlay();
+ } else {
applySpeed();
- mMediaPlayer.start();
+ mMediaPlayer.play();
mCurrentState = STATE_PLAYING;
updatePlaybackState();
}
- mTargetState = STATE_PLAYING;
- if (DEBUG) {
- Log.d(TAG, "onPlay(). mCurrentState=" + mCurrentState
- + ", mTargetState=" + mTargetState);
- }
+ mCurrentState = STATE_PLAYING;
+ }
+ mTargetState = STATE_PLAYING;
+ if (DEBUG) {
+ Log.d(TAG, "onPlay(). mCurrentState=" + mCurrentState
+ + ", mTargetState=" + mTargetState);
}
showController();
}
@Override
public void onPause() {
- if (isRemotePlayback()) {
- mRouteSessionCallback.onPause();
- } else {
- if (isInPlaybackState()) {
- if (mMediaPlayer.isPlaying()) {
- mMediaPlayer.pause();
- mCurrentState = STATE_PAUSED;
- updatePlaybackState();
- }
+ if (isInPlaybackState()) {
+ if (isRemotePlayback()) {
+ mRoutePlayer.onPause();
+ mCurrentState = STATE_PAUSED;
+ } else if (mMediaPlayer.isPlaying()) {
+ mMediaPlayer.pause();
+ mCurrentState = STATE_PAUSED;
+ updatePlaybackState();
}
- mTargetState = STATE_PAUSED;
- if (DEBUG) {
- Log.d(TAG, "onPause(). mCurrentState=" + mCurrentState
- + ", mTargetState=" + mTargetState);
- }
+ }
+ mTargetState = STATE_PAUSED;
+ if (DEBUG) {
+ Log.d(TAG, "onPause(). mCurrentState=" + mCurrentState
+ + ", mTargetState=" + mTargetState);
}
showController();
}
@Override
public void onSeekTo(long pos) {
- if (isRemotePlayback()) {
- mRouteSessionCallback.onSeekTo(pos);
- } else {
- if (isInPlaybackState()) {
- mMediaPlayer.seekTo(pos, MediaPlayer.SEEK_PREVIOUS_SYNC);
+ if (isInPlaybackState()) {
+ if (isRemotePlayback()) {
+ mRoutePlayer.onSeekTo(pos);
+ } else {
+ mMediaPlayer.seekTo(pos, MediaPlayer2.SEEK_PREVIOUS_SYNC);
mSeekWhenPrepared = 0;
updatePlaybackState();
- } else {
- mSeekWhenPrepared = pos;
}
+ } else {
+ mSeekWhenPrepared = pos;
}
showController();
}
@@ -1086,74 +1181,11 @@
@Override
public void onStop() {
if (isRemotePlayback()) {
- mRouteSessionCallback.onStop();
+ mRoutePlayer.onStop();
} else {
resetPlayer();
}
showController();
}
}
-
- private class RouteSessionCallback extends MediaSession.Callback {
- RemotePlaybackClient mClient;
-
- RemotePlaybackClient.StatusCallback mStatusCallback =
- new RemotePlaybackClient.StatusCallback() {
- @Override
- public void onItemStatusChanged(Bundle data,
- String sessionId, MediaSessionStatus sessionStatus,
- String itemId, MediaItemStatus itemStatus) {
- // TODO: implement this
- }
-
- @Override
- public void onSessionStatusChanged(Bundle data,
- String sessionId, MediaSessionStatus sessionStatus) {
- // TODO: implement this
- }
-
- @Override
- public void onSessionChanged(String sessionId) {
- // TODO: implement this
- }
- };
-
- @Override
- public void onCommand(String command, Bundle args, ResultReceiver receiver) {
- ensureClient();
- // TODO: implement this
- }
-
- @Override
- public void onPlay() {
- ensureClient();
- // TODO: implement this
- }
-
- @Override
- public void onPause() {
- ensureClient();
- // TODO: implement this
- }
-
- @Override
- public void onSeekTo(long pos) {
- ensureClient();
- // TODO: implement this
- }
-
- @Override
- public void onStop() {
- ensureClient();
- // TODO: implement this
- }
-
- private void ensureClient() {
- if (mClient == null) {
- mClient = new RemotePlaybackClient(
- mInstance.getContext(), mMediaRouter.getSelectedRoute());
- mClient.setStatusCallback(mStatusCallback);
- }
- }
- }
}
diff --git a/packages/MediaComponents/src/com/android/widget/VideoViewInterface.java b/packages/MediaComponents/src/com/android/widget/VideoViewInterface.java
index 2a5eb94..854d47e 100644
--- a/packages/MediaComponents/src/com/android/widget/VideoViewInterface.java
+++ b/packages/MediaComponents/src/com/android/widget/VideoViewInterface.java
@@ -17,31 +17,31 @@
package com.android.widget;
import android.annotation.NonNull;
-import android.media.MediaPlayer;
+import android.media.MediaPlayer2;
import android.view.View;
interface VideoViewInterface {
/**
- * Assigns the view's surface to the given MediaPlayer instance.
+ * Assigns the view's surface to the given MediaPlayer2 instance.
*
- * @param mp MediaPlayer
+ * @param mp MediaPlayer2
* @return true if the surface is successfully assigned, false if not. It will fail to assign
- * if any of MediaPlayer or surface is unavailable.
+ * if any of MediaPlayer2 or surface is unavailable.
*/
- boolean assignSurfaceToMediaPlayer(MediaPlayer mp);
+ boolean assignSurfaceToMediaPlayer(MediaPlayer2 mp);
void setSurfaceListener(SurfaceListener l);
int getViewType();
- void setMediaPlayer(MediaPlayer mp);
+ void setMediaPlayer(MediaPlayer2 mp);
/**
- * Takes over oldView. It means that the MediaPlayer will start rendering on this view.
+ * Takes over oldView. It means that the MediaPlayer2 will start rendering on this view.
* The visibility of oldView will be set as {@link View.GONE}. If the view doesn't have a
- * MediaPlayer instance or its surface is not available, the actual execution is deferred until
- * a MediaPlayer instance is set by {@link #setMediaPlayer} or its surface becomes available.
+ * MediaPlayer2 instance or its surface is not available, the actual execution is deferred until
+ * a MediaPlayer2 instance is set by {@link #setMediaPlayer} or its surface becomes available.
* {@link SurfaceListener.onSurfaceTakeOverDone} will be called when the actual execution is
* done.
*
- * @param oldView The view that MediaPlayer is currently rendering on.
+ * @param oldView The view that MediaPlayer2 is currently rendering on.
*/
void takeOver(@NonNull VideoViewInterface oldView);
diff --git a/packages/MediaComponents/test/Android.mk b/packages/MediaComponents/test/Android.mk
deleted file mode 100644
index 8703b9f..0000000
--- a/packages/MediaComponents/test/Android.mk
+++ /dev/null
@@ -1,28 +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.
-
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-# TODO(jaewan): Copy this to the CTS as well
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_STATIC_JAVA_LIBRARIES := \
- android-support-test \
- mockito-target-minus-junit4 \
- compatibility-device-util
-
-LOCAL_PACKAGE_NAME := MediaComponentsTest
-include $(BUILD_PACKAGE)
diff --git a/packages/MediaComponents/test/AndroidManifest.xml b/packages/MediaComponents/test/AndroidManifest.xml
deleted file mode 100644
index 5ebe31a..0000000
--- a/packages/MediaComponents/test/AndroidManifest.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-<?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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.media.test">
-
- <application android:label="Media API Test">
- <uses-library android:name="android.test.runner" />
-
- <activity android:name="android.media.MockActivity" />
-
- <!-- Keep the test services synced together with the TestUtils.java -->
- <service android:name="android.media.MockMediaSessionService2">
- <intent-filter>
- <action android:name="android.media.MediaSessionService2" />
- </intent-filter>
- <meta-data android:name="android.media.session" android:value="TestSession" />
- </service>
- <!-- Keep the test services synced together with the MockMediaLibraryService -->
- <service android:name="android.media.MockMediaLibraryService2">
- <intent-filter>
- <action android:name="android.media.MediaLibraryService2" />
- </intent-filter>
- <meta-data android:name="android.media.session" android:value="TestLibrary" />
- </service>
- </application>
-
- <instrumentation
- android:name="android.support.test.runner.AndroidJUnitRunner"
- android:targetPackage="android.media.test"
- android:label="Media API test" />
-
-</manifest>
diff --git a/packages/MediaComponents/test/src/android/media/MediaBrowser2Test.java b/packages/MediaComponents/test/src/android/media/MediaBrowser2Test.java
deleted file mode 100644
index d1c7717..0000000
--- a/packages/MediaComponents/test/src/android/media/MediaBrowser2Test.java
+++ /dev/null
@@ -1,594 +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 android.media;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertNotNull;
-import static junit.framework.Assert.assertTrue;
-import static junit.framework.Assert.assertNull;
-import static junit.framework.Assert.fail;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.media.MediaBrowser2.BrowserCallback;
-import android.media.MediaLibraryService2.MediaLibrarySession;
-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.TestServiceRegistry.SessionCallbackProxy;
-import android.os.Bundle;
-import android.os.ResultReceiver;
-import android.os.Process;
-import android.support.annotation.CallSuper;
-import android.support.annotation.NonNull;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests {@link MediaBrowser2}.
- * <p>
- * This test inherits {@link MediaController2Test} to ensure that inherited APIs from
- * {@link MediaController2} works cleanly.
- */
-// TODO(jaewan): Implement host-side test so browser and service can run in different processes.
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class MediaBrowser2Test extends MediaController2Test {
- private static final String TAG = "MediaBrowser2Test";
-
- @Override
- TestControllerInterface onCreateController(@NonNull SessionToken2 token,
- @Nullable TestControllerCallbackInterface callback) {
- if (callback == null) {
- callback = new TestBrowserCallbackInterface() {};
- }
- return new TestMediaBrowser(mContext, token, new TestBrowserCallback(callback));
- }
-
- interface TestBrowserCallbackInterface extends TestControllerCallbackInterface {
- // Browser specific callbacks
- default void onGetLibraryRootDone(Bundle rootHints, String rootMediaId, Bundle rootExtra) {}
- default void onGetItemDone(String mediaId, MediaItem2 result) {}
- default void onChildrenChanged(String parentId, int itemCount, Bundle extras) {}
- default void onGetChildrenDone(String parentId, int page, int pageSize,
- List<MediaItem2> result, Bundle extras) {}
- default void onSearchResultChanged(String query, int itemCount, Bundle extras) {}
- default void onGetSearchResultDone(String query, int page, int pageSize,
- List<MediaItem2> result, Bundle extras) {}
- }
-
- @Test
- public void testGetLibraryRoot() throws InterruptedException {
- final Bundle param = new Bundle();
- param.putString(TAG, TAG);
-
- final CountDownLatch latch = new CountDownLatch(1);
- final TestControllerCallbackInterface callback = new TestBrowserCallbackInterface() {
- @Override
- public void onGetLibraryRootDone(Bundle rootHints, String rootMediaId,
- Bundle rootExtra) {
- assertTrue(TestUtils.equals(param, rootHints));
- assertEquals(MockMediaLibraryService2.ROOT_ID, rootMediaId);
- assertTrue(TestUtils.equals(MockMediaLibraryService2.EXTRAS, rootExtra));
- latch.countDown();
- }
- };
-
- final SessionToken2 token = MockMediaLibraryService2.getToken(mContext);
- MediaBrowser2 browser =
- (MediaBrowser2) createController(token,true, callback);
- browser.getLibraryRoot(param);
- assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
- }
-
- @Test
- public void testGetItem() throws InterruptedException {
- final String mediaId = MockMediaLibraryService2.MEDIA_ID_GET_ITEM;
-
- final CountDownLatch latch = new CountDownLatch(1);
- final TestControllerCallbackInterface callback = new TestBrowserCallbackInterface() {
- @Override
- public void onGetItemDone(String mediaIdOut, MediaItem2 result) {
- assertEquals(mediaId, mediaIdOut);
- assertNotNull(result);
- assertEquals(mediaId, result.getMediaId());
- latch.countDown();
- }
- };
-
- final SessionToken2 token = MockMediaLibraryService2.getToken(mContext);
- MediaBrowser2 browser = (MediaBrowser2) createController(token, true, callback);
- browser.getItem(mediaId);
- assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
- }
-
- @Test
- public void testGetItemNullResult() throws InterruptedException {
- final String mediaId = "random_media_id";
-
- final CountDownLatch latch = new CountDownLatch(1);
- final TestControllerCallbackInterface callback = new TestBrowserCallbackInterface() {
- @Override
- public void onGetItemDone(String mediaIdOut, MediaItem2 result) {
- assertEquals(mediaId, mediaIdOut);
- assertNull(result);
- latch.countDown();
- }
- };
-
- final SessionToken2 token = MockMediaLibraryService2.getToken(mContext);
- MediaBrowser2 browser = (MediaBrowser2) createController(token, true, callback);
- browser.getItem(mediaId);
- assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
- }
-
- @Test
- public void testGetChildren() throws InterruptedException {
- final String parentId = MockMediaLibraryService2.PARENT_ID;
- final int page = 4;
- final int pageSize = 10;
- final Bundle extras = new Bundle();
- extras.putString(TAG, TAG);
-
- final CountDownLatch latch = new CountDownLatch(1);
- final TestControllerCallbackInterface callback = new TestBrowserCallbackInterface() {
- @Override
- public void onGetChildrenDone(String parentIdOut, int pageOut, int pageSizeOut,
- List<MediaItem2> result, Bundle extrasOut) {
- assertEquals(parentId, parentIdOut);
- assertEquals(page, pageOut);
- assertEquals(pageSize, pageSizeOut);
- assertTrue(TestUtils.equals(extras, extrasOut));
- assertNotNull(result);
-
- int fromIndex = (page - 1) * pageSize;
- int toIndex = Math.min(page * pageSize, MockMediaLibraryService2.CHILDREN_COUNT);
-
- // Compare the given results with originals.
- for (int originalIndex = fromIndex; originalIndex < toIndex; originalIndex++) {
- int relativeIndex = originalIndex - fromIndex;
- assertEquals(
- MockMediaLibraryService2.GET_CHILDREN_RESULT.get(originalIndex)
- .getMediaId(),
- result.get(relativeIndex).getMediaId());
- }
- latch.countDown();
- }
- };
-
- final SessionToken2 token = MockMediaLibraryService2.getToken(mContext);
- MediaBrowser2 browser = (MediaBrowser2) createController(token, true, callback);
- browser.getChildren(parentId, page, pageSize, extras);
- assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
- }
-
- @Test
- public void testGetChildrenEmptyResult() throws InterruptedException {
- final String parentId = MockMediaLibraryService2.PARENT_ID_NO_CHILDREN;
-
- final CountDownLatch latch = new CountDownLatch(1);
- final TestControllerCallbackInterface callback = new TestBrowserCallbackInterface() {
- @Override
- public void onGetChildrenDone(String parentIdOut, int pageOut, int pageSizeOut,
- List<MediaItem2> result, Bundle extrasOut) {
- assertNotNull(result);
- assertEquals(0, result.size());
- latch.countDown();
- }
- };
-
- final SessionToken2 token = MockMediaLibraryService2.getToken(mContext);
- MediaBrowser2 browser = (MediaBrowser2) createController(token, true, callback);
- browser.getChildren(parentId, 1, 1, null);
- assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
- }
-
- @Test
- public void testGetChildrenNullResult() throws InterruptedException {
- final String parentId = MockMediaLibraryService2.PARENT_ID_ERROR;
-
- final CountDownLatch latch = new CountDownLatch(1);
- final TestControllerCallbackInterface callback = new TestBrowserCallbackInterface() {
- @Override
- public void onGetChildrenDone(String parentIdOut, int pageOut, int pageSizeOut,
- List<MediaItem2> result, Bundle extrasOut) {
- assertNull(result);
- latch.countDown();
- }
- };
-
- final SessionToken2 token = MockMediaLibraryService2.getToken(mContext);
- MediaBrowser2 browser = (MediaBrowser2) createController(token, true, callback);
- browser.getChildren(parentId, 1, 1, null);
- assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
- }
-
- @Ignore
- @Test
- public void testSearch() throws InterruptedException {
- final String query = MockMediaLibraryService2.SEARCH_QUERY;
- final int page = 4;
- final int pageSize = 10;
- final Bundle extras = new Bundle();
- extras.putString(TAG, TAG);
-
- final CountDownLatch latchForSearch = new CountDownLatch(1);
- final CountDownLatch latchForGetSearchResult = new CountDownLatch(1);
- final TestControllerCallbackInterface callback = new TestBrowserCallbackInterface() {
- @Override
- public void onSearchResultChanged(String queryOut, int itemCount, Bundle extrasOut) {
- assertEquals(query, queryOut);
- assertTrue(TestUtils.equals(extras, extrasOut));
- assertEquals(MockMediaLibraryService2.SEARCH_RESULT_COUNT, itemCount);
- latchForSearch.countDown();
- }
-
- @Override
- public void onGetSearchResultDone(String queryOut, int pageOut, int pageSizeOut,
- List<MediaItem2> result, Bundle extrasOut) {
- assertEquals(query, queryOut);
- assertEquals(page, pageOut);
- assertEquals(pageSize, pageSizeOut);
- assertTrue(TestUtils.equals(extras, extrasOut));
- assertNotNull(result);
-
- int fromIndex = (page - 1) * pageSize;
- int toIndex = Math.min(
- page * pageSize, MockMediaLibraryService2.SEARCH_RESULT_COUNT);
-
- // Compare the given results with originals.
- for (int originalIndex = fromIndex; originalIndex < toIndex; originalIndex++) {
- int relativeIndex = originalIndex - fromIndex;
- assertEquals(
- MockMediaLibraryService2.SEARCH_RESULT.get(originalIndex).getMediaId(),
- result.get(relativeIndex).getMediaId());
- }
- latchForGetSearchResult.countDown();
- }
- };
-
- // Request the search.
- final SessionToken2 token = MockMediaLibraryService2.getToken(mContext);
- MediaBrowser2 browser = (MediaBrowser2) createController(token, true, callback);
- browser.search(query, extras);
- assertTrue(latchForSearch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-
- // Get the search result.
- browser.getSearchResult(query, page, pageSize, extras);
- assertTrue(latchForGetSearchResult.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
- }
-
- @Test
- public void testSearchTakesTime() throws InterruptedException {
- final String query = MockMediaLibraryService2.SEARCH_QUERY_TAKES_TIME;
- final Bundle extras = new Bundle();
- extras.putString(TAG, TAG);
-
- final CountDownLatch latch = new CountDownLatch(1);
- final TestControllerCallbackInterface callback = new TestBrowserCallbackInterface() {
- @Override
- public void onSearchResultChanged(String queryOut, int itemCount, Bundle extrasOut) {
- assertEquals(query, queryOut);
- assertTrue(TestUtils.equals(extras, extrasOut));
- assertEquals(MockMediaLibraryService2.SEARCH_RESULT_COUNT, itemCount);
- latch.countDown();
- }
- };
-
- final SessionToken2 token = MockMediaLibraryService2.getToken(mContext);
- MediaBrowser2 browser = (MediaBrowser2) createController(token, true, callback);
- browser.search(query, extras);
- assertTrue(latch.await(
- MockMediaLibraryService2.SEARCH_TIME_IN_MS + WAIT_TIME_MS, TimeUnit.MILLISECONDS));
- }
-
- @Test
- public void testSearchEmptyResult() throws InterruptedException {
- final String query = MockMediaLibraryService2.SEARCH_QUERY_EMPTY_RESULT;
- final Bundle extras = new Bundle();
- extras.putString(TAG, TAG);
-
- final CountDownLatch latch = new CountDownLatch(1);
- final TestControllerCallbackInterface callback = new TestBrowserCallbackInterface() {
- @Override
- public void onSearchResultChanged(String queryOut, int itemCount, Bundle extrasOut) {
- assertEquals(query, queryOut);
- assertTrue(TestUtils.equals(extras, extrasOut));
- assertEquals(0, itemCount);
- latch.countDown();
- }
- };
-
- final SessionToken2 token = MockMediaLibraryService2.getToken(mContext);
- MediaBrowser2 browser = (MediaBrowser2) createController(token, true, callback);
- browser.search(query, extras);
- assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
- }
-
- @Test
- public void testSubscribe() throws InterruptedException {
- final String testParentId = "testSubscribeId";
- final Bundle testExtras = new Bundle();
- testExtras.putString(testParentId, testParentId);
-
- final CountDownLatch latch = new CountDownLatch(1);
- final SessionCallbackProxy callbackProxy = new SessionCallbackProxy(mContext) {
- @Override
- public void onSubscribe(ControllerInfo info, String parentId, Bundle extras) {
- if (Process.myUid() == info.getUid()) {
- assertEquals(testParentId, parentId);
- assertTrue(TestUtils.equals(testExtras, extras));
- latch.countDown();
- }
- }
- };
- TestServiceRegistry.getInstance().setSessionCallbackProxy(callbackProxy);
- final SessionToken2 token = MockMediaLibraryService2.getToken(mContext);
- MediaBrowser2 browser = (MediaBrowser2) createController(token);
- browser.subscribe(testParentId, testExtras);
- assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
- }
-
- @Ignore
- @Test
- public void testUnsubscribe() throws InterruptedException {
- final String testParentId = "testUnsubscribeId";
- final CountDownLatch latch = new CountDownLatch(1);
- final SessionCallbackProxy callbackProxy = new SessionCallbackProxy(mContext) {
- @Override
- public void onUnsubscribe(ControllerInfo info, String parentId) {
- if (Process.myUid() == info.getUid()) {
- assertEquals(testParentId, parentId);
- latch.countDown();
- }
- }
- };
- TestServiceRegistry.getInstance().setSessionCallbackProxy(callbackProxy);
- final SessionToken2 token = MockMediaLibraryService2.getToken(mContext);
- MediaBrowser2 browser = (MediaBrowser2) createController(token);
- browser.unsubscribe(testParentId);
- assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
- }
-
- @Test
- public void testBrowserCallback_notifyChildrenChanged() throws InterruptedException {
- // TODO(jaewan): Add test for the notifyChildrenChanged itself.
- final String testParentId1 = "testBrowserCallback_notifyChildrenChanged_unexpectedParent";
- final String testParentId2 = "testBrowserCallback_notifyChildrenChanged";
- final int testChildrenCount = 101;
- final Bundle testExtras = new Bundle();
- testExtras.putString(testParentId1, testParentId1);
-
- final CountDownLatch latch = new CountDownLatch(3);
- final SessionCallbackProxy sessionCallbackProxy = new SessionCallbackProxy(mContext) {
- @Override
- public CommandGroup onConnect(ControllerInfo controller) {
- final MockMediaLibraryService2 service = (MockMediaLibraryService2)
- TestServiceRegistry.getInstance().getServiceInstance();
- final MediaLibrarySession session = (MediaLibrarySession) service.getSession();
- // Shouldn't trigger onChildrenChanged() for the browser, because it hasn't
- // subscribed.
- session.notifyChildrenChanged(testParentId1, testChildrenCount, null);
- session.notifyChildrenChanged(controller, testParentId1, testChildrenCount, null);
- return super.onConnect(controller);
- }
-
- @Override
- public void onSubscribe(ControllerInfo info, String parentId, Bundle extras) {
- if (Process.myUid() == info.getUid()) {
- final MediaLibrarySession session = (MediaLibrarySession) mSession;
- session.notifyChildrenChanged(testParentId2, testChildrenCount, null);
- session.notifyChildrenChanged(info, testParentId2, testChildrenCount,
- testExtras);
- }
- }
- };
- final TestBrowserCallbackInterface controllerCallbackProxy =
- new TestBrowserCallbackInterface() {
- @Override
- public void onChildrenChanged(String parentId, int itemCount, Bundle extras) {
- switch ((int) latch.getCount()) {
- case 3:
- assertEquals(testParentId2, parentId);
- assertEquals(testChildrenCount, itemCount);
- assertNull(extras);
- latch.countDown();
- break;
- case 2:
- assertEquals(testParentId2, parentId);
- assertEquals(testChildrenCount, itemCount);
- assertTrue(TestUtils.equals(testExtras, extras));
- latch.countDown();
- break;
- default:
- // Unexpected call.
- fail();
- }
- }
- };
- TestServiceRegistry.getInstance().setSessionCallbackProxy(sessionCallbackProxy);
- final SessionToken2 token = MockMediaLibraryService2.getToken(mContext);
- final MediaBrowser2 browser = (MediaBrowser2) createController(
- token, true, controllerCallbackProxy);
- final MockMediaLibraryService2 service =
- (MockMediaLibraryService2) TestServiceRegistry.getInstance().getServiceInstance();
- if (mSession != null) {
- mSession.close();
- }
- mSession = service.getSession();
- assertTrue(mSession instanceof MediaLibrarySession);
- browser.subscribe(testParentId2, null);
- // This ensures that onChildrenChanged() is only called for the expected reasons.
- assertFalse(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
- }
-
- public static class TestBrowserCallback extends BrowserCallback
- implements WaitForConnectionInterface {
- private final TestControllerCallbackInterface mCallbackProxy;
- public final CountDownLatch connectLatch = new CountDownLatch(1);
- public final CountDownLatch disconnectLatch = new CountDownLatch(1);
-
- TestBrowserCallback(TestControllerCallbackInterface callbackProxy) {
- if (callbackProxy == null) {
- throw new IllegalArgumentException("Callback proxy shouldn't be null. Test bug");
- }
- mCallbackProxy = callbackProxy;
- }
-
- @CallSuper
- @Override
- public void onConnected(MediaController2 controller, CommandGroup commands) {
- connectLatch.countDown();
- }
-
- @CallSuper
- @Override
- public void onDisconnected(MediaController2 controller) {
- disconnectLatch.countDown();
- }
-
- @Override
- public void onPlaybackStateChanged(MediaController2 controller, PlaybackState2 state) {
- mCallbackProxy.onPlaybackStateChanged(state);
- }
-
- @Override
- public void onPlaylistParamsChanged(MediaController2 controller, PlaylistParams params) {
- mCallbackProxy.onPlaylistParamsChanged(params);
- }
-
- @Override
- public void onPlaybackInfoChanged(MediaController2 controller,
- MediaController2.PlaybackInfo info) {
- mCallbackProxy.onPlaybackInfoChanged(info);
- }
-
- @Override
- public void onCustomCommand(MediaController2 controller, Command command, Bundle args,
- ResultReceiver receiver) {
- mCallbackProxy.onCustomCommand(command, args, receiver);
- }
-
-
- @Override
- public void onCustomLayoutChanged(MediaController2 controller, List<CommandButton> layout) {
- mCallbackProxy.onCustomLayoutChanged(layout);
- }
-
- @Override
- public void onGetLibraryRootDone(MediaBrowser2 browser, Bundle rootHints,
- String rootMediaId, Bundle rootExtra) {
- super.onGetLibraryRootDone(browser, rootHints, rootMediaId, rootExtra);
- if (mCallbackProxy instanceof TestBrowserCallbackInterface) {
- ((TestBrowserCallbackInterface) mCallbackProxy)
- .onGetLibraryRootDone(rootHints, rootMediaId, rootExtra);
- }
- }
-
- @Override
- public void onGetItemDone(MediaBrowser2 browser, String mediaId, MediaItem2 result) {
- super.onGetItemDone(browser, mediaId, result);
- if (mCallbackProxy instanceof TestBrowserCallbackInterface) {
- ((TestBrowserCallbackInterface) mCallbackProxy).onGetItemDone(mediaId, result);
- }
- }
-
- @Override
- public void onGetChildrenDone(MediaBrowser2 browser, String parentId, int page,
- int pageSize, List<MediaItem2> result, Bundle extras) {
- super.onGetChildrenDone(browser, parentId, page, pageSize, result, extras);
- if (mCallbackProxy instanceof TestBrowserCallbackInterface) {
- ((TestBrowserCallbackInterface) mCallbackProxy)
- .onGetChildrenDone(parentId, page, pageSize, result, extras);
- }
- }
-
- @Override
- public void onSearchResultChanged(MediaBrowser2 browser, String query, int itemCount,
- Bundle extras) {
- super.onSearchResultChanged(browser, query, itemCount, extras);
- if (mCallbackProxy instanceof TestBrowserCallbackInterface) {
- ((TestBrowserCallbackInterface) mCallbackProxy)
- .onSearchResultChanged(query, itemCount, extras);
- }
- }
-
- @Override
- public void onGetSearchResultDone(MediaBrowser2 browser, String query, int page,
- int pageSize, List<MediaItem2> result, Bundle extras) {
- super.onGetSearchResultDone(browser, query, page, pageSize, result, extras);
- if (mCallbackProxy instanceof TestBrowserCallbackInterface) {
- ((TestBrowserCallbackInterface) mCallbackProxy)
- .onGetSearchResultDone(query, page, pageSize, result, extras);
- }
- }
-
- @Override
- public void onChildrenChanged(MediaBrowser2 browser, String parentId, int itemCount,
- Bundle extras) {
- super.onChildrenChanged(browser, parentId, itemCount, extras);
- if (mCallbackProxy instanceof TestBrowserCallbackInterface) {
- ((TestBrowserCallbackInterface) mCallbackProxy)
- .onChildrenChanged(parentId, itemCount, extras);
- }
- }
-
- @Override
- public void waitForConnect(boolean expect) throws InterruptedException {
- if (expect) {
- assertTrue(connectLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
- } else {
- assertFalse(connectLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
- }
- }
-
- @Override
- public void waitForDisconnect(boolean expect) throws InterruptedException {
- if (expect) {
- assertTrue(disconnectLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
- } else {
- assertFalse(disconnectLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
- }
- }
- }
-
- public class TestMediaBrowser extends MediaBrowser2 implements TestControllerInterface {
- private final BrowserCallback mCallback;
-
- public TestMediaBrowser(@NonNull Context context, @NonNull SessionToken2 token,
- @NonNull ControllerCallback callback) {
- super(context, token, sHandlerExecutor, (BrowserCallback) callback);
- mCallback = (BrowserCallback) callback;
- }
-
- @Override
- public BrowserCallback getCallback() {
- return mCallback;
- }
- }
-}
diff --git a/packages/MediaComponents/test/src/android/media/MediaController2Test.java b/packages/MediaComponents/test/src/android/media/MediaController2Test.java
deleted file mode 100644
index e6ad098..0000000
--- a/packages/MediaComponents/test/src/android/media/MediaController2Test.java
+++ /dev/null
@@ -1,855 +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 android.media;
-
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.media.MediaSession2.Command;
-import android.media.MediaSession2.CommandGroup;
-import android.media.MediaSession2.ControllerInfo;
-import android.media.MediaSession2.PlaylistParams;
-import android.media.MediaSession2.SessionCallback;
-import android.media.TestServiceRegistry.SessionCallbackProxy;
-import android.media.TestUtils.SyncHandler;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Process;
-import android.os.ResultReceiver;
-import android.support.test.filters.FlakyTest;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import static android.media.TestUtils.ensurePlaylistParamsModeEquals;
-
-import static org.junit.Assert.*;
-
-/**
- * Tests {@link MediaController2}.
- */
-// TODO(jaewan): Implement host-side test so controller and session can run in different processes.
-// TODO(jaewan): Fix flaky failure -- see MediaController2Impl.getController()
-// TODO(jaeawn): Revisit create/close session in the sHandler. It's no longer necessary.
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-@FlakyTest
-public class MediaController2Test extends MediaSession2TestBase {
- private static final String TAG = "MediaController2Test";
-
- PendingIntent mIntent;
- MediaSession2 mSession;
- MediaController2 mController;
- MockPlayer mPlayer;
-
- @Before
- @Override
- public void setUp() throws Exception {
- super.setUp();
- final Intent sessionActivity = new Intent(mContext, MockActivity.class);
- // Create this test specific MediaSession2 to use our own Handler.
- mIntent = PendingIntent.getActivity(mContext, 0, sessionActivity, 0);
-
- mPlayer = new MockPlayer(1);
- mSession = new MediaSession2.Builder(mContext)
- .setPlayer(mPlayer)
- .setSessionCallback(sHandlerExecutor, new SessionCallback(mContext) {})
- .setSessionActivity(mIntent)
- .setId(TAG).build();
- mController = createController(mSession.getToken());
- TestServiceRegistry.getInstance().setHandler(sHandler);
- }
-
- @After
- @Override
- public void cleanUp() throws Exception {
- super.cleanUp();
- if (mSession != null) {
- mSession.close();
- }
- TestServiceRegistry.getInstance().cleanUp();
- }
-
- @Test
- public void testPlay() throws InterruptedException {
- mController.play();
- try {
- assertTrue(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
- } catch (InterruptedException e) {
- fail(e.getMessage());
- }
- assertTrue(mPlayer.mPlayCalled);
- }
-
- @Test
- public void testPause() throws InterruptedException {
- mController.pause();
- try {
- assertTrue(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
- } catch (InterruptedException e) {
- fail(e.getMessage());
- }
- assertTrue(mPlayer.mPauseCalled);
- }
-
- @Ignore
- @Test
- public void testSkipToPrevious() throws InterruptedException {
- mController.skipToPrevious();
- try {
- assertTrue(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
- } catch (InterruptedException e) {
- fail(e.getMessage());
- }
- assertTrue(mPlayer.mSkipToPreviousCalled);
- }
-
- @Test
- public void testSkipToNext() throws InterruptedException {
- mController.skipToNext();
- try {
- assertTrue(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
- } catch (InterruptedException e) {
- fail(e.getMessage());
- }
- assertTrue(mPlayer.mSkipToNextCalled);
- }
-
- @Ignore
- @Test
- public void testStop() throws InterruptedException {
- mController.stop();
- try {
- assertTrue(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
- } catch (InterruptedException e) {
- fail(e.getMessage());
- }
- assertTrue(mPlayer.mStopCalled);
- }
-
- @Test
- public void testPrepare() throws InterruptedException {
- mController.prepare();
- try {
- assertTrue(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
- } catch (InterruptedException e) {
- fail(e.getMessage());
- }
- assertTrue(mPlayer.mPrepareCalled);
- }
-
- @Ignore
- @Test
- public void testFastForward() throws InterruptedException {
- mController.fastForward();
- try {
- assertTrue(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
- } catch (InterruptedException e) {
- fail(e.getMessage());
- }
- assertTrue(mPlayer.mFastForwardCalled);
- }
-
- @Ignore
- @Test
- public void testRewind() throws InterruptedException {
- mController.rewind();
- try {
- assertTrue(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
- } catch (InterruptedException e) {
- fail(e.getMessage());
- }
- assertTrue(mPlayer.mRewindCalled);
- }
-
- @Test
- public void testSeekTo() throws InterruptedException {
- final long seekPosition = 12125L;
- mController.seekTo(seekPosition);
- try {
- assertTrue(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
- } catch (InterruptedException e) {
- fail(e.getMessage());
- }
- assertTrue(mPlayer.mSeekToCalled);
- assertEquals(seekPosition, mPlayer.mSeekPosition);
- }
-
- // TODO(jaewan): Re-enable this test
- /*
- @Test
- public void testSetCurrentPlaylistItem() throws InterruptedException {
- final
- final int itemIndex = 9;
- mController.skipToPlaylistItem(itemIndex);
- try {
- assertTrue(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
- } catch (InterruptedException e) {
- fail(e.getMessage());
- }
- assertTrue(mPlayer.mSetCurrentPlaylistItemCalled);
- assertEquals(itemIndex, mPlayer.mCurrentItem);
- }
- */
-
- @Test
- public void testGetSessionActivity() throws InterruptedException {
- PendingIntent sessionActivity = mController.getSessionActivity();
- assertEquals(mContext.getPackageName(), sessionActivity.getCreatorPackage());
- assertEquals(Process.myUid(), sessionActivity.getCreatorUid());
- }
-
- @Ignore
- @Test
- public void testGetSetPlaylistParams() throws Exception {
- final PlaylistParams params = new PlaylistParams(mContext,
- PlaylistParams.REPEAT_MODE_ALL,
- PlaylistParams.SHUFFLE_MODE_ALL,
- null /* PlaylistMetadata */);
-
- final CountDownLatch latch = new CountDownLatch(1);
- final TestControllerCallbackInterface callback = new TestControllerCallbackInterface() {
- @Override
- public void onPlaylistParamsChanged(PlaylistParams givenParams) {
- ensurePlaylistParamsModeEquals(params, givenParams);
- latch.countDown();
- }
- };
-
- final MediaController2 controller = createController(mSession.getToken(), true, callback);
- controller.setPlaylistParams(params);
-
- assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
- ensurePlaylistParamsModeEquals(params, mSession.getPlaylistParams());
- ensurePlaylistParamsModeEquals(params, controller.getPlaylistParams());
- }
-
- @Test
- public void testSetVolumeTo() throws Exception {
- final int maxVolume = 100;
- final int currentVolume = 23;
- final int volumeControlType = VolumeProvider2.VOLUME_CONTROL_ABSOLUTE;
- TestVolumeProvider volumeProvider =
- new TestVolumeProvider(mContext, volumeControlType, maxVolume, currentVolume);
-
- mSession.updatePlayer(new MockPlayer(0), null, volumeProvider);
- final MediaController2 controller = createController(mSession.getToken(), true, null);
-
- final int targetVolume = 50;
- controller.setVolumeTo(targetVolume, 0 /* flags */);
- assertTrue(volumeProvider.mLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
- assertTrue(volumeProvider.mSetVolumeToCalled);
- assertEquals(targetVolume, volumeProvider.mVolume);
- }
-
- @Test
- public void testAdjustVolume() throws Exception {
- final int maxVolume = 100;
- final int currentVolume = 23;
- final int volumeControlType = VolumeProvider2.VOLUME_CONTROL_ABSOLUTE;
- TestVolumeProvider volumeProvider =
- new TestVolumeProvider(mContext, volumeControlType, maxVolume, currentVolume);
-
- mSession.updatePlayer(new MockPlayer(0), null, volumeProvider);
- final MediaController2 controller = createController(mSession.getToken(), true, null);
-
- final int direction = AudioManager.ADJUST_RAISE;
- controller.adjustVolume(direction, 0 /* flags */);
- assertTrue(volumeProvider.mLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
- assertTrue(volumeProvider.mAdjustVolumeCalled);
- assertEquals(direction, volumeProvider.mDirection);
- }
-
- @Test
- public void testGetPackageName() {
- assertEquals(mContext.getPackageName(), mController.getSessionToken().getPackageName());
- }
-
- // This also tests getPlaybackState().
- @Ignore
- @Test
- public void testControllerCallback_onPlaybackStateChanged() throws InterruptedException {
- final CountDownLatch latch = new CountDownLatch(1);
- final TestControllerCallbackInterface callback = new TestControllerCallbackInterface() {
- @Override
- public void onPlaybackStateChanged(PlaybackState2 state) {
- // Called only once when the player's playback state is changed after this.
- assertEquals(PlaybackState2.STATE_PAUSED, state.getState());
- latch.countDown();
- }
- };
- mPlayer.notifyPlaybackState(createPlaybackState(PlaybackState2.STATE_PLAYING));
- mController = createController(mSession.getToken(), true, callback);
- assertEquals(PlaybackState2.STATE_PLAYING, mController.getPlaybackState().getState());
- mPlayer.notifyPlaybackState(createPlaybackState(PlaybackState2.STATE_PAUSED));
- assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
- assertEquals(PlaybackState2.STATE_PAUSED, mController.getPlaybackState().getState());
- }
-
- @Test
- public void testSendCustomCommand() throws InterruptedException {
- // TODO(jaewan): Need to revisit with the permission.
- final Command testCommand =
- new Command(mContext, MediaSession2.COMMAND_CODE_PLAYBACK_PREPARE);
- final Bundle testArgs = new Bundle();
- testArgs.putString("args", "testSendCustomAction");
-
- final CountDownLatch latch = new CountDownLatch(1);
- final SessionCallback callback = new SessionCallback(mContext) {
- @Override
- public void onCustomCommand(MediaSession2 session, ControllerInfo controller,
- Command customCommand, Bundle args, ResultReceiver cb) {
- super.onCustomCommand(session, controller, customCommand, args, cb);
- assertEquals(mContext.getPackageName(), controller.getPackageName());
- assertEquals(testCommand, customCommand);
- assertTrue(TestUtils.equals(testArgs, args));
- assertNull(cb);
- latch.countDown();
- }
- };
- mSession.close();
- mSession = new MediaSession2.Builder(mContext).setPlayer(mPlayer)
- .setSessionCallback(sHandlerExecutor, callback).setId(TAG).build();
- final MediaController2 controller = createController(mSession.getToken());
- controller.sendCustomCommand(testCommand, testArgs, null);
- assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
- }
-
- @Test
- public void testControllerCallback_onConnected() throws InterruptedException {
- // createController() uses controller callback to wait until the controller becomes
- // available.
- MediaController2 controller = createController(mSession.getToken());
- assertNotNull(controller);
- }
-
- @Test
- public void testControllerCallback_sessionRejects() throws InterruptedException {
- final MediaSession2.SessionCallback sessionCallback = new SessionCallback(mContext) {
- @Override
- public MediaSession2.CommandGroup onConnect(MediaSession2 session,
- ControllerInfo controller) {
- return null;
- }
- };
- sHandler.postAndSync(() -> {
- mSession.close();
- mSession = new MediaSession2.Builder(mContext).setPlayer(mPlayer)
- .setSessionCallback(sHandlerExecutor, sessionCallback).build();
- });
- MediaController2 controller =
- createController(mSession.getToken(), false, null);
- assertNotNull(controller);
- waitForConnect(controller, false);
- waitForDisconnect(controller, true);
- }
-
- @Test
- public void testControllerCallback_releaseSession() throws InterruptedException {
- sHandler.postAndSync(() -> {
- mSession.close();
- });
- waitForDisconnect(mController, true);
- }
-
- @Test
- public void testControllerCallback_release() throws InterruptedException {
- mController.close();
- waitForDisconnect(mController, true);
- }
-
- @Test
- public void testPlayFromSearch() throws InterruptedException {
- final String request = "random query";
- final Bundle bundle = new Bundle();
- bundle.putString("key", "value");
- final CountDownLatch latch = new CountDownLatch(1);
- final SessionCallback callback = new SessionCallback(mContext) {
- @Override
- public void onPlayFromSearch(MediaSession2 session, ControllerInfo controller,
- String query, Bundle extras) {
- super.onPlayFromSearch(session, controller, query, extras);
- assertEquals(mContext.getPackageName(), controller.getPackageName());
- assertEquals(request, query);
- assertTrue(TestUtils.equals(bundle, extras));
- latch.countDown();
- }
- };
- try (MediaSession2 session = new MediaSession2.Builder(mContext)
- .setPlayer(mPlayer)
- .setSessionCallback(sHandlerExecutor, callback)
- .setId("testPlayFromSearch").build()) {
- MediaController2 controller = createController(session.getToken());
- controller.playFromSearch(request, bundle);
- assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
- }
- }
-
- @Test
- public void testPlayFromUri() throws InterruptedException {
- final Uri request = Uri.parse("foo://boo");
- final Bundle bundle = new Bundle();
- bundle.putString("key", "value");
- final CountDownLatch latch = new CountDownLatch(1);
- final SessionCallback callback = new SessionCallback(mContext) {
- @Override
- public void onPlayFromUri(MediaSession2 session, ControllerInfo controller, Uri uri,
- Bundle extras) {
- assertEquals(mContext.getPackageName(), controller.getPackageName());
- assertEquals(request, uri);
- assertTrue(TestUtils.equals(bundle, extras));
- latch.countDown();
- }
- };
- try (MediaSession2 session = new MediaSession2.Builder(mContext)
- .setPlayer(mPlayer)
- .setSessionCallback(sHandlerExecutor, callback)
- .setId("testPlayFromUri").build()) {
- MediaController2 controller = createController(session.getToken());
- controller.playFromUri(request, bundle);
- assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
- }
- }
-
- @Test
- public void testPlayFromMediaId() throws InterruptedException {
- final String request = "media_id";
- final Bundle bundle = new Bundle();
- bundle.putString("key", "value");
- final CountDownLatch latch = new CountDownLatch(1);
- final SessionCallback callback = new SessionCallback(mContext) {
- @Override
- public void onPlayFromMediaId(MediaSession2 session, ControllerInfo controller,
- String mediaId, Bundle extras) {
- assertEquals(mContext.getPackageName(), controller.getPackageName());
- assertEquals(request, mediaId);
- assertTrue(TestUtils.equals(bundle, extras));
- latch.countDown();
- }
- };
- try (MediaSession2 session = new MediaSession2.Builder(mContext)
- .setPlayer(mPlayer)
- .setSessionCallback(sHandlerExecutor, callback)
- .setId("testPlayFromMediaId").build()) {
- MediaController2 controller = createController(session.getToken());
- controller.playFromMediaId(request, bundle);
- assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
- }
- }
-
-
- @Test
- public void testPrepareFromSearch() throws InterruptedException {
- final String request = "random query";
- final Bundle bundle = new Bundle();
- bundle.putString("key", "value");
- final CountDownLatch latch = new CountDownLatch(1);
- final SessionCallback callback = new SessionCallback(mContext) {
- @Override
- public void onPrepareFromSearch(MediaSession2 session, ControllerInfo controller,
- String query, Bundle extras) {
- assertEquals(mContext.getPackageName(), controller.getPackageName());
- assertEquals(request, query);
- assertTrue(TestUtils.equals(bundle, extras));
- latch.countDown();
- }
- };
- try (MediaSession2 session = new MediaSession2.Builder(mContext)
- .setPlayer(mPlayer)
- .setSessionCallback(sHandlerExecutor, callback)
- .setId("testPrepareFromSearch").build()) {
- MediaController2 controller = createController(session.getToken());
- controller.prepareFromSearch(request, bundle);
- assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
- }
- }
-
- @Test
- public void testPrepareFromUri() throws InterruptedException {
- final Uri request = Uri.parse("foo://boo");
- final Bundle bundle = new Bundle();
- bundle.putString("key", "value");
- final CountDownLatch latch = new CountDownLatch(1);
- final SessionCallback callback = new SessionCallback(mContext) {
- @Override
- public void onPrepareFromUri(MediaSession2 session, ControllerInfo controller, Uri uri,
- Bundle extras) {
- assertEquals(mContext.getPackageName(), controller.getPackageName());
- assertEquals(request, uri);
- assertTrue(TestUtils.equals(bundle, extras));
- latch.countDown();
- }
- };
- try (MediaSession2 session = new MediaSession2.Builder(mContext)
- .setPlayer(mPlayer)
- .setSessionCallback(sHandlerExecutor, callback)
- .setId("testPrepareFromUri").build()) {
- MediaController2 controller = createController(session.getToken());
- controller.prepareFromUri(request, bundle);
- assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
- }
- }
-
- @Test
- public void testPrepareFromMediaId() throws InterruptedException {
- final String request = "media_id";
- final Bundle bundle = new Bundle();
- bundle.putString("key", "value");
- final CountDownLatch latch = new CountDownLatch(1);
- final SessionCallback callback = new SessionCallback(mContext) {
- @Override
- public void onPrepareFromMediaId(MediaSession2 session, ControllerInfo controller,
- String mediaId, Bundle extras) {
- assertEquals(mContext.getPackageName(), controller.getPackageName());
- assertEquals(request, mediaId);
- assertTrue(TestUtils.equals(bundle, extras));
- latch.countDown();
- }
- };
- try (MediaSession2 session = new MediaSession2.Builder(mContext)
- .setPlayer(mPlayer)
- .setSessionCallback(sHandlerExecutor, callback)
- .setId("testPrepareFromMediaId").build()) {
- MediaController2 controller = createController(session.getToken());
- controller.prepareFromMediaId(request, bundle);
- assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
- }
- }
-
- @Test
- public void testSetRating() throws InterruptedException {
- final int ratingType = Rating2.RATING_5_STARS;
- final float ratingValue = 3.5f;
- final Rating2 rating = Rating2.newStarRating(mContext, ratingType, ratingValue);
- final String mediaId = "media_id";
-
- final CountDownLatch latch = new CountDownLatch(1);
- final SessionCallback callback = new SessionCallback(mContext) {
- @Override
- public void onSetRating(MediaSession2 session, ControllerInfo controller,
- String mediaIdOut, Rating2 ratingOut) {
- assertEquals(mContext.getPackageName(), controller.getPackageName());
- assertEquals(mediaId, mediaIdOut);
- assertEquals(rating, ratingOut);
- latch.countDown();
- }
- };
-
- try (MediaSession2 session = new MediaSession2.Builder(mContext)
- .setPlayer(mPlayer)
- .setSessionCallback(sHandlerExecutor, callback)
- .setId("testSetRating").build()) {
- MediaController2 controller = createController(session.getToken());
- controller.setRating(mediaId, rating);
- assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
- }
- }
-
- @Test
- public void testIsConnected() throws InterruptedException {
- assertTrue(mController.isConnected());
- sHandler.postAndSync(()->{
- mSession.close();
- });
- // postAndSync() to wait until the disconnection is propagated.
- sHandler.postAndSync(()->{
- assertFalse(mController.isConnected());
- });
- }
-
- /**
- * Test potential deadlock for calls between controller and session.
- */
- @Test
- public void testDeadlock() throws InterruptedException {
- sHandler.postAndSync(() -> {
- mSession.close();
- mSession = null;
- });
-
- // Two more threads are needed not to block test thread nor test wide thread (sHandler).
- final HandlerThread sessionThread = new HandlerThread("testDeadlock_session");
- final HandlerThread testThread = new HandlerThread("testDeadlock_test");
- sessionThread.start();
- testThread.start();
- final SyncHandler sessionHandler = new SyncHandler(sessionThread.getLooper());
- final Handler testHandler = new Handler(testThread.getLooper());
- final CountDownLatch latch = new CountDownLatch(1);
- try {
- final MockPlayer player = new MockPlayer(0);
- sessionHandler.postAndSync(() -> {
- mSession = new MediaSession2.Builder(mContext)
- .setPlayer(mPlayer)
- .setSessionCallback(sHandlerExecutor, new SessionCallback(mContext) {})
- .setId("testDeadlock").build();
- });
- final MediaController2 controller = createController(mSession.getToken());
- testHandler.post(() -> {
- final PlaybackState2 state = createPlaybackState(PlaybackState2.STATE_ERROR);
- for (int i = 0; i < 100; i++) {
- // triggers call from session to controller.
- player.notifyPlaybackState(state);
- // triggers call from controller to session.
- controller.play();
-
- // Repeat above
- player.notifyPlaybackState(state);
- controller.pause();
- player.notifyPlaybackState(state);
- controller.stop();
- player.notifyPlaybackState(state);
- controller.skipToNext();
- player.notifyPlaybackState(state);
- controller.skipToPrevious();
- }
- // This may hang if deadlock happens.
- latch.countDown();
- });
- assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
- } finally {
- if (mSession != null) {
- sessionHandler.postAndSync(() -> {
- // Clean up here because sessionHandler will be removed afterwards.
- mSession.close();
- mSession = null;
- });
- }
- if (sessionThread != null) {
- sessionThread.quitSafely();
- }
- if (testThread != null) {
- testThread.quitSafely();
- }
- }
- }
-
- @Test
- public void testGetServiceToken() {
- SessionToken2 token = TestUtils.getServiceToken(mContext, MockMediaSessionService2.ID);
- assertNotNull(token);
- assertEquals(mContext.getPackageName(), token.getPackageName());
- assertEquals(MockMediaSessionService2.ID, token.getId());
- assertEquals(SessionToken2.TYPE_SESSION_SERVICE, token.getType());
- }
-
- private void connectToService(SessionToken2 token) throws InterruptedException {
- if (mSession != null) {
- mSession.close();
- }
- mController = createController(token);
- mSession = TestServiceRegistry.getInstance().getServiceInstance().getSession();
- mPlayer = (MockPlayer) mSession.getPlayer();
- }
-
- @Test
- public void testConnectToService_sessionService() throws InterruptedException {
- testConnectToService(MockMediaSessionService2.ID);
- }
-
- @Ignore
- @Test
- public void testConnectToService_libraryService() throws InterruptedException {
- testConnectToService(MockMediaLibraryService2.ID);
- }
-
- public void testConnectToService(String id) throws InterruptedException {
- final CountDownLatch latch = new CountDownLatch(1);
- final SessionCallbackProxy proxy = new SessionCallbackProxy(mContext) {
- @Override
- public CommandGroup onConnect(ControllerInfo controller) {
- if (Process.myUid() == controller.getUid()) {
- assertEquals(mContext.getPackageName(), controller.getPackageName());
- assertFalse(controller.isTrusted());
- latch.countDown();
- }
- return super.onConnect(controller);
- }
- };
- TestServiceRegistry.getInstance().setSessionCallbackProxy(proxy);
- connectToService(TestUtils.getServiceToken(mContext, id));
- assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
- // Test command from controller to session service
- mController.play();
- assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
- assertTrue(mPlayer.mPlayCalled);
-
- // Test command from session service to controller
- // TODO(jaewan): Add equivalent tests again
- /*
- final CountDownLatch latch = new CountDownLatch(1);
- mController.registerPlayerEventCallback((state) -> {
- assertNotNull(state);
- assertEquals(PlaybackState.STATE_REWINDING, state.getState());
- latch.countDown();
- }, sHandler);
- mPlayer.notifyPlaybackState(
- TestUtils.createPlaybackState(PlaybackState.STATE_REWINDING));
- assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
- */
- }
-
- @Test
- public void testControllerAfterSessionIsGone_session() throws InterruptedException {
- testControllerAfterSessionIsGone(mSession.getToken().getId());
- }
-
- @Ignore
- @Test
- public void testControllerAfterSessionIsGone_sessionService() throws InterruptedException {
- connectToService(TestUtils.getServiceToken(mContext, MockMediaSessionService2.ID));
- testControllerAfterSessionIsGone(MockMediaSessionService2.ID);
- }
-
- @Test
- public void testClose_beforeConnected() throws InterruptedException {
- MediaController2 controller =
- createController(mSession.getToken(), false, null);
- controller.close();
- }
-
- @Test
- public void testClose_twice() throws InterruptedException {
- mController.close();
- mController.close();
- }
-
- @Test
- public void testClose_session() throws InterruptedException {
- final String id = mSession.getToken().getId();
- mController.close();
- // close is done immediately for session.
- testNoInteraction();
-
- // Test whether the controller is notified about later close of the session or
- // re-creation.
- testControllerAfterSessionIsGone(id);
- }
-
- @Test
- public void testClose_sessionService() throws InterruptedException {
- testCloseFromService(MockMediaSessionService2.ID);
- }
-
- @Test
- public void testClose_libraryService() throws InterruptedException {
- testCloseFromService(MockMediaLibraryService2.ID);
- }
-
- private void testCloseFromService(String id) throws InterruptedException {
- final CountDownLatch latch = new CountDownLatch(1);
- final SessionCallbackProxy proxy = new SessionCallbackProxy(mContext) {
- @Override
- public void onServiceDestroyed() {
- latch.countDown();
- }
- };
- TestServiceRegistry.getInstance().setSessionCallbackProxy(proxy);
- mController = createController(TestUtils.getServiceToken(mContext, id));
- mController.close();
- // Wait until close triggers onDestroy() of the session service.
- assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
- assertNull(TestServiceRegistry.getInstance().getServiceInstance());
- testNoInteraction();
-
- // Test whether the controller is notified about later close of the session or
- // re-creation.
- testControllerAfterSessionIsGone(id);
- }
-
- private void testControllerAfterSessionIsGone(final String id) throws InterruptedException {
- sHandler.postAndSync(() -> {
- // TODO(jaewan): Use Session.close later when we add the API.
- mSession.close();
- });
- waitForDisconnect(mController, true);
- testNoInteraction();
-
- // Ensure that the controller cannot use newly create session with the same ID.
- sHandler.postAndSync(() -> {
- // Recreated session has different session stub, so previously created controller
- // shouldn't be available.
- mSession = new MediaSession2.Builder(mContext)
- .setPlayer(mPlayer)
- .setSessionCallback(sHandlerExecutor, new SessionCallback(mContext) {})
- .setId(id).build();
- });
- testNoInteraction();
- }
-
- private void testNoInteraction() throws InterruptedException {
- // TODO: Uncomment
- /*
- final CountDownLatch latch = new CountDownLatch(1);
- final PlayerEventCallback callback = new PlayerEventCallback() {
- @Override
- public void onPlaybackStateChanged(PlaybackState2 state) {
- fail("Controller shouldn't be notified about change in session after the close.");
- latch.countDown();
- }
- };
- */
-
- // TODO(jaewan): Add equivalent tests again
- /*
- mController.registerPlayerEventCallback(playbackListener, sHandler);
- mPlayer.notifyPlaybackState(TestUtils.createPlaybackState(PlaybackState.STATE_BUFFERING));
- assertFalse(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
- mController.unregisterPlayerEventCallback(playbackListener);
- */
- }
-
- // TODO(jaewan): Add test for service connect rejection, when we differentiate session
- // active/inactive and connection accept/refuse
-
- class TestVolumeProvider extends VolumeProvider2 {
- final CountDownLatch mLatch = new CountDownLatch(1);
- boolean mSetVolumeToCalled;
- boolean mAdjustVolumeCalled;
- int mVolume;
- int mDirection;
-
- public TestVolumeProvider(Context context, int controlType, int maxVolume,
- int currentVolume) {
- super(context, controlType, maxVolume, currentVolume);
- }
-
- @Override
- public void onSetVolumeTo(int volume) {
- mSetVolumeToCalled = true;
- mVolume = volume;
- mLatch.countDown();
- }
-
- @Override
- public void onAdjustVolume(int direction) {
- mAdjustVolumeCalled = true;
- mDirection = direction;
- mLatch.countDown();
- }
- }
-}
diff --git a/packages/MediaComponents/test/src/android/media/MediaMetadata2Test.java b/packages/MediaComponents/test/src/android/media/MediaMetadata2Test.java
deleted file mode 100644
index 06b51bd..0000000
--- a/packages/MediaComponents/test/src/android/media/MediaMetadata2Test.java
+++ /dev/null
@@ -1,63 +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 android.media;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertTrue;
-
-import android.content.Context;
-import android.media.MediaMetadata2.Builder;
-import android.os.Bundle;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-import android.support.test.InstrumentationRegistry;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class MediaMetadata2Test {
- private Context mContext;
-
- @Before
- public void setUp() throws Exception {
- mContext = InstrumentationRegistry.getTargetContext();
- }
-
- @Test
- public void testBuilder() {
- final Bundle extras = new Bundle();
- extras.putString("MediaMetadata2Test", "testBuilder");
- final String title = "title";
- final long discNumber = 10;
- final Rating2 rating = Rating2.newThumbRating(mContext, true);
-
- MediaMetadata2.Builder builder = new Builder(mContext);
- builder.setExtras(extras);
- builder.putString(MediaMetadata2.METADATA_KEY_DISPLAY_TITLE, title);
- builder.putLong(MediaMetadata2.METADATA_KEY_DISC_NUMBER, discNumber);
- builder.putRating(MediaMetadata2.METADATA_KEY_USER_RATING, rating);
-
- MediaMetadata2 metadata = builder.build();
- assertTrue(TestUtils.equals(extras, metadata.getExtras()));
- assertEquals(title, metadata.getString(MediaMetadata2.METADATA_KEY_DISPLAY_TITLE));
- assertEquals(discNumber, metadata.getLong(MediaMetadata2.METADATA_KEY_DISC_NUMBER));
- assertEquals(rating, metadata.getRating(MediaMetadata2.METADATA_KEY_USER_RATING));
- }
-}
diff --git a/packages/MediaComponents/test/src/android/media/MediaSession2Test.java b/packages/MediaComponents/test/src/android/media/MediaSession2Test.java
deleted file mode 100644
index 8c1a749..0000000
--- a/packages/MediaComponents/test/src/android/media/MediaSession2Test.java
+++ /dev/null
@@ -1,533 +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 android.media;
-
-import static android.media.AudioAttributes.CONTENT_TYPE_MUSIC;
-import static android.media.TestUtils.ensurePlaylistParamsModeEquals;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertTrue;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.fail;
-
-import android.content.Context;
-import android.media.MediaController2.PlaybackInfo;
-import android.media.MediaSession2.Builder;
-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.MediaSession2.SessionCallback;
-import android.os.Bundle;
-import android.os.Process;
-import android.os.ResultReceiver;
-import android.support.annotation.NonNull;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-import android.text.TextUtils;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests {@link MediaSession2}.
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class MediaSession2Test extends MediaSession2TestBase {
- private static final String TAG = "MediaSession2Test";
-
- private MediaSession2 mSession;
- private MockPlayer mPlayer;
-
- @Before
- @Override
- public void setUp() throws Exception {
- super.setUp();
- mPlayer = new MockPlayer(0);
- mSession = new MediaSession2.Builder(mContext).setPlayer(mPlayer)
- .setSessionCallback(sHandlerExecutor, new SessionCallback(mContext) {}).build();
- }
-
- @After
- @Override
- public void cleanUp() throws Exception {
- super.cleanUp();
- mSession.close();
- }
-
- @Ignore
- @Test
- public void testBuilder() throws Exception {
- try {
- MediaSession2.Builder builder = new Builder(mContext);
- fail("null player shouldn't be allowed");
- } catch (IllegalArgumentException e) {
- // expected. pass-through
- }
- MediaSession2.Builder builder = new Builder(mContext).setPlayer(mPlayer);
- try {
- builder.setId(null);
- fail("null id shouldn't be allowed");
- } catch (IllegalArgumentException e) {
- // expected. pass-through
- }
- }
-
- @Test
- public void testUpdatePlayer() throws Exception {
- MockPlayer player = new MockPlayer(0);
- // Test if setPlayer doesn't crash with various situations.
- mSession.updatePlayer(mPlayer, null, null);
- mSession.updatePlayer(player, null, null);
- mSession.close();
- }
-
- @Test
- public void testSetPlayer_playbackInfo() throws Exception {
- MockPlayer player = new MockPlayer(0);
- AudioAttributes attrs = new AudioAttributes.Builder()
- .setContentType(CONTENT_TYPE_MUSIC)
- .build();
- player.setAudioAttributes(attrs);
-
- final int maxVolume = 100;
- final int currentVolume = 23;
- final int volumeControlType = VolumeProvider2.VOLUME_CONTROL_ABSOLUTE;
- VolumeProvider2 volumeProvider =
- new VolumeProvider2(mContext, volumeControlType, maxVolume, currentVolume) { };
-
- final CountDownLatch latch = new CountDownLatch(1);
- final TestControllerCallbackInterface callback = new TestControllerCallbackInterface() {
- @Override
- public void onPlaybackInfoChanged(PlaybackInfo info) {
- assertEquals(MediaController2.PlaybackInfo.PLAYBACK_TYPE_REMOTE,
- info.getPlaybackType());
- assertEquals(attrs, info.getAudioAttributes());
- assertEquals(volumeControlType, info.getPlaybackType());
- assertEquals(maxVolume, info.getMaxVolume());
- assertEquals(currentVolume, info.getCurrentVolume());
- latch.countDown();
- }
- };
-
- mSession.updatePlayer(player, null, null);
-
- final MediaController2 controller = createController(mSession.getToken(), true, callback);
- PlaybackInfo info = controller.getPlaybackInfo();
- assertNotNull(info);
- assertEquals(PlaybackInfo.PLAYBACK_TYPE_LOCAL, info.getPlaybackType());
- assertEquals(attrs, info.getAudioAttributes());
- AudioManager manager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
- int localVolumeControlType = manager.isVolumeFixed()
- ? VolumeProvider2.VOLUME_CONTROL_FIXED : VolumeProvider2.VOLUME_CONTROL_ABSOLUTE;
- assertEquals(localVolumeControlType, info.getControlType());
- assertEquals(manager.getStreamMaxVolume(AudioManager.STREAM_MUSIC), info.getMaxVolume());
- assertEquals(manager.getStreamVolume(AudioManager.STREAM_MUSIC), info.getCurrentVolume());
-
- mSession.updatePlayer(player, null, volumeProvider);
- assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-
- info = controller.getPlaybackInfo();
- assertNotNull(info);
- assertEquals(PlaybackInfo.PLAYBACK_TYPE_REMOTE, info.getPlaybackType());
- assertEquals(attrs, info.getAudioAttributes());
- assertEquals(volumeControlType, info.getControlType());
- assertEquals(maxVolume, info.getMaxVolume());
- assertEquals(currentVolume, info.getCurrentVolume());
- }
-
- @Test
- public void testPlay() throws Exception {
- sHandler.postAndSync(() -> {
- mSession.play();
- assertTrue(mPlayer.mPlayCalled);
- });
- }
-
- @Test
- public void testPause() throws Exception {
- sHandler.postAndSync(() -> {
- mSession.pause();
- assertTrue(mPlayer.mPauseCalled);
- });
- }
-
- @Ignore
- @Test
- public void testStop() throws Exception {
- sHandler.postAndSync(() -> {
- mSession.stop();
- assertTrue(mPlayer.mStopCalled);
- });
- }
-
- @Test
- public void testSkipToNext() throws Exception {
- sHandler.postAndSync(() -> {
- mSession.skipToNext();
- assertTrue(mPlayer.mSkipToNextCalled);
- });
- }
-
- @Ignore
- @Test
- public void testSkipToPrevious() throws Exception {
- sHandler.postAndSync(() -> {
- mSession.skipToPrevious();
- assertTrue(mPlayer.mSkipToPreviousCalled);
- });
- }
-
- @Ignore
- @Test
- public void testSetPlaylist() throws Exception {
- final List<MediaItem2> playlist = new ArrayList<>();
-
- final CountDownLatch latch = new CountDownLatch(1);
- final TestControllerCallbackInterface callback = new TestControllerCallbackInterface() {
- @Override
- public void onPlaylistChanged(List<MediaItem2> givenList) {
- assertMediaItemListEquals(playlist, givenList);
- latch.countDown();
- }
- };
-
- final MediaController2 controller = createController(mSession.getToken(), true, callback);
- mSession.setPlaylist(playlist);
-
- assertTrue(mPlayer.mSetPlaylistCalled);
- assertMediaItemListEquals(playlist, mPlayer.mPlaylist);
- assertMediaItemListEquals(playlist, mSession.getPlaylist());
-
- assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
- assertMediaItemListEquals(playlist, controller.getPlaylist());
- }
-
- @Ignore
- @Test
- public void testSetPlaylistParams() throws Exception {
- final PlaylistParams params = new PlaylistParams(mContext,
- PlaylistParams.REPEAT_MODE_ALL,
- PlaylistParams.SHUFFLE_MODE_ALL,
- null /* PlaylistMetadata */);
-
- final CountDownLatch latch = new CountDownLatch(1);
- final TestControllerCallbackInterface callback = new TestControllerCallbackInterface() {
- @Override
- public void onPlaylistParamsChanged(PlaylistParams givenParams) {
- ensurePlaylistParamsModeEquals(params, givenParams);
- latch.countDown();
- }
- };
-
- final MediaController2 controller = createController(mSession.getToken(), true, callback);
- mSession.setPlaylistParams(params);
- assertTrue(mPlayer.mSetPlaylistParamsCalled);
- ensurePlaylistParamsModeEquals(params, mPlayer.mPlaylistParams);
- ensurePlaylistParamsModeEquals(params, mSession.getPlaylistParams());
- assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
- }
-
- @Ignore
- @Test
- public void testRegisterEventCallback() throws InterruptedException {
- final int testWhat = 1001;
- final MockPlayer player = new MockPlayer(0);
- final CountDownLatch playbackLatch = new CountDownLatch(3);
- final CountDownLatch errorLatch = new CountDownLatch(1);
- // TODO: Uncomment or remove
- /*
- final PlayerEventCallback callback = new PlayerEventCallback() {
- @Override
- public void onPlaybackStateChanged(PlaybackState2 state) {
- assertEquals(sHandler.getLooper(), Looper.myLooper());
- switch ((int) playbackLatch.getCount()) {
- case 3:
- assertNull(state);
- break;
- case 2:
- assertNotNull(state);
- assertEquals(PlaybackState2.STATE_PLAYING, state.getState());
- break;
- case 1:
- assertNotNull(state);
- assertEquals(PlaybackState2.STATE_PAUSED, state.getState());
- break;
- case 0:
- fail();
- }
- playbackLatch.countDown();
- }
-
- @Override
- public void onError(String mediaId, int what, int extra) {
- assertEquals(testWhat, what);
- errorLatch.countDown();
- }
- };
- */
- player.notifyPlaybackState(createPlaybackState(PlaybackState2.STATE_PLAYING));
- // EventCallback will be notified with the mPlayer's playback state (null)
- // TODO: Uncomment or remove
- //mSession.registerPlayerEventCallback(sHandlerExecutor, callback);
- // When the player is set, EventCallback will be notified about the new player's state.
- mSession.updatePlayer(player, null, null);
- // When the player is set, EventCallback will be notified about the new player's state.
- player.notifyPlaybackState(createPlaybackState(PlaybackState2.STATE_PAUSED));
- assertTrue(playbackLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
- player.notifyError(testWhat);
- assertTrue(errorLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
- }
-
- @Test
- public void testBadPlayer() throws InterruptedException {
- // TODO(jaewan): Add equivalent tests again
- final CountDownLatch latch = new CountDownLatch(4); // expected call + 1
- final BadPlayer player = new BadPlayer(0);
- // TODO: Uncomment or remove
- /*
- mSession.registerPlayerEventCallback(sHandlerExecutor, new PlayerEventCallback() {
- @Override
- public void onPlaybackStateChanged(PlaybackState2 state) {
- // This will be called for every setPlayer() calls, but no more.
- assertNull(state);
- latch.countDown();
- }
- });
- */
- mSession.updatePlayer(player, null, null);
- mSession.updatePlayer(mPlayer, null, null);
- player.notifyPlaybackState(createPlaybackState(PlaybackState2.STATE_PAUSED));
- assertFalse(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
- }
-
- private static class BadPlayer extends MockPlayer {
- public BadPlayer(int count) {
- super(count);
- }
-
- @Override
- public void unregisterPlayerEventCallback(@NonNull PlayerEventCallback listener) {
- // No-op. This bad player will keep push notification to the listener that is previously
- // registered by session.setPlayer().
- }
- }
-
- @Test
- public void testOnCommandCallback() throws InterruptedException {
- final MockOnCommandCallback callback = new MockOnCommandCallback();
- sHandler.postAndSync(() -> {
- mSession.close();
- mPlayer = new MockPlayer(1);
- mSession = new MediaSession2.Builder(mContext).setPlayer(mPlayer)
- .setSessionCallback(sHandlerExecutor, callback).build();
- });
- MediaController2 controller = createController(mSession.getToken());
- controller.pause();
- assertFalse(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
- assertFalse(mPlayer.mPauseCalled);
- assertEquals(1, callback.commands.size());
- assertEquals(MediaSession2.COMMAND_CODE_PLAYBACK_PAUSE,
- (long) callback.commands.get(0).getCommandCode());
- controller.skipToNext();
- assertTrue(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
- assertTrue(mPlayer.mSkipToNextCalled);
- assertFalse(mPlayer.mPauseCalled);
- assertEquals(2, callback.commands.size());
- assertEquals(MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_NEXT_ITEM,
- (long) callback.commands.get(1).getCommandCode());
- }
-
- @Test
- public void testOnConnectCallback() throws InterruptedException {
- final MockOnConnectCallback sessionCallback = new MockOnConnectCallback();
- sHandler.postAndSync(() -> {
- mSession.close();
- mSession = new MediaSession2.Builder(mContext).setPlayer(mPlayer)
- .setSessionCallback(sHandlerExecutor, sessionCallback).build();
- });
- MediaController2 controller =
- createController(mSession.getToken(), false, null);
- assertNotNull(controller);
- waitForConnect(controller, false);
- waitForDisconnect(controller, true);
- }
-
- @Test
- public void testSetCustomLayout() throws InterruptedException {
- final List<CommandButton> buttons = new ArrayList<>();
- buttons.add(new CommandButton.Builder(mContext)
- .setCommand(new Command(mContext, MediaSession2.COMMAND_CODE_PLAYBACK_PLAY))
- .setDisplayName("button").build());
- final CountDownLatch latch = new CountDownLatch(1);
- final SessionCallback sessionCallback = new SessionCallback(mContext) {
- @Override
- public CommandGroup onConnect(MediaSession2 session,
- ControllerInfo controller) {
- if (mContext.getPackageName().equals(controller.getPackageName())) {
- mSession.setCustomLayout(controller, buttons);
- }
- return super.onConnect(session, controller);
- }
- };
-
- try (final MediaSession2 session = new MediaSession2.Builder(mContext)
- .setPlayer(mPlayer)
- .setId("testSetCustomLayout")
- .setSessionCallback(sHandlerExecutor, sessionCallback)
- .build()) {
- if (mSession != null) {
- mSession.close();
- mSession = session;
- }
- final TestControllerCallbackInterface callback = new TestControllerCallbackInterface() {
- @Override
- public void onCustomLayoutChanged(List<CommandButton> layout) {
- assertEquals(layout.size(), buttons.size());
- for (int i = 0; i < layout.size(); i++) {
- assertEquals(layout.get(i).getCommand(), buttons.get(i).getCommand());
- assertEquals(layout.get(i).getDisplayName(),
- buttons.get(i).getDisplayName());
- }
- latch.countDown();
- }
- };
- final MediaController2 controller =
- createController(session.getToken(), true, callback);
- assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
- }
- }
-
- @Test
- public void testSendCustomAction() throws InterruptedException {
- final Command testCommand =
- new Command(mContext, MediaSession2.COMMAND_CODE_PLAYBACK_PREPARE);
- final Bundle testArgs = new Bundle();
- testArgs.putString("args", "testSendCustomAction");
-
- final CountDownLatch latch = new CountDownLatch(2);
- final TestControllerCallbackInterface callback = new TestControllerCallbackInterface() {
- @Override
- public void onCustomCommand(Command command, Bundle args, ResultReceiver receiver) {
- assertEquals(testCommand, command);
- assertTrue(TestUtils.equals(testArgs, args));
- assertNull(receiver);
- latch.countDown();
- }
- };
- final MediaController2 controller =
- createController(mSession.getToken(), true, callback);
- // TODO(jaewan): Test with multiple controllers
- mSession.sendCustomCommand(testCommand, testArgs);
-
- ControllerInfo controllerInfo = getTestControllerInfo();
- assertNotNull(controllerInfo);
- // TODO(jaewan): Test receivers as well.
- mSession.sendCustomCommand(controllerInfo, testCommand, testArgs, null);
- assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
- }
-
- private ControllerInfo getTestControllerInfo() {
- List<ControllerInfo> controllers = mSession.getConnectedControllers();
- assertNotNull(controllers);
- final String packageName = mContext.getPackageName();
- for (int i = 0; i < controllers.size(); i++) {
- if (TextUtils.equals(packageName, controllers.get(i).getPackageName())) {
- return controllers.get(i);
- }
- }
- fail("Fails to get custom command");
- return null;
- }
-
- public class MockOnConnectCallback extends SessionCallback {
- public MockOnConnectCallback() {
- super(mContext);
- }
-
- @Override
- public MediaSession2.CommandGroup onConnect(MediaSession2 session,
- ControllerInfo controllerInfo) {
- if (Process.myUid() != controllerInfo.getUid()) {
- return null;
- }
- assertEquals(mContext.getPackageName(), controllerInfo.getPackageName());
- assertEquals(Process.myUid(), controllerInfo.getUid());
- assertFalse(controllerInfo.isTrusted());
- // Reject all
- return null;
- }
- }
-
- public class MockOnCommandCallback extends SessionCallback {
- public final ArrayList<MediaSession2.Command> commands = new ArrayList<>();
-
- public MockOnCommandCallback() {
- super(mContext);
- }
-
- @Override
- public boolean onCommandRequest(MediaSession2 session, ControllerInfo controllerInfo,
- MediaSession2.Command command) {
- assertEquals(mContext.getPackageName(), controllerInfo.getPackageName());
- assertEquals(Process.myUid(), controllerInfo.getUid());
- assertFalse(controllerInfo.isTrusted());
- commands.add(command);
- if (command.getCommandCode() == MediaSession2.COMMAND_CODE_PLAYBACK_PAUSE) {
- return false;
- }
- return true;
- }
- }
-
- private static void assertMediaItemListEquals(List<MediaItem2> a, List<MediaItem2> b) {
- if (a == null || b == null) {
- assertEquals(a, b);
- }
- assertEquals(a.size(), b.size());
-
- for (int i = 0; i < a.size(); i++) {
- MediaItem2 aItem = a.get(i);
- MediaItem2 bItem = b.get(i);
-
- if (aItem == null || bItem == null) {
- assertEquals(aItem, bItem);
- continue;
- }
-
- assertEquals(aItem.getMediaId(), bItem.getMediaId());
- assertEquals(aItem.getFlags(), bItem.getFlags());
- TestUtils.equals(aItem.getMetadata().toBundle(), bItem.getMetadata().toBundle());
-
- // Note: Here it does not check whether DataSourceDesc are equal,
- // since there DataSourceDec is not comparable.
- }
- }
-}
diff --git a/packages/MediaComponents/test/src/android/media/MediaSession2TestBase.java b/packages/MediaComponents/test/src/android/media/MediaSession2TestBase.java
deleted file mode 100644
index b32400f..0000000
--- a/packages/MediaComponents/test/src/android/media/MediaSession2TestBase.java
+++ /dev/null
@@ -1,266 +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 android.media;
-
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
-
-import android.content.Context;
-import android.media.MediaController2.ControllerCallback;
-import android.media.MediaSession2.Command;
-import android.media.MediaSession2.CommandButton;
-import android.media.MediaSession2.CommandGroup;
-import android.os.Bundle;
-import android.os.HandlerThread;
-import android.os.ResultReceiver;
-import android.support.annotation.CallSuper;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.test.InstrumentationRegistry;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executor;
-import java.util.concurrent.TimeUnit;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-
-/**
- * Base class for session test.
- */
-abstract class MediaSession2TestBase {
- // Expected success
- static final int WAIT_TIME_MS = 1000;
-
- // Expected timeout
- static final int TIMEOUT_MS = 500;
-
- static TestUtils.SyncHandler sHandler;
- static Executor sHandlerExecutor;
-
- Context mContext;
- private List<MediaController2> mControllers = new ArrayList<>();
-
- interface TestControllerInterface {
- ControllerCallback getCallback();
- }
-
- // Any change here should be also reflected to the TestControllerCallback and
- // TestBrowserCallback
- interface TestControllerCallbackInterface {
- // Add methods in ControllerCallback that you want to test.
- default void onPlaylistChanged(List<MediaItem2> playlist) {}
- default void onPlaylistParamsChanged(MediaSession2.PlaylistParams params) {}
- default void onPlaybackInfoChanged(MediaController2.PlaybackInfo info) {}
- default void onPlaybackStateChanged(PlaybackState2 state) {}
- default void onCustomLayoutChanged(List<CommandButton> layout) {}
- default void onCustomCommand(Command command, Bundle args, ResultReceiver receiver) {}
- }
-
- interface WaitForConnectionInterface {
- void waitForConnect(boolean expect) throws InterruptedException;
- void waitForDisconnect(boolean expect) throws InterruptedException;
- }
-
- @BeforeClass
- public static void setUpThread() {
- if (sHandler == null) {
- HandlerThread handlerThread = new HandlerThread("MediaSession2TestBase");
- handlerThread.start();
- sHandler = new TestUtils.SyncHandler(handlerThread.getLooper());
- sHandlerExecutor = (runnable) -> {
- sHandler.post(runnable);
- };
- }
- }
-
- @AfterClass
- public static void cleanUpThread() {
- if (sHandler != null) {
- sHandler.getLooper().quitSafely();
- sHandler = null;
- sHandlerExecutor = null;
- }
- }
-
- @CallSuper
- public void setUp() throws Exception {
- mContext = InstrumentationRegistry.getTargetContext();
- }
-
- @CallSuper
- public void cleanUp() throws Exception {
- for (int i = 0; i < mControllers.size(); i++) {
- mControllers.get(i).close();
- }
- }
-
- /**
- * Creates a {@link android.media.session.PlaybackState} with the given state.
- *
- * @param state one of the PlaybackState.STATE_xxx.
- * @return a PlaybackState
- */
- public PlaybackState2 createPlaybackState(int state) {
- return new PlaybackState2(mContext, state, 0, 0, 1.0f, 0, 0);
- }
-
- final MediaController2 createController(SessionToken2 token) throws InterruptedException {
- return createController(token, true, null);
- }
-
- final MediaController2 createController(@NonNull SessionToken2 token,
- boolean waitForConnect, @Nullable TestControllerCallbackInterface callback)
- throws InterruptedException {
- TestControllerInterface instance = onCreateController(token, callback);
- if (!(instance instanceof MediaController2)) {
- throw new RuntimeException("Test has a bug. Expected MediaController2 but returned "
- + instance);
- }
- MediaController2 controller = (MediaController2) instance;
- mControllers.add(controller);
- if (waitForConnect) {
- waitForConnect(controller, true);
- }
- return controller;
- }
-
- private static WaitForConnectionInterface getWaitForConnectionInterface(
- MediaController2 controller) {
- if (!(controller instanceof TestControllerInterface)) {
- throw new RuntimeException("Test has a bug. Expected controller implemented"
- + " TestControllerInterface but got " + controller);
- }
- ControllerCallback callback = ((TestControllerInterface) controller).getCallback();
- if (!(callback instanceof WaitForConnectionInterface)) {
- throw new RuntimeException("Test has a bug. Expected controller with callback "
- + " implemented WaitForConnectionInterface but got " + controller);
- }
- return (WaitForConnectionInterface) callback;
- }
-
- public static void waitForConnect(MediaController2 controller, boolean expected)
- throws InterruptedException {
- getWaitForConnectionInterface(controller).waitForConnect(expected);
- }
-
- public static void waitForDisconnect(MediaController2 controller, boolean expected)
- throws InterruptedException {
- getWaitForConnectionInterface(controller).waitForDisconnect(expected);
- }
-
- TestControllerInterface onCreateController(@NonNull SessionToken2 token,
- @Nullable TestControllerCallbackInterface callback) {
- if (callback == null) {
- callback = new TestControllerCallbackInterface() {};
- }
- return new TestMediaController(mContext, token, new TestControllerCallback(callback));
- }
-
- public static class TestControllerCallback extends MediaController2.ControllerCallback
- implements WaitForConnectionInterface {
- public final TestControllerCallbackInterface mCallbackProxy;
- public final CountDownLatch connectLatch = new CountDownLatch(1);
- public final CountDownLatch disconnectLatch = new CountDownLatch(1);
-
- TestControllerCallback(@NonNull TestControllerCallbackInterface callbackProxy) {
- if (callbackProxy == null) {
- throw new IllegalArgumentException("Callback proxy shouldn't be null. Test bug");
- }
- mCallbackProxy = callbackProxy;
- }
-
- @CallSuper
- @Override
- public void onConnected(MediaController2 controller, CommandGroup commands) {
- connectLatch.countDown();
- }
-
- @CallSuper
- @Override
- public void onDisconnected(MediaController2 controller) {
- disconnectLatch.countDown();
- }
-
- @Override
- public void onPlaybackStateChanged(MediaController2 controller, PlaybackState2 state) {
- mCallbackProxy.onPlaybackStateChanged(state);
- }
-
- @Override
- public void onCustomCommand(MediaController2 controller, Command command, Bundle args,
- ResultReceiver receiver) {
- mCallbackProxy.onCustomCommand(command, args, receiver);
- }
-
- @Override
- public void waitForConnect(boolean expect) throws InterruptedException {
- if (expect) {
- assertTrue(connectLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
- } else {
- assertFalse(connectLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
- }
- }
-
- @Override
- public void waitForDisconnect(boolean expect) throws InterruptedException {
- if (expect) {
- assertTrue(disconnectLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
- } else {
- assertFalse(disconnectLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
- }
- }
-
- @Override
- public void onPlaylistChanged(MediaController2 controller, List<MediaItem2> params) {
- mCallbackProxy.onPlaylistChanged(params);
- }
-
- @Override
- public void onPlaylistParamsChanged(MediaController2 controller,
- MediaSession2.PlaylistParams params) {
- mCallbackProxy.onPlaylistParamsChanged(params);
- }
-
- @Override
- public void onPlaybackInfoChanged(MediaController2 controller,
- MediaController2.PlaybackInfo info) {
- mCallbackProxy.onPlaybackInfoChanged(info);
- }
-
- @Override
- public void onCustomLayoutChanged(MediaController2 controller, List<CommandButton> layout) {
- mCallbackProxy.onCustomLayoutChanged(layout);
- }
- }
-
- public class TestMediaController extends MediaController2 implements TestControllerInterface {
- private final ControllerCallback mCallback;
-
- public TestMediaController(@NonNull Context context, @NonNull SessionToken2 token,
- @NonNull ControllerCallback callback) {
- super(context, token, sHandlerExecutor, callback);
- mCallback = callback;
- }
-
- @Override
- public ControllerCallback getCallback() {
- return mCallback;
- }
- }
-}
diff --git a/packages/MediaComponents/test/src/android/media/MediaSession2_PermissionTest.java b/packages/MediaComponents/test/src/android/media/MediaSession2_PermissionTest.java
deleted file mode 100644
index d89cecd..0000000
--- a/packages/MediaComponents/test/src/android/media/MediaSession2_PermissionTest.java
+++ /dev/null
@@ -1,365 +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 android.media;
-
-import static android.media.MediaSession2.*;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.Mockito.after;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.media.MediaSession2;
-import android.media.MediaSession2.Command;
-import android.media.MediaSession2.CommandGroup;
-import android.media.MediaSession2.SessionCallback;
-import android.net.Uri;
-import android.os.Process;
-import android.support.test.filters.MediumTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentMatcher;
-import org.mockito.Mockito;
-
-/**
- * Tests whether {@link MediaSession2} receives commands that hasn't allowed.
- */
-@RunWith(AndroidJUnit4.class)
-@MediumTest
-public class MediaSession2_PermissionTest extends MediaSession2TestBase {
- private static final String SESSION_ID = "MediaSession2Test_permission";
-
- private MockPlayer mPlayer;
- private MediaSession2 mSession;
- private MediaSession2.SessionCallback mCallback;
-
- private MediaSession2 matchesSession() {
- return argThat((session) -> session == mSession);
- }
-
- private static ControllerInfo matchesCaller() {
- return argThat((controllerInfo) -> controllerInfo.getUid() == Process.myUid());
- }
-
- private static Command matches(final int commandCode) {
- return argThat((command) -> command.getCommandCode() == commandCode);
- }
-
- @Before
- @Override
- public void setUp() throws Exception {
- super.setUp();
- }
-
- @After
- @Override
- public void cleanUp() throws Exception {
- super.cleanUp();
- if (mSession != null) {
- mSession.close();
- mSession = null;
- }
- mPlayer = null;
- mCallback = null;
- }
-
- private MediaSession2 createSessionWithAllowedActions(CommandGroup commands) {
- mPlayer = new MockPlayer(0);
- if (commands == null) {
- commands = new CommandGroup(mContext);
- }
- mCallback = mock(SessionCallback.class);
- when(mCallback.onConnect(any(), any())).thenReturn(commands);
- if (mSession != null) {
- mSession.close();
- }
- mSession = new MediaSession2.Builder(mContext).setPlayer(mPlayer).setId(SESSION_ID)
- .setSessionCallback(sHandlerExecutor, mCallback).build();
- return mSession;
- }
-
- private CommandGroup createCommandGroupWith(int commandCode) {
- CommandGroup commands = new CommandGroup(mContext);
- commands.addCommand(new Command(mContext, commandCode));
- return commands;
- }
-
- private CommandGroup createCommandGroupWithout(int commandCode) {
- CommandGroup commands = new CommandGroup(mContext);
- commands.addAllPredefinedCommands();
- commands.removeCommand(new Command(mContext, commandCode));
- return commands;
- }
-
- @Test
- public void testPlay() throws InterruptedException {
- createSessionWithAllowedActions(createCommandGroupWith(COMMAND_CODE_PLAYBACK_PLAY));
- createController(mSession.getToken()).play();
- verify(mCallback, timeout(TIMEOUT_MS).atLeastOnce()).onCommandRequest(
- matchesSession(), matchesCaller(), matches(COMMAND_CODE_PLAYBACK_PLAY));
-
- createSessionWithAllowedActions(createCommandGroupWithout(COMMAND_CODE_PLAYBACK_PLAY));
- createController(mSession.getToken()).play();
- verify(mCallback, after(WAIT_TIME_MS).never()).onCommandRequest(any(), any(), any());
- }
-
- @Test
- public void testPause() throws InterruptedException {
- createSessionWithAllowedActions(createCommandGroupWith(COMMAND_CODE_PLAYBACK_PAUSE));
- createController(mSession.getToken()).pause();
- verify(mCallback, timeout(TIMEOUT_MS).atLeastOnce()).onCommandRequest(
- matchesSession(), matchesCaller(), matches(COMMAND_CODE_PLAYBACK_PAUSE));
-
- createSessionWithAllowedActions(createCommandGroupWithout(COMMAND_CODE_PLAYBACK_PAUSE));
- createController(mSession.getToken()).pause();
- verify(mCallback, after(WAIT_TIME_MS).never()).onCommandRequest(any(), any(), any());
- }
-
- @Test
- public void testStop() throws InterruptedException {
- createSessionWithAllowedActions(createCommandGroupWith(COMMAND_CODE_PLAYBACK_STOP));
- createController(mSession.getToken()).stop();
- verify(mCallback, timeout(TIMEOUT_MS).atLeastOnce()).onCommandRequest(
- matchesSession(), matchesCaller(), matches(COMMAND_CODE_PLAYBACK_STOP));
-
- createSessionWithAllowedActions(createCommandGroupWithout(COMMAND_CODE_PLAYBACK_STOP));
- createController(mSession.getToken()).stop();
- verify(mCallback, after(WAIT_TIME_MS).never()).onCommandRequest(any(), any(), any());
- }
-
- @Test
- public void testSkipToNext() throws InterruptedException {
- createSessionWithAllowedActions(
- createCommandGroupWith(COMMAND_CODE_PLAYBACK_SKIP_NEXT_ITEM));
- createController(mSession.getToken()).skipToNext();
- verify(mCallback, timeout(TIMEOUT_MS).atLeastOnce()).onCommandRequest(
- matchesSession(), matchesCaller(), matches(COMMAND_CODE_PLAYBACK_SKIP_NEXT_ITEM));
-
- createSessionWithAllowedActions(
- createCommandGroupWithout(COMMAND_CODE_PLAYBACK_SKIP_NEXT_ITEM));
- createController(mSession.getToken()).skipToNext();
- verify(mCallback, after(WAIT_TIME_MS).never()).onCommandRequest(any(), any(), any());
- }
-
- @Test
- public void testSkipToPrevious() throws InterruptedException {
- createSessionWithAllowedActions(
- createCommandGroupWith(COMMAND_CODE_PLAYBACK_SKIP_PREV_ITEM));
- createController(mSession.getToken()).skipToPrevious();
- verify(mCallback, timeout(TIMEOUT_MS).atLeastOnce()).onCommandRequest(
- matchesSession(), matchesCaller(), matches(COMMAND_CODE_PLAYBACK_SKIP_PREV_ITEM));
-
- createSessionWithAllowedActions(
- createCommandGroupWithout(COMMAND_CODE_PLAYBACK_SKIP_PREV_ITEM));
- createController(mSession.getToken()).skipToPrevious();
- verify(mCallback, after(WAIT_TIME_MS).never()).onCommandRequest(any(), any(), any());
- }
-
- @Test
- public void testFastForward() throws InterruptedException {
- createSessionWithAllowedActions(
- createCommandGroupWith(COMMAND_CODE_PLAYBACK_FAST_FORWARD));
- createController(mSession.getToken()).fastForward();
- verify(mCallback, timeout(TIMEOUT_MS).atLeastOnce()).onCommandRequest(
- matchesSession(), matchesCaller(), matches(COMMAND_CODE_PLAYBACK_FAST_FORWARD));
-
- createSessionWithAllowedActions(
- createCommandGroupWithout(COMMAND_CODE_PLAYBACK_FAST_FORWARD));
- createController(mSession.getToken()).fastForward();
- verify(mCallback, after(WAIT_TIME_MS).never()).onCommandRequest(any(), any(), any());
- }
-
- @Test
- public void testRewind() throws InterruptedException {
- createSessionWithAllowedActions(
- createCommandGroupWith(COMMAND_CODE_PLAYBACK_REWIND));
- createController(mSession.getToken()).rewind();
- verify(mCallback, timeout(TIMEOUT_MS).atLeastOnce()).onCommandRequest(
- matchesSession(), matchesCaller(), matches(COMMAND_CODE_PLAYBACK_REWIND));
-
- createSessionWithAllowedActions(createCommandGroupWithout(COMMAND_CODE_PLAYBACK_REWIND));
- createController(mSession.getToken()).rewind();
- verify(mCallback, after(WAIT_TIME_MS).never()).onCommandRequest(any(), any(), any());
- }
-
- @Test
- public void testSeekTo() throws InterruptedException {
- final long position = 10;
- createSessionWithAllowedActions(
- createCommandGroupWith(COMMAND_CODE_PLAYBACK_SEEK_TO));
- createController(mSession.getToken()).seekTo(position);
- verify(mCallback, timeout(TIMEOUT_MS).atLeastOnce()).onCommandRequest(
- matchesSession(), matchesCaller(), matches(COMMAND_CODE_PLAYBACK_SEEK_TO));
-
- createSessionWithAllowedActions(createCommandGroupWithout(COMMAND_CODE_PLAYBACK_SEEK_TO));
- createController(mSession.getToken()).seekTo(position);
- verify(mCallback, after(WAIT_TIME_MS).never()).onCommandRequest(any(), any(), any());
- }
-
- // TODO(jaewan): Uncomment when we implement skipToPlaylistItem()
- /*
- @Test
- public void testSkipToPlaylistItem() throws InterruptedException {
- final Uri uri = Uri.parse("set://current.playlist.item");
- final DataSourceDesc dsd = new DataSourceDesc.Builder()
- .setDataSource(mContext, uri).build();
- final MediaItem2 item = new MediaItem2.Builder(mContext, MediaItem2.FLAG_PLAYABLE)
- .setDataSourceDesc(dsd).build();
- createSessionWithAllowedActions(
- createCommandGroupWith(COMMAND_CODE_PLAYBACK_SET_CURRENT_PLAYLIST_ITEM));
- createController(mSession.getToken()).skipToPlaylistItem(item);
- verify(mCallback, timeout(TIMEOUT_MS).atLeastOnce()).onCommandRequest(matchesCaller(),
- matches(COMMAND_CODE_PLAYBACK_SET_CURRENT_PLAYLIST_ITEM));
-
- createSessionWithAllowedActions(
- createCommandGroupWithout(COMMAND_CODE_PLAYBACK_SET_CURRENT_PLAYLIST_ITEM));
- createController(mSession.getToken()).skipToPlaylistItem(item);
- verify(mCallback, after(WAIT_TIME_MS).never()).onCommandRequest(any(), any());
- }
- */
-
- @Test
- public void testSetPlaylistParams() throws InterruptedException {
- final PlaylistParams param = new PlaylistParams(mContext,
- PlaylistParams.REPEAT_MODE_ALL, PlaylistParams.SHUFFLE_MODE_ALL, null);
- createSessionWithAllowedActions(
- createCommandGroupWith(COMMAND_CODE_PLAYBACK_SET_PLAYLIST_PARAMS));
- createController(mSession.getToken()).setPlaylistParams(param);
- verify(mCallback, timeout(TIMEOUT_MS).atLeastOnce()).onCommandRequest(
- matchesSession(), matchesCaller(),
- matches(COMMAND_CODE_PLAYBACK_SET_PLAYLIST_PARAMS));
-
- createSessionWithAllowedActions(
- createCommandGroupWithout(COMMAND_CODE_PLAYBACK_SET_PLAYLIST_PARAMS));
- createController(mSession.getToken()).setPlaylistParams(param);
- verify(mCallback, after(WAIT_TIME_MS).never()).onCommandRequest(any(), any(), any());
- }
-
- @Test
- public void testSetVolume() throws InterruptedException {
- createSessionWithAllowedActions(createCommandGroupWith(COMMAND_CODE_SET_VOLUME));
- createController(mSession.getToken()).setVolumeTo(0, 0);
- verify(mCallback, timeout(TIMEOUT_MS).atLeastOnce()).onCommandRequest(
- matchesSession(), matchesCaller(), matches(COMMAND_CODE_SET_VOLUME));
-
- createSessionWithAllowedActions(createCommandGroupWithout(COMMAND_CODE_SET_VOLUME));
- createController(mSession.getToken()).setVolumeTo(0, 0);
- verify(mCallback, after(WAIT_TIME_MS).never()).onCommandRequest(any(), any(), any());
- }
-
- @Test
- public void testPlayFromMediaId() throws InterruptedException {
- final String mediaId = "testPlayFromMediaId";
- createSessionWithAllowedActions(
- createCommandGroupWith(COMMAND_CODE_PLAY_FROM_MEDIA_ID));
- createController(mSession.getToken()).playFromMediaId(mediaId, null);
- verify(mCallback, timeout(TIMEOUT_MS).atLeastOnce()).onPlayFromMediaId(
- matchesSession(), matchesCaller(), eq(mediaId), isNull());
-
- createSessionWithAllowedActions(
- createCommandGroupWithout(COMMAND_CODE_PLAY_FROM_MEDIA_ID));
- createController(mSession.getToken()).playFromMediaId(mediaId, null);
- verify(mCallback, after(WAIT_TIME_MS).never()).onPlayFromMediaId(
- any(), any(), any(), any());
- }
-
- @Test
- public void testPlayFromUri() throws InterruptedException {
- final Uri uri = Uri.parse("play://from.uri");
- createSessionWithAllowedActions(
- createCommandGroupWith(COMMAND_CODE_PLAY_FROM_URI));
- createController(mSession.getToken()).playFromUri(uri, null);
- verify(mCallback, timeout(TIMEOUT_MS).atLeastOnce()).onPlayFromUri(
- matchesSession(), matchesCaller(), eq(uri), isNull());
-
- createSessionWithAllowedActions(
- createCommandGroupWithout(COMMAND_CODE_PLAY_FROM_URI));
- createController(mSession.getToken()).playFromUri(uri, null);
- verify(mCallback, after(WAIT_TIME_MS).never()).onPlayFromUri(any(), any(), any(), any());
- }
-
- @Test
- public void testPlayFromSearch() throws InterruptedException {
- final String query = "testPlayFromSearch";
- createSessionWithAllowedActions(
- createCommandGroupWith(COMMAND_CODE_PLAY_FROM_SEARCH));
- createController(mSession.getToken()).playFromSearch(query, null);
- verify(mCallback, timeout(TIMEOUT_MS).atLeastOnce()).onPlayFromSearch(
- matchesSession(), matchesCaller(), eq(query), isNull());
-
- createSessionWithAllowedActions(
- createCommandGroupWithout(COMMAND_CODE_PLAY_FROM_SEARCH));
- createController(mSession.getToken()).playFromSearch(query, null);
- verify(mCallback, after(WAIT_TIME_MS).never()).onPlayFromSearch(any(), any(), any(), any());
- }
-
- @Test
- public void testPrepareFromMediaId() throws InterruptedException {
- final String mediaId = "testPrepareFromMediaId";
- createSessionWithAllowedActions(
- createCommandGroupWith(COMMAND_CODE_PREPARE_FROM_MEDIA_ID));
- createController(mSession.getToken()).prepareFromMediaId(mediaId, null);
- verify(mCallback, timeout(TIMEOUT_MS).atLeastOnce()).onPrepareFromMediaId(
- matchesSession(), matchesCaller(), eq(mediaId), isNull());
-
- createSessionWithAllowedActions(
- createCommandGroupWithout(COMMAND_CODE_PREPARE_FROM_MEDIA_ID));
- createController(mSession.getToken()).prepareFromMediaId(mediaId, null);
- verify(mCallback, after(WAIT_TIME_MS).never()).onPrepareFromMediaId(
- any(), any(), any(), any());
- }
-
- @Test
- public void testPrepareFromUri() throws InterruptedException {
- final Uri uri = Uri.parse("prepare://from.uri");
- createSessionWithAllowedActions(
- createCommandGroupWith(COMMAND_CODE_PREPARE_FROM_URI));
- createController(mSession.getToken()).prepareFromUri(uri, null);
- verify(mCallback, timeout(TIMEOUT_MS).atLeastOnce()).onPrepareFromUri(
- matchesSession(), matchesCaller(), eq(uri), isNull());
-
- createSessionWithAllowedActions(
- createCommandGroupWithout(COMMAND_CODE_PREPARE_FROM_URI));
- createController(mSession.getToken()).prepareFromUri(uri, null);
- verify(mCallback, after(WAIT_TIME_MS).never()).onPrepareFromUri(any(), any(), any(), any());
- }
-
- @Test
- public void testPrepareFromSearch() throws InterruptedException {
- final String query = "testPrepareFromSearch";
- createSessionWithAllowedActions(
- createCommandGroupWith(COMMAND_CODE_PREPARE_FROM_SEARCH));
- createController(mSession.getToken()).prepareFromSearch(query, null);
- verify(mCallback, timeout(TIMEOUT_MS).atLeastOnce()).onPrepareFromSearch(
- matchesSession(), matchesCaller(), eq(query), isNull());
-
- createSessionWithAllowedActions(
- createCommandGroupWithout(COMMAND_CODE_PREPARE_FROM_SEARCH));
- createController(mSession.getToken()).prepareFromSearch(query, null);
- verify(mCallback, after(WAIT_TIME_MS).never()).onPrepareFromSearch(
- any(), any(), any(), any());
- }
-}
diff --git a/packages/MediaComponents/test/src/android/media/MediaSessionManager_MediaSession2.java b/packages/MediaComponents/test/src/android/media/MediaSessionManager_MediaSession2.java
deleted file mode 100644
index 17b200f..0000000
--- a/packages/MediaComponents/test/src/android/media/MediaSessionManager_MediaSession2.java
+++ /dev/null
@@ -1,340 +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 android.media;
-
-import android.content.Context;
-import android.media.MediaSession2.ControllerInfo;
-import android.media.MediaSession2.SessionCallback;
-import android.media.session.MediaSessionManager;
-import android.media.session.MediaSessionManager.OnSessionTokensChangedListener;
-import android.media.session.PlaybackState;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.UUID;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import static org.junit.Assert.*;
-
-/**
- * Tests {@link MediaSessionManager} with {@link MediaSession2} specific APIs.
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class MediaSessionManager_MediaSession2 extends MediaSession2TestBase {
- private static final String TAG = "MediaSessionManager_MediaSession2";
-
- private MediaSessionManager mManager;
- private MediaSession2 mSession;
-
- @Before
- @Override
- public void setUp() throws Exception {
- super.setUp();
- mManager = (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
-
- // Specify TAG here so {@link MediaSession2.getInstance()} doesn't complaint about
- // per test thread differs across the {@link MediaSession2} with the same TAG.
- final MockPlayer player = new MockPlayer(1);
- mSession = new MediaSession2.Builder(mContext)
- .setPlayer(player)
- .setSessionCallback(sHandlerExecutor, new SessionCallback(mContext) { })
- .setId(TAG)
- .build();
- ensureChangeInSession();
- }
-
- @After
- @Override
- public void cleanUp() throws Exception {
- super.cleanUp();
- sHandler.removeCallbacksAndMessages(null);
- mSession.close();
- }
-
- // TODO(jaewan): Make this host-side test to see per-user behavior.
- @Ignore
- @Test
- public void testGetMediaSession2Tokens_hasMediaController() throws InterruptedException {
- final MockPlayer player = (MockPlayer) mSession.getPlayer();
- player.notifyPlaybackState(createPlaybackState(PlaybackState.STATE_STOPPED));
-
- MediaController2 controller = null;
- List<SessionToken2> tokens = mManager.getActiveSessionTokens();
- assertNotNull(tokens);
- for (int i = 0; i < tokens.size(); i++) {
- SessionToken2 token = tokens.get(i);
- if (mContext.getPackageName().equals(token.getPackageName())
- && TAG.equals(token.getId())) {
- assertNull(controller);
- controller = createController(token);
- }
- }
- assertNotNull(controller);
-
- // Test if the found controller is correct one.
- assertEquals(PlaybackState.STATE_STOPPED, controller.getPlaybackState().getState());
- controller.play();
-
- assertTrue(player.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
- assertTrue(player.mPlayCalled);
- }
-
- /**
- * Test if server recognizes a session even if the session refuses the connection from server.
- *
- * @throws InterruptedException
- */
- @Test
- public void testGetSessionTokens_sessionRejected() throws InterruptedException {
- sHandler.postAndSync(() -> {
- mSession.close();
- mSession = new MediaSession2.Builder(mContext).setPlayer(new MockPlayer(0))
- .setId(TAG).setSessionCallback(sHandlerExecutor, new SessionCallback(mContext) {
- @Override
- public MediaSession2.CommandGroup onConnect(
- MediaSession2 session, ControllerInfo controller) {
- // Reject all connection request.
- return null;
- }
- }).build();
- });
- ensureChangeInSession();
-
- boolean foundSession = false;
- List<SessionToken2> tokens = mManager.getActiveSessionTokens();
- assertNotNull(tokens);
- for (int i = 0; i < tokens.size(); i++) {
- SessionToken2 token = tokens.get(i);
- if (mContext.getPackageName().equals(token.getPackageName())
- && TAG.equals(token.getId())) {
- assertFalse(foundSession);
- foundSession = true;
- }
- }
- assertTrue(foundSession);
- }
-
- @Test
- public void testGetMediaSession2Tokens_playerRemoved() throws InterruptedException {
- // Release
- sHandler.postAndSync(() -> {
- mSession.close();
- });
- ensureChangeInSession();
-
- // When the mSession's player becomes null, it should lose binder connection between server.
- // So server will forget the session.
- List<SessionToken2> tokens = mManager.getActiveSessionTokens();
- for (int i = 0; i < tokens.size(); i++) {
- SessionToken2 token = tokens.get(i);
- assertFalse(mContext.getPackageName().equals(token.getPackageName())
- && TAG.equals(token.getId()));
- }
- }
-
- @Test
- public void testGetMediaSessionService2Token() throws InterruptedException {
- boolean foundTestSessionService = false;
- boolean foundTestLibraryService = false;
- List<SessionToken2> tokens = mManager.getSessionServiceTokens();
- for (int i = 0; i < tokens.size(); i++) {
- SessionToken2 token = tokens.get(i);
- if (mContext.getPackageName().equals(token.getPackageName())
- && MockMediaSessionService2.ID.equals(token.getId())) {
- assertFalse(foundTestSessionService);
- assertEquals(SessionToken2.TYPE_SESSION_SERVICE, token.getType());
- foundTestSessionService = true;
- } else if (mContext.getPackageName().equals(token.getPackageName())
- && MockMediaLibraryService2.ID.equals(token.getId())) {
- assertFalse(foundTestLibraryService);
- assertEquals(SessionToken2.TYPE_LIBRARY_SERVICE, token.getType());
- foundTestLibraryService = true;
- }
- }
- assertTrue(foundTestSessionService);
- assertTrue(foundTestLibraryService);
- }
-
- @Test
- public void testGetAllSessionTokens() throws InterruptedException {
- boolean foundTestSession = false;
- boolean foundTestSessionService = false;
- boolean foundTestLibraryService = false;
- List<SessionToken2> tokens = mManager.getAllSessionTokens();
- for (int i = 0; i < tokens.size(); i++) {
- SessionToken2 token = tokens.get(i);
- if (!mContext.getPackageName().equals(token.getPackageName())) {
- continue;
- }
- switch (token.getId()) {
- case TAG:
- assertFalse(foundTestSession);
- foundTestSession = true;
- break;
- case MockMediaSessionService2.ID:
- assertFalse(foundTestSessionService);
- foundTestSessionService = true;
- assertEquals(SessionToken2.TYPE_SESSION_SERVICE, token.getType());
- break;
- case MockMediaLibraryService2.ID:
- assertFalse(foundTestLibraryService);
- assertEquals(SessionToken2.TYPE_LIBRARY_SERVICE, token.getType());
- foundTestLibraryService = true;
- break;
- default:
- fail("Unexpected session " + token + " exists in the package");
- }
- }
- assertTrue(foundTestSession);
- assertTrue(foundTestSessionService);
- assertTrue(foundTestLibraryService);
- }
-
- @Test
- public void testAddOnSessionTokensChangedListener() throws InterruptedException {
- TokensChangedListener listener = new TokensChangedListener();
- mManager.addOnSessionTokensChangedListener(sHandlerExecutor, listener);
-
- listener.reset();
- MediaSession2 session1 = new MediaSession2.Builder(mContext)
- .setPlayer(new MockPlayer(0))
- .setId(UUID.randomUUID().toString())
- .build();
- assertTrue(listener.await());
- assertTrue(listener.findToken(session1.getToken()));
-
- listener.reset();
- session1.close();
- assertTrue(listener.await());
- assertFalse(listener.findToken(session1.getToken()));
-
- listener.reset();
- MediaSession2 session2 = new MediaSession2.Builder(mContext)
- .setPlayer(new MockPlayer(0))
- .setId(UUID.randomUUID().toString())
- .build();
- assertTrue(listener.await());
- assertFalse(listener.findToken(session1.getToken()));
- assertTrue(listener.findToken(session2.getToken()));
-
- listener.reset();
- MediaSession2 session3 = new MediaSession2.Builder(mContext)
- .setPlayer(new MockPlayer(0))
- .setId(UUID.randomUUID().toString())
- .build();
- assertTrue(listener.await());
- assertFalse(listener.findToken(session1.getToken()));
- assertTrue(listener.findToken(session2.getToken()));
- assertTrue(listener.findToken(session3.getToken()));
-
- listener.reset();
- session2.close();
- assertTrue(listener.await());
- assertFalse(listener.findToken(session1.getToken()));
- assertFalse(listener.findToken(session2.getToken()));
- assertTrue(listener.findToken(session3.getToken()));
-
- listener.reset();
- session3.close();
- assertTrue(listener.await());
- assertFalse(listener.findToken(session1.getToken()));
- assertFalse(listener.findToken(session2.getToken()));
- assertFalse(listener.findToken(session3.getToken()));
-
- mManager.removeOnSessionTokensChangedListener(listener);
- }
-
- @Test
- public void testRemoveOnSessionTokensChangedListener() throws InterruptedException {
- TokensChangedListener listener = new TokensChangedListener();
- mManager.addOnSessionTokensChangedListener(sHandlerExecutor, listener);
-
- listener.reset();
- MediaSession2 session1 = new MediaSession2.Builder(mContext)
- .setPlayer(new MockPlayer(0))
- .setId(UUID.randomUUID().toString())
- .build();
- assertTrue(listener.await());
-
- mManager.removeOnSessionTokensChangedListener(listener);
-
- listener.reset();
- session1.close();
- assertFalse(listener.await());
-
- listener.reset();
- MediaSession2 session2 = new MediaSession2.Builder(mContext)
- .setPlayer(new MockPlayer(0))
- .setId(UUID.randomUUID().toString())
- .build();
- assertFalse(listener.await());
-
- listener.reset();
- MediaSession2 session3 = new MediaSession2.Builder(mContext)
- .setPlayer(new MockPlayer(0))
- .setId(UUID.randomUUID().toString())
- .build();
- assertFalse(listener.await());
-
- listener.reset();
- session2.close();
- assertFalse(listener.await());
-
- listener.reset();
- session3.close();
- assertFalse(listener.await());
- }
-
- // Ensures if the session creation/release is notified to the server.
- private void ensureChangeInSession() throws InterruptedException {
- // TODO(jaewan): Wait by listener.
- Thread.sleep(WAIT_TIME_MS);
- }
-
- private class TokensChangedListener implements OnSessionTokensChangedListener {
- private CountDownLatch mLatch;
- private List<SessionToken2> mTokens;
-
- private void reset() {
- mLatch = new CountDownLatch(1);
- mTokens = null;
- }
-
- private boolean await() throws InterruptedException {
- return mLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
- }
-
- private boolean findToken(SessionToken2 token) {
- return mTokens.contains(token);
- }
-
- @Override
- public void onSessionTokensChanged(List<SessionToken2> tokens) {
- mTokens = tokens;
- mLatch.countDown();
- }
- }
-}
diff --git a/packages/MediaComponents/test/src/android/media/MockMediaLibraryService2.java b/packages/MediaComponents/test/src/android/media/MockMediaLibraryService2.java
deleted file mode 100644
index fb02f7a..0000000
--- a/packages/MediaComponents/test/src/android/media/MockMediaLibraryService2.java
+++ /dev/null
@@ -1,258 +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 android.media;
-
-import static junit.framework.Assert.fail;
-
-import static org.junit.Assert.assertEquals;
-
-import android.content.Context;
-import android.media.MediaSession2.CommandGroup;
-import android.media.MediaSession2.ControllerInfo;
-import android.media.MediaLibraryService2.MediaLibrarySession.MediaLibrarySessionCallback;
-import android.media.TestServiceRegistry.SessionCallbackProxy;
-import android.media.TestUtils.SyncHandler;
-import android.os.Bundle;
-import android.util.Log;
-
-import java.io.FileDescriptor;
-import java.util.ArrayList;
-import java.util.List;
-
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-
-import javax.annotation.concurrent.GuardedBy;
-
-/**
- * Mock implementation of {@link MediaLibraryService2} for testing.
- */
-public class MockMediaLibraryService2 extends MediaLibraryService2 {
- // Keep in sync with the AndroidManifest.xml
- public static final String ID = "TestLibrary";
-
- public static final String ROOT_ID = "rootId";
- public static final Bundle EXTRAS = new Bundle();
-
- public static final String MEDIA_ID_GET_ITEM = "media_id_get_item";
-
- public static final String PARENT_ID = "parent_id";
- public static final String PARENT_ID_NO_CHILDREN = "parent_id_no_children";
- public static final String PARENT_ID_ERROR = "parent_id_error";
-
- public static final List<MediaItem2> GET_CHILDREN_RESULT = new ArrayList<>();
- public static final int CHILDREN_COUNT = 100;
-
- public static final String SEARCH_QUERY = "search_query";
- public static final String SEARCH_QUERY_TAKES_TIME = "search_query_takes_time";
- public static final int SEARCH_TIME_IN_MS = 5000;
- public static final String SEARCH_QUERY_EMPTY_RESULT = "search_query_empty_result";
-
- public static final List<MediaItem2> SEARCH_RESULT = new ArrayList<>();
- public static final int SEARCH_RESULT_COUNT = 50;
-
- private static final DataSourceDesc DATA_SOURCE_DESC =
- new DataSourceDesc.Builder().setDataSource(new FileDescriptor()).build();
-
- private static final String TAG = "MockMediaLibrarySvc2";
-
- static {
- EXTRAS.putString(ROOT_ID, ROOT_ID);
- }
- @GuardedBy("MockMediaLibraryService2.class")
- private static SessionToken2 sToken;
-
- private MediaLibrarySession mSession;
-
- public MockMediaLibraryService2() {
- super();
- GET_CHILDREN_RESULT.clear();
- String getChildrenMediaIdPrefix = "get_children_media_id_";
- for (int i = 0; i < CHILDREN_COUNT; i++) {
- GET_CHILDREN_RESULT.add(createMediaItem(getChildrenMediaIdPrefix + i));
- }
-
- SEARCH_RESULT.clear();
- String getSearchResultMediaIdPrefix = "get_search_result_media_id_";
- for (int i = 0; i < SEARCH_RESULT_COUNT; i++) {
- SEARCH_RESULT.add(createMediaItem(getSearchResultMediaIdPrefix + i));
- }
- }
-
- @Override
- public void onCreate() {
- super.onCreate();
- TestServiceRegistry.getInstance().setServiceInstance(this);
- }
-
- @Override
- public MediaLibrarySession onCreateSession(String sessionId) {
- final MockPlayer player = new MockPlayer(1);
- final SyncHandler handler = (SyncHandler) TestServiceRegistry.getInstance().getHandler();
- final Executor executor = (runnable) -> handler.post(runnable);
- SessionCallbackProxy sessionCallbackProxy = TestServiceRegistry.getInstance()
- .getSessionCallbackProxy();
- if (sessionCallbackProxy == null) {
- // Ensures non-null
- sessionCallbackProxy = new SessionCallbackProxy(this) {};
- }
- TestLibrarySessionCallback callback =
- new TestLibrarySessionCallback(sessionCallbackProxy);
- mSession = new MediaLibrarySession.Builder(MockMediaLibraryService2.this, executor,
- callback).setPlayer(player).setId(sessionId).build();
- return mSession;
- }
-
- @Override
- public void onDestroy() {
- TestServiceRegistry.getInstance().cleanUp();
- super.onDestroy();
- }
-
- public static SessionToken2 getToken(Context context) {
- synchronized (MockMediaLibraryService2.class) {
- if (sToken == null) {
- sToken = new SessionToken2(context, context.getPackageName(),
- MockMediaLibraryService2.class.getName());
- assertEquals(SessionToken2.TYPE_LIBRARY_SERVICE, sToken.getType());
- }
- return sToken;
- }
- }
-
- private class TestLibrarySessionCallback extends MediaLibrarySessionCallback {
- private final SessionCallbackProxy mCallbackProxy;
-
- public TestLibrarySessionCallback(SessionCallbackProxy callbackProxy) {
- super(MockMediaLibraryService2.this);
- mCallbackProxy = callbackProxy;
- }
-
- @Override
- public CommandGroup onConnect(MediaSession2 session,
- ControllerInfo controller) {
- return mCallbackProxy.onConnect(controller);
- }
-
- @Override
- public LibraryRoot onGetLibraryRoot(MediaLibrarySession session, ControllerInfo controller,
- Bundle rootHints) {
- return new LibraryRoot(MockMediaLibraryService2.this, ROOT_ID, EXTRAS);
- }
-
- @Override
- public MediaItem2 onGetItem(MediaLibrarySession session, ControllerInfo controller,
- String mediaId) {
- if (MEDIA_ID_GET_ITEM.equals(mediaId)) {
- return createMediaItem(mediaId);
- } else {
- return null;
- }
- }
-
- @Override
- public List<MediaItem2> onGetChildren(MediaLibrarySession session,
- ControllerInfo controller, String parentId, int page, int pageSize, Bundle extras) {
- if (PARENT_ID.equals(parentId)) {
- return getPaginatedResult(GET_CHILDREN_RESULT, page, pageSize);
- } else if (PARENT_ID_ERROR.equals(parentId)) {
- return null;
- }
- // Includes the case of PARENT_ID_NO_CHILDREN.
- return new ArrayList<>();
- }
-
- @Override
- public void onSearch(MediaLibrarySession session, ControllerInfo controllerInfo,
- String query, Bundle extras) {
- if (SEARCH_QUERY.equals(query)) {
- mSession.notifySearchResultChanged(controllerInfo, query, SEARCH_RESULT_COUNT,
- extras);
- } else if (SEARCH_QUERY_TAKES_TIME.equals(query)) {
- // Searching takes some time. Notify after 5 seconds.
- Executors.newSingleThreadScheduledExecutor().schedule(new Runnable() {
- @Override
- public void run() {
- mSession.notifySearchResultChanged(
- controllerInfo, query, SEARCH_RESULT_COUNT, extras);
- }
- }, SEARCH_TIME_IN_MS, TimeUnit.MILLISECONDS);
- } else if (SEARCH_QUERY_EMPTY_RESULT.equals(query)) {
- mSession.notifySearchResultChanged(controllerInfo, query, 0, extras);
- } else {
- // TODO: For the error case, how should we notify the browser?
- }
- }
-
- @Override
- public List<MediaItem2> onGetSearchResult(MediaLibrarySession session,
- ControllerInfo controllerInfo, String query, int page, int pageSize,
- Bundle extras) {
- if (SEARCH_QUERY.equals(query)) {
- return getPaginatedResult(SEARCH_RESULT, page, pageSize);
- } else {
- return null;
- }
- }
-
- @Override
- public void onSubscribe(MediaLibrarySession session, ControllerInfo controller,
- String parentId, Bundle extras) {
- mCallbackProxy.onSubscribe(controller, parentId, extras);
- }
-
- @Override
- public void onUnsubscribe(MediaLibrarySession session, ControllerInfo controller,
- String parentId) {
- mCallbackProxy.onUnsubscribe(controller, parentId);
- }
- }
-
- private List<MediaItem2> getPaginatedResult(List<MediaItem2> items, int page, int pageSize) {
- if (items == null) {
- return null;
- } else if (items.size() == 0) {
- return new ArrayList<>();
- }
-
- final int totalItemCount = items.size();
- int fromIndex = (page - 1) * pageSize;
- int toIndex = Math.min(page * pageSize, totalItemCount);
-
- List<MediaItem2> paginatedResult = new ArrayList<>();
- try {
- // The case of (fromIndex >= totalItemCount) will throw exception below.
- paginatedResult = items.subList(fromIndex, toIndex);
- } catch (IndexOutOfBoundsException | IllegalArgumentException ex) {
- Log.d(TAG, "Result is empty for given pagination arguments: totalItemCount="
- + totalItemCount + ", page=" + page + ", pageSize=" + pageSize, ex);
- }
- return paginatedResult;
- }
-
- private MediaItem2 createMediaItem(String mediaId) {
- Context context = MockMediaLibraryService2.this;
- return new MediaItem2.Builder(context, 0 /* Flags */)
- .setMediaId(mediaId)
- .setDataSourceDesc(DATA_SOURCE_DESC)
- .setMetadata(new MediaMetadata2.Builder(context)
- .putString(MediaMetadata2.METADATA_KEY_MEDIA_ID, mediaId)
- .build())
- .build();
- }
-}
diff --git a/packages/MediaComponents/test/src/android/media/MockMediaSessionService2.java b/packages/MediaComponents/test/src/android/media/MockMediaSessionService2.java
deleted file mode 100644
index ce7ce8b..0000000
--- a/packages/MediaComponents/test/src/android/media/MockMediaSessionService2.java
+++ /dev/null
@@ -1,111 +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 android.media;
-
-import static junit.framework.Assert.fail;
-
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.content.Context;
-import android.media.MediaSession2.CommandGroup;
-import android.media.MediaSession2.ControllerInfo;
-import android.media.MediaSession2.SessionCallback;
-import android.media.TestServiceRegistry.SessionCallbackProxy;
-import android.media.TestUtils.SyncHandler;
-
-import java.util.concurrent.Executor;
-
-/**
- * Mock implementation of {@link android.media.MediaSessionService2} for testing.
- */
-public class MockMediaSessionService2 extends MediaSessionService2 {
- // Keep in sync with the AndroidManifest.xml
- public static final String ID = "TestSession";
-
- private static final String DEFAULT_MEDIA_NOTIFICATION_CHANNEL_ID = "media_session_service";
- private static final int DEFAULT_MEDIA_NOTIFICATION_ID = 1001;
-
- private NotificationChannel mDefaultNotificationChannel;
- private MediaSession2 mSession;
- private NotificationManager mNotificationManager;
-
- @Override
- public void onCreate() {
- super.onCreate();
- TestServiceRegistry.getInstance().setServiceInstance(this);
- mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
- }
-
- @Override
- public MediaSession2 onCreateSession(String sessionId) {
- final MockPlayer player = new MockPlayer(1);
- final SyncHandler handler = (SyncHandler) TestServiceRegistry.getInstance().getHandler();
- final Executor executor = (runnable) -> handler.post(runnable);
- SessionCallbackProxy sessionCallbackProxy = TestServiceRegistry.getInstance()
- .getSessionCallbackProxy();
- if (sessionCallbackProxy == null) {
- // Ensures non-null
- sessionCallbackProxy = new SessionCallbackProxy(this) {};
- }
- TestSessionServiceCallback callback =
- new TestSessionServiceCallback(sessionCallbackProxy);
- mSession = new MediaSession2.Builder(this)
- .setPlayer(player)
- .setSessionCallback(executor, callback)
- .setId(sessionId).build();
- return mSession;
- }
-
- @Override
- public void onDestroy() {
- TestServiceRegistry.getInstance().cleanUp();
- super.onDestroy();
- }
-
- @Override
- public MediaNotification onUpdateNotification() {
- if (mDefaultNotificationChannel == null) {
- mDefaultNotificationChannel = new NotificationChannel(
- DEFAULT_MEDIA_NOTIFICATION_CHANNEL_ID,
- DEFAULT_MEDIA_NOTIFICATION_CHANNEL_ID,
- NotificationManager.IMPORTANCE_DEFAULT);
- mNotificationManager.createNotificationChannel(mDefaultNotificationChannel);
- }
- Notification notification = new Notification.Builder(
- this, DEFAULT_MEDIA_NOTIFICATION_CHANNEL_ID)
- .setContentTitle(getPackageName())
- .setContentText("Dummt test notification")
- .setSmallIcon(android.R.drawable.sym_def_app_icon).build();
- return new MediaNotification(this, DEFAULT_MEDIA_NOTIFICATION_ID, notification);
- }
-
- private class TestSessionServiceCallback extends SessionCallback {
- private final SessionCallbackProxy mCallbackProxy;
-
- public TestSessionServiceCallback(SessionCallbackProxy callbackProxy) {
- super(MockMediaSessionService2.this);
- mCallbackProxy = callbackProxy;
- }
-
- @Override
- public CommandGroup onConnect(MediaSession2 session,
- ControllerInfo controller) {
- return mCallbackProxy.onConnect(controller);
- }
- }
-}
diff --git a/packages/MediaComponents/test/src/android/media/MockPlayer.java b/packages/MediaComponents/test/src/android/media/MockPlayer.java
deleted file mode 100644
index 05962cf..0000000
--- a/packages/MediaComponents/test/src/android/media/MockPlayer.java
+++ /dev/null
@@ -1,316 +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 android.media;
-
-import android.media.MediaSession2.PlaylistParams;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.util.ArrayMap;
-
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executor;
-
-/**
- * A mock implementation of {@link MediaPlayerBase} for testing.
- */
-public class MockPlayer extends MediaPlayerBase {
- public final CountDownLatch mCountDownLatch;
-
- public boolean mPlayCalled;
- public boolean mPauseCalled;
- public boolean mStopCalled;
- public boolean mSkipToPreviousCalled;
- public boolean mSkipToNextCalled;
- public boolean mPrepareCalled;
- public boolean mFastForwardCalled;
- public boolean mRewindCalled;
- public boolean mSeekToCalled;
- public long mSeekPosition;
- public boolean mSetCurrentPlaylistItemCalled;
- public MediaItem2 mCurrentItem;
- public boolean mSetPlaylistCalled;
- public boolean mSetPlaylistParamsCalled;
-
- public ArrayMap<PlayerEventCallback, Executor> mCallbacks = new ArrayMap<>();
- public List<MediaItem2> mPlaylist;
- public PlaylistParams mPlaylistParams;
-
- private PlaybackState2 mLastPlaybackState;
- private AudioAttributes mAudioAttributes;
-
- public MockPlayer(int count) {
- mCountDownLatch = (count > 0) ? new CountDownLatch(count) : null;
- }
-
- @Override
- public void close() {
- // no-op
- }
-
- @Override
- public void reset() {
- // no-op
- }
-
- @Override
- public void play() {
- mPlayCalled = true;
- if (mCountDownLatch != null) {
- mCountDownLatch.countDown();
- }
- }
-
- @Override
- public void pause() {
- mPauseCalled = true;
- if (mCountDownLatch != null) {
- mCountDownLatch.countDown();
- }
- }
-
- // TODO: Uncomment or remove
- /*
- @Override
- public void stop() {
- mStopCalled = true;
- if (mCountDownLatch != null) {
- mCountDownLatch.countDown();
- }
- }
- */
-
- // TODO: Uncomment or remove
- /*
- @Override
- public void skipToPrevious() {
- mSkipToPreviousCalled = true;
- if (mCountDownLatch != null) {
- mCountDownLatch.countDown();
- }
- }
- */
-
- @Override
- public void skipToNext() {
- mSkipToNextCalled = true;
- if (mCountDownLatch != null) {
- mCountDownLatch.countDown();
- }
- }
-
- @Override
- public void prepare() {
- mPrepareCalled = true;
- if (mCountDownLatch != null) {
- mCountDownLatch.countDown();
- }
- }
-
- // TODO: Uncomment or remove
- /*
- @Override
- public void fastForward() {
- mFastForwardCalled = true;
- if (mCountDownLatch != null) {
- mCountDownLatch.countDown();
- }
- }
- */
-
- // TODO: Uncomment or remove
- /*
- @Override
- public void rewind() {
- mRewindCalled = true;
- if (mCountDownLatch != null) {
- mCountDownLatch.countDown();
- }
- }
- */
-
- @Override
- public void seekTo(long pos) {
- mSeekToCalled = true;
- mSeekPosition = pos;
- if (mCountDownLatch != null) {
- mCountDownLatch.countDown();
- }
- }
-
- // TODO: Uncomment or remove
- /*
- @Override
- public void setCurrentPlaylistItem(MediaItem2 item) {
- mSetCurrentPlaylistItemCalled = true;
- mCurrentItem = item;
- if (mCountDownLatch != null) {
- mCountDownLatch.countDown();
- }
- }
- */
-
- // TODO: Uncomment or remove
- /*
- @Nullable
- @Override
- public PlaybackState2 getPlaybackState() {
- return mLastPlaybackState;
- }
- */
-
- @Override
- public int getPlayerState() {
- return mLastPlaybackState.getState();
- }
-
- @Override
- public int getBufferingState() {
- // TODO: implement this
- return -1;
- }
-
- @Override
- public void registerPlayerEventCallback(@NonNull Executor executor,
- @NonNull PlayerEventCallback callback) {
- mCallbacks.put(callback, executor);
- }
-
- @Override
- public void unregisterPlayerEventCallback(@NonNull PlayerEventCallback callback) {
- mCallbacks.remove(callback);
- }
-
- public void notifyPlaybackState(final PlaybackState2 state) {
- mLastPlaybackState = state;
- for (int i = 0; i < mCallbacks.size(); i++) {
- final PlayerEventCallback callback = mCallbacks.keyAt(i);
- final Executor executor = mCallbacks.valueAt(i);
- // TODO: Uncomment or remove
- //executor.execute(() -> callback.onPlaybackStateChanged(state));
- }
- }
-
- public void notifyError(int what) {
- for (int i = 0; i < mCallbacks.size(); i++) {
- final PlayerEventCallback callback = mCallbacks.keyAt(i);
- final Executor executor = mCallbacks.valueAt(i);
- // TODO: Uncomment or remove
- //executor.execute(() -> callback.onError(null, what, 0));
- }
- }
-
- // TODO: Uncomment or remove
- /*
- @Override
- public void setPlaylistParams(PlaylistParams params) {
- mSetPlaylistParamsCalled = true;
- mPlaylistParams = params;
- }
- */
-
- // TODO: Uncomment or remove
- /*
- @Override
- public void addPlaylistItem(int index, MediaItem2 item) {
- }
- */
-
- // TODO: Uncomment or remove
- /*
- @Override
- public void removePlaylistItem(MediaItem2 item) {
- }
- */
-
- // TODO: Uncomment or remove
- /*
- @Override
- public PlaylistParams getPlaylistParams() {
- return mPlaylistParams;
- }
- */
-
- @Override
- public void setAudioAttributes(AudioAttributes attributes) {
- mAudioAttributes = attributes;
- }
-
- @Override
- public AudioAttributes getAudioAttributes() {
- return mAudioAttributes;
- }
-
- // TODO: Uncomment or remove
- /*
- @Override
- public void setPlaylist(List<MediaItem2> playlist) {
- mSetPlaylistCalled = true;
- mPlaylist = playlist;
- }
- */
-
- // TODO: Uncomment or remove
- /*
- @Override
- public List<MediaItem2> getPlaylist() {
- return mPlaylist;
- }
- */
-
- @Override
- public void setDataSource(@NonNull DataSourceDesc dsd) {
- // TODO: Implement this
- }
-
- @Override
- public void setNextDataSource(@NonNull DataSourceDesc dsd) {
- // TODO: Implement this
- }
-
- @Override
- public void setNextDataSources(@NonNull List<DataSourceDesc> dsds) {
- // TODO: Implement this
- }
-
- @Override
- public DataSourceDesc getCurrentDataSource() {
- // TODO: Implement this
- return null;
- }
-
- @Override
- public void loopCurrent(boolean loop) {
- // TODO: implement this
- }
-
- @Override
- public void setPlaybackSpeed(float speed) {
- // TODO: implement this
- }
-
- @Override
- public void setPlayerVolume(float volume) {
- // TODO: implement this
- }
-
- @Override
- public float getPlayerVolume() {
- // TODO: implement this
- return -1;
- }
-}
diff --git a/packages/MediaComponents/test/src/android/media/SessionToken2Test.java b/packages/MediaComponents/test/src/android/media/SessionToken2Test.java
deleted file mode 100644
index efde78a..0000000
--- a/packages/MediaComponents/test/src/android/media/SessionToken2Test.java
+++ /dev/null
@@ -1,63 +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 android.media;
-
-import static junit.framework.Assert.assertEquals;
-
-import android.content.Context;
-import android.os.Process;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-import android.support.test.InstrumentationRegistry;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Tests {@link SessionToken2}.
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class SessionToken2Test {
- private Context mContext;
-
- @Before
- public void setUp() throws Exception {
- mContext = InstrumentationRegistry.getTargetContext();
- }
-
- @Test
- public void testConstructor_sessionService() {
- SessionToken2 token = new SessionToken2(mContext, mContext.getPackageName(),
- MockMediaSessionService2.class.getCanonicalName());
- assertEquals(MockMediaSessionService2.ID, token.getId());
- assertEquals(mContext.getPackageName(), token.getPackageName());
- assertEquals(Process.myUid(), token.getUid());
- assertEquals(SessionToken2.TYPE_SESSION_SERVICE, token.getType());
- }
-
- @Test
- public void testConstructor_libraryService() {
- SessionToken2 token = new SessionToken2(mContext, mContext.getPackageName(),
- MockMediaLibraryService2.class.getCanonicalName());
- assertEquals(MockMediaLibraryService2.ID, token.getId());
- assertEquals(mContext.getPackageName(), token.getPackageName());
- assertEquals(Process.myUid(), token.getUid());
- assertEquals(SessionToken2.TYPE_LIBRARY_SERVICE, token.getType());
- }
-}
\ No newline at end of file
diff --git a/packages/MediaComponents/test/src/android/media/TestServiceRegistry.java b/packages/MediaComponents/test/src/android/media/TestServiceRegistry.java
deleted file mode 100644
index 08e0cf0..0000000
--- a/packages/MediaComponents/test/src/android/media/TestServiceRegistry.java
+++ /dev/null
@@ -1,164 +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 android.media;
-
-import static org.junit.Assert.fail;
-
-import android.content.Context;
-import android.media.MediaSession2.CommandGroup;
-import android.media.MediaSession2.ControllerInfo;
-import android.media.TestUtils.SyncHandler;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Process;
-import android.support.annotation.GuardedBy;
-
-/**
- * Keeps the instance of currently running {@link MockMediaSessionService2}. And also provides
- * a way to control them in one place.
- * <p>
- * It only support only one service at a time.
- */
-public class TestServiceRegistry {
- /**
- * Proxy for both {@link MediaSession2.SessionCallback} and
- * {@link MediaLibraryService2.MediaLibrarySessionCallback}.
- */
- public static abstract class SessionCallbackProxy {
- private final Context mContext;
-
- /**
- * Constructor
- */
- public SessionCallbackProxy(Context context) {
- mContext = context;
- }
-
- public final Context getContext() {
- return mContext;
- }
-
- /**
- * @param controller
- * @return
- */
- public CommandGroup onConnect(ControllerInfo controller) {
- if (Process.myUid() == controller.getUid()) {
- CommandGroup commands = new CommandGroup(mContext);
- commands.addAllPredefinedCommands();
- return commands;
- }
- return null;
- }
-
- /**
- * Called when enclosing service is created.
- */
- public void onServiceCreated(MediaSessionService2 service) { }
-
- /**
- * Called when enclosing service is destroyed.
- */
- public void onServiceDestroyed() { }
-
- public void onSubscribe(ControllerInfo info, String parentId, Bundle extra) { }
- public void onUnsubscribe(ControllerInfo info, String parentId) { }
- }
-
- @GuardedBy("TestServiceRegistry.class")
- private static TestServiceRegistry sInstance;
- @GuardedBy("TestServiceRegistry.class")
- private MediaSessionService2 mService;
- @GuardedBy("TestServiceRegistry.class")
- private SyncHandler mHandler;
- @GuardedBy("TestServiceRegistry.class")
- private SessionCallbackProxy mCallbackProxy;
-
- public static TestServiceRegistry getInstance() {
- synchronized (TestServiceRegistry.class) {
- if (sInstance == null) {
- sInstance = new TestServiceRegistry();
- }
- return sInstance;
- }
- }
-
- public void setHandler(Handler handler) {
- synchronized (TestServiceRegistry.class) {
- mHandler = new SyncHandler(handler.getLooper());
- }
- }
-
- public Handler getHandler() {
- synchronized (TestServiceRegistry.class) {
- return mHandler;
- }
- }
-
- public void setSessionCallbackProxy(SessionCallbackProxy callbackProxy) {
- synchronized (TestServiceRegistry.class) {
- mCallbackProxy = callbackProxy;
- }
- }
-
- public SessionCallbackProxy getSessionCallbackProxy() {
- synchronized (TestServiceRegistry.class) {
- return mCallbackProxy;
- }
- }
-
- public void setServiceInstance(MediaSessionService2 service) {
- synchronized (TestServiceRegistry.class) {
- if (mService != null) {
- fail("Previous service instance is still running. Clean up manually to ensure"
- + " previoulsy running service doesn't break current test");
- }
- mService = service;
- if (mCallbackProxy != null) {
- mCallbackProxy.onServiceCreated(service);
- }
- }
- }
-
- public MediaSessionService2 getServiceInstance() {
- synchronized (TestServiceRegistry.class) {
- return mService;
- }
- }
-
- public void cleanUp() {
- synchronized (TestServiceRegistry.class) {
- final SessionCallbackProxy callbackProxy = mCallbackProxy;
- if (mService != null) {
- mService.getSession().close();
- // stopSelf() would not kill service while the binder connection established by
- // bindService() exists, and close() above will do the job instead.
- // So stopSelf() isn't really needed, but just for sure.
- mService.stopSelf();
- mService = null;
- }
- if (mHandler != null) {
- mHandler.removeCallbacksAndMessages(null);
- }
- mCallbackProxy = null;
-
- if (callbackProxy != null) {
- callbackProxy.onServiceDestroyed();
- }
- }
- }
-}
diff --git a/packages/MediaComponents/test/src/android/media/TestUtils.java b/packages/MediaComponents/test/src/android/media/TestUtils.java
deleted file mode 100644
index 12b24c0..0000000
--- a/packages/MediaComponents/test/src/android/media/TestUtils.java
+++ /dev/null
@@ -1,120 +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 android.media;
-
-import android.content.Context;
-import android.media.MediaSession2.PlaylistParams;
-import android.media.session.MediaSessionManager;
-import android.media.session.PlaybackState;
-import android.os.Bundle;
-import android.os.Handler;
-
-import android.os.Looper;
-
-import java.util.List;
-import java.util.Objects;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-/**
- * Utilities for tests.
- */
-public final class TestUtils {
- private static final int WAIT_TIME_MS = 1000;
- private static final int WAIT_SERVICE_TIME_MS = 5000;
-
- /**
- * Finds the session with id in this test package.
- *
- * @param context
- * @param id
- * @return
- */
- // TODO(jaewan): Currently not working.
- public static SessionToken2 getServiceToken(Context context, String id) {
- MediaSessionManager manager =
- (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);
- List<SessionToken2> tokens = manager.getSessionServiceTokens();
- for (int i = 0; i < tokens.size(); i++) {
- SessionToken2 token = tokens.get(i);
- if (context.getPackageName().equals(token.getPackageName())
- && id.equals(token.getId())) {
- return token;
- }
- }
- fail("Failed to find service");
- return null;
- }
-
- /**
- * Compares contents of two bundles.
- *
- * @param a a bundle
- * @param b another bundle
- * @return {@code true} if two bundles are the same. {@code false} otherwise. This may be
- * incorrect if any bundle contains a bundle.
- */
- public static boolean equals(Bundle a, Bundle b) {
- if (a == b) {
- return true;
- }
- if (a == null || b == null) {
- return false;
- }
- if (!a.keySet().containsAll(b.keySet())
- || !b.keySet().containsAll(a.keySet())) {
- return false;
- }
- for (String key : a.keySet()) {
- if (!Objects.equals(a.get(key), b.get(key))) {
- return false;
- }
- }
- return true;
- }
-
- public static void ensurePlaylistParamsModeEquals(PlaylistParams a, PlaylistParams b) {
- assertEquals(a.getRepeatMode(), b.getRepeatMode());
- assertEquals(a.getShuffleMode(), b.getShuffleMode());
- }
-
- /**
- * Handler that always waits until the Runnable finishes.
- */
- public static class SyncHandler extends Handler {
- public SyncHandler(Looper looper) {
- super(looper);
- }
-
- public void postAndSync(Runnable runnable) throws InterruptedException {
- final CountDownLatch latch = new CountDownLatch(1);
- if (getLooper() == Looper.myLooper()) {
- runnable.run();
- } else {
- post(()->{
- runnable.run();
- latch.countDown();
- });
- assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
- }
- }
- }
-}
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/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp
index 1301998..3134323 100644
--- a/services/audioflinger/Threads.cpp
+++ b/services/audioflinger/Threads.cpp
@@ -1837,6 +1837,10 @@
void AudioFlinger::PlaybackThread::preExit()
{
ALOGV(" preExit()");
+ // FIXME this is using hard-coded strings but in the future, this functionality will be
+ // converted to use audio HAL extensions required to support tunneling
+ status_t result = mOutput->stream->setParameters(String8("exiting=1"));
+ ALOGE_IF(result != OK, "Error when setting parameters on exit: %d", result);
}
// PlaybackThread::createTrack_l() must be called with AudioFlinger::mLock held
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/api1/Camera2Client.cpp b/services/camera/libcameraservice/api1/Camera2Client.cpp
index 3578bba..65faac9 100644
--- a/services/camera/libcameraservice/api1/Camera2Client.cpp
+++ b/services/camera/libcameraservice/api1/Camera2Client.cpp
@@ -779,7 +779,35 @@
int lastJpegStreamId = mJpegProcessor->getStreamId();
// If jpeg stream will slow down preview, make sure we remove it before starting preview
if (params.slowJpegMode) {
- mJpegProcessor->deleteStream();
+ // Pause preview if we are streaming
+ int32_t activeRequestId = mStreamingProcessor->getActiveRequestId();
+ if (activeRequestId != 0) {
+ res = mStreamingProcessor->togglePauseStream(/*pause*/true);
+ if (res != OK) {
+ ALOGE("%s: Camera %d: Can't pause streaming: %s (%d)",
+ __FUNCTION__, mCameraId, strerror(-res), res);
+ }
+ res = mDevice->waitUntilDrained();
+ if (res != OK) {
+ ALOGE("%s: Camera %d: Waiting to stop streaming failed: %s (%d)",
+ __FUNCTION__, mCameraId, strerror(-res), res);
+ }
+ }
+
+ res = mJpegProcessor->deleteStream();
+
+ if (res != OK) {
+ ALOGE("%s: Camera %d: delete Jpeg stream failed: %s (%d)",
+ __FUNCTION__, mCameraId, strerror(-res), res);
+ }
+
+ if (activeRequestId != 0) {
+ res = mStreamingProcessor->togglePauseStream(/*pause*/false);
+ if (res != OK) {
+ ALOGE("%s: Camera %d: Can't unpause streaming: %s (%d)",
+ __FUNCTION__, mCameraId, strerror(-res), res);
+ }
+ }
} else {
res = updateProcessorStream(mJpegProcessor, params);
if (res != OK) {
diff --git a/services/camera/libcameraservice/api1/client2/JpegProcessor.cpp b/services/camera/libcameraservice/api1/client2/JpegProcessor.cpp
index cc4249f..b7020fe 100755
--- a/services/camera/libcameraservice/api1/client2/JpegProcessor.cpp
+++ b/services/camera/libcameraservice/api1/client2/JpegProcessor.cpp
@@ -199,7 +199,11 @@
return INVALID_OPERATION;
}
- device->deleteStream(mCaptureStreamId);
+ status_t res = device->deleteStream(mCaptureStreamId);
+ if (res != OK) {
+ ALOGE("%s: delete stream %d failed!", __FUNCTION__, mCaptureStreamId);
+ return res;
+ }
mCaptureHeap.clear();
mCaptureWindow.clear();
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/camera/libcameraservice/device3/Camera3Device.cpp b/services/camera/libcameraservice/device3/Camera3Device.cpp
index 42bcb2d..67b5e06 100644
--- a/services/camera/libcameraservice/device3/Camera3Device.cpp
+++ b/services/camera/libcameraservice/device3/Camera3Device.cpp
@@ -1550,7 +1550,7 @@
// CameraDevice semantics require device to already be idle before
// deleteStream is called, unlike for createStream.
if (mStatus == STATUS_ACTIVE) {
- ALOGV("%s: Camera %s: Device not idle", __FUNCTION__, mId.string());
+ ALOGW("%s: Camera %s: Device not idle", __FUNCTION__, mId.string());
return -EBUSY;
}
diff --git a/services/camera/libcameraservice/device3/Camera3StreamSplitter.cpp b/services/camera/libcameraservice/device3/Camera3StreamSplitter.cpp
index bc12f39..e3bb5dc 100644
--- a/services/camera/libcameraservice/device3/Camera3StreamSplitter.cpp
+++ b/services/camera/libcameraservice/device3/Camera3StreamSplitter.cpp
@@ -364,7 +364,7 @@
// queue, no onBufferReleased is called by the buffer queue.
// Proactively trigger the callback to avoid buffer loss.
if (queueOutput.bufferReplaced) {
- onBufferReleasedByOutputLocked(output, surfaceId);
+ onBufferReplacedLocked(output, surfaceId);
}
return res;
@@ -582,7 +582,16 @@
void Camera3StreamSplitter::onBufferReleasedByOutput(
const sp<IGraphicBufferProducer>& from) {
ATRACE_CALL();
+ sp<Fence> fence;
+
+ int slot = BufferItem::INVALID_BUFFER_SLOT;
+ auto res = from->dequeueBuffer(&slot, &fence, mWidth, mHeight, mFormat, mProducerUsage,
+ nullptr, nullptr);
Mutex::Autolock lock(mMutex);
+ handleOutputDequeueStatusLocked(res, slot);
+ if (res != OK) {
+ return;
+ }
size_t surfaceId = 0;
bool found = false;
@@ -598,61 +607,47 @@
return;
}
- onBufferReleasedByOutputLocked(from, surfaceId);
+ returnOutputBufferLocked(fence, from, surfaceId, slot);
}
-void Camera3StreamSplitter::onBufferReleasedByOutputLocked(
+void Camera3StreamSplitter::onBufferReplacedLocked(
const sp<IGraphicBufferProducer>& from, size_t surfaceId) {
ATRACE_CALL();
- sp<GraphicBuffer> buffer;
sp<Fence> fence;
- if (mOutputSlots[from] == nullptr) {
- //Output surface got likely removed by client.
- return;
- }
- auto outputSlots = *mOutputSlots[from];
int slot = BufferItem::INVALID_BUFFER_SLOT;
auto res = from->dequeueBuffer(&slot, &fence, mWidth, mHeight, mFormat, mProducerUsage,
nullptr, nullptr);
- if (res == NO_INIT) {
- // If we just discovered that this output has been abandoned, note that,
- // but we can't do anything else, since buffer is invalid
- onAbandonedLocked();
- return;
- } else if (res == IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION) {
- SP_LOGE("%s: Producer needs to re-allocate buffer!", __FUNCTION__);
- SP_LOGE("%s: This should not happen with buffer allocation disabled!", __FUNCTION__);
- return;
- } else if (res == IGraphicBufferProducer::RELEASE_ALL_BUFFERS) {
- SP_LOGE("%s: All slot->buffer mapping should be released!", __FUNCTION__);
- SP_LOGE("%s: This should not happen with buffer allocation disabled!", __FUNCTION__);
- return;
- } else if (res == NO_MEMORY) {
- SP_LOGE("%s: No free buffers", __FUNCTION__);
- return;
- } else if (res == WOULD_BLOCK) {
- SP_LOGE("%s: Dequeue call will block", __FUNCTION__);
- return;
- } else if (res != OK || (slot == BufferItem::INVALID_BUFFER_SLOT)) {
- SP_LOGE("%s: dequeue buffer from output failed (%d)", __FUNCTION__, res);
+ handleOutputDequeueStatusLocked(res, slot);
+ if (res != OK) {
return;
}
- buffer = outputSlots[slot];
+ returnOutputBufferLocked(fence, from, surfaceId, slot);
+}
+
+void Camera3StreamSplitter::returnOutputBufferLocked(const sp<Fence>& fence,
+ const sp<IGraphicBufferProducer>& from, size_t surfaceId, int slot) {
+ sp<GraphicBuffer> buffer;
+
+ if (mOutputSlots[from] == nullptr) {
+ //Output surface got likely removed by client.
+ return;
+ }
+
+ auto outputSlots = *mOutputSlots[from];
+ buffer = outputSlots[slot];
BufferTracker& tracker = *(mBuffers[buffer->getId()]);
// Merge the release fence of the incoming buffer so that the fence we send
// back to the input includes all of the outputs' fences
if (fence != nullptr && fence->isValid()) {
tracker.mergeFence(fence);
}
- SP_LOGV("%s: dequeued buffer %" PRId64 " %p from output %p", __FUNCTION__,
- buffer->getId(), buffer.get(), from.get());
auto detachBuffer = mDetachedBuffers.find(buffer->getId());
bool detach = (detachBuffer != mDetachedBuffers.end());
if (detach) {
- res = from->detachBuffer(slot);
+ auto res = from->detachBuffer(slot);
if (res == NO_ERROR) {
outputSlots[slot] = nullptr;
} else {
@@ -664,6 +659,26 @@
decrementBufRefCountLocked(buffer->getId(), surfaceId);
}
+void Camera3StreamSplitter::handleOutputDequeueStatusLocked(status_t res, int slot) {
+ if (res == NO_INIT) {
+ // If we just discovered that this output has been abandoned, note that,
+ // but we can't do anything else, since buffer is invalid
+ onAbandonedLocked();
+ } else if (res == IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION) {
+ SP_LOGE("%s: Producer needs to re-allocate buffer!", __FUNCTION__);
+ SP_LOGE("%s: This should not happen with buffer allocation disabled!", __FUNCTION__);
+ } else if (res == IGraphicBufferProducer::RELEASE_ALL_BUFFERS) {
+ SP_LOGE("%s: All slot->buffer mapping should be released!", __FUNCTION__);
+ SP_LOGE("%s: This should not happen with buffer allocation disabled!", __FUNCTION__);
+ } else if (res == NO_MEMORY) {
+ SP_LOGE("%s: No free buffers", __FUNCTION__);
+ } else if (res == WOULD_BLOCK) {
+ SP_LOGE("%s: Dequeue call will block", __FUNCTION__);
+ } else if (res != OK || (slot == BufferItem::INVALID_BUFFER_SLOT)) {
+ SP_LOGE("%s: dequeue buffer from output failed (%d)", __FUNCTION__, res);
+ }
+}
+
void Camera3StreamSplitter::onAbandonedLocked() {
// If this is called from binderDied callback, it means the app process
// holding the binder has died. CameraService will be notified of the binder
diff --git a/services/camera/libcameraservice/device3/Camera3StreamSplitter.h b/services/camera/libcameraservice/device3/Camera3StreamSplitter.h
index 76a5b7d..fea1bdb 100644
--- a/services/camera/libcameraservice/device3/Camera3StreamSplitter.h
+++ b/services/camera/libcameraservice/device3/Camera3StreamSplitter.h
@@ -122,10 +122,8 @@
// onFrameAvailable call to proceed.
void onBufferReleasedByOutput(const sp<IGraphicBufferProducer>& from);
- // This is the implementation of onBufferReleasedByOutput without the mutex locked.
- // It could either be called from onBufferReleasedByOutput or from
- // onFrameAvailable when a buffer in the async buffer queue is overwritten.
- void onBufferReleasedByOutputLocked(const sp<IGraphicBufferProducer>& from, size_t surfaceId);
+ // Called by outputBufferLocked when a buffer in the async buffer queue got replaced.
+ void onBufferReplacedLocked(const sp<IGraphicBufferProducer>& from, size_t surfaceId);
// When this is called, the splitter disconnects from (i.e., abandons) its
// input queue and signals any waiting onFrameAvailable calls to wake up.
@@ -138,6 +136,13 @@
// 0, return the buffer back to the input BufferQueue.
void decrementBufRefCountLocked(uint64_t id, size_t surfaceId);
+ // Check for and handle any output surface dequeue errors.
+ void handleOutputDequeueStatusLocked(status_t res, int slot);
+
+ // Handles released output surface buffers.
+ void returnOutputBufferLocked(const sp<Fence>& fence, const sp<IGraphicBufferProducer>& from,
+ size_t surfaceId, int slot);
+
// This is a thin wrapper class that lets us determine which BufferQueue
// the IProducerListener::onBufferReleased callback is associated with. We
// create one of these per output BufferQueue, and then pass the producer
diff --git a/services/codec2/Android.bp b/services/codec2/Android.bp
new file mode 100644
index 0000000..4cfca1d
--- /dev/null
+++ b/services/codec2/Android.bp
@@ -0,0 +1,101 @@
+cc_binary {
+ name: "vendor.google.media.c2@1.0-service",
+ defaults: ["hidl_defaults"],
+ soc_specific: true,
+ relative_install_path: "hw",
+ srcs: [
+ "vendor.cpp",
+ ],
+
+ init_rc: ["vendor.google.media.c2@1.0-service.rc"],
+
+ shared_libs: [
+ "vendor.google.media.c2@1.0",
+ "libavservices_minijail_vendor",
+ "libhidlbase",
+ "libhidltransport",
+ "libhwbinder",
+ "liblog",
+ "libstagefright_codec2_hidl@1.0",
+ "libstagefright_codec2_vndk",
+ "libutils",
+ ],
+
+ arch: {
+ arm: {
+ required: ["codec2.vendor.base.policy"],
+ },
+ x86: {
+ required: ["codec2.vendor.base.policy"],
+ },
+ },
+
+ compile_multilib: "32",
+}
+
+cc_binary {
+ name: "vendor.google.media.c2@1.0-service-system",
+ defaults: ["hidl_defaults"],
+ relative_install_path: "hw",
+ srcs: [
+ "system.cpp",
+ ],
+
+ init_rc: ["vendor.google.media.c2@1.0-service-system.rc"],
+
+ shared_libs: [
+ "vendor.google.media.c2@1.0",
+ "libavservices_minijail",
+ "libcutils",
+ "libhidlbase",
+ "libhidltransport",
+ "libhwbinder",
+ "liblog",
+ "libstagefright_codec2_hidl@1.0",
+ "libstagefright_codec2_vndk",
+ "libutils",
+ "libv4l2_c2componentstore",
+ ],
+
+ arch: {
+ arm: {
+ required: ["codec2.system.base.policy"],
+ },
+ x86: {
+ required: ["codec2.system.base.policy"],
+ },
+ },
+
+ required: [
+ "libstagefright_soft_c2avcdec",
+ "libstagefright_soft_c2avcenc",
+ "libstagefright_soft_c2aacdec",
+ "libstagefright_soft_c2aacenc",
+ "libstagefright_soft_c2amrnbdec",
+ "libstagefright_soft_c2amrnbenc",
+ "libstagefright_soft_c2amrwbdec",
+ "libstagefright_soft_c2amrwbenc",
+ "libstagefright_soft_c2hevcdec",
+ "libstagefright_soft_c2g711alawdec",
+ "libstagefright_soft_c2g711mlawdec",
+ "libstagefright_soft_c2mpeg2dec",
+ "libstagefright_soft_c2h263dec",
+ "libstagefright_soft_c2h263enc",
+ "libstagefright_soft_c2mpeg4dec",
+ "libstagefright_soft_c2mpeg4enc",
+ "libstagefright_soft_c2mp3dec",
+ "libstagefright_soft_c2vorbisdec",
+ "libstagefright_soft_c2opusdec",
+ "libstagefright_soft_c2vp8dec",
+ "libstagefright_soft_c2vp9dec",
+ "libstagefright_soft_c2vp8enc",
+ "libstagefright_soft_c2vp9enc",
+ "libstagefright_soft_c2rawdec",
+ "libstagefright_soft_c2flacdec",
+ "libstagefright_soft_c2flacenc",
+ "libstagefright_soft_c2gsmdec",
+ ],
+
+ compile_multilib: "32",
+}
+
diff --git a/services/codec2/Android.mk b/services/codec2/Android.mk
new file mode 100644
index 0000000..fa49875
--- /dev/null
+++ b/services/codec2/Android.mk
@@ -0,0 +1,43 @@
+LOCAL_PATH := $(call my-dir)
+
+# vendor service seccomp policy
+ifeq ($(TARGET_ARCH), $(filter $(TARGET_ARCH), x86 x86_64 arm arm64))
+include $(CLEAR_VARS)
+LOCAL_MODULE := codec2.vendor.base.policy
+LOCAL_VENDOR_MODULE := true
+LOCAL_MODULE_CLASS := ETC
+LOCAL_MODULE_PATH := $(TARGET_OUT_VENDOR)/etc/seccomp_policy
+LOCAL_REQUIRED_MODULES := crash_dump.policy
+ifdef TARGET_2ND_ARCH
+ ifneq ($(TARGET_TRANSLATE_2ND_ARCH),true)
+ LOCAL_SRC_FILES := seccomp_policy/codec2.vendor.base-$(TARGET_2ND_ARCH).policy
+ else
+ LOCAL_SRC_FILES := seccomp_policy/codec2.vendor.base-$(TARGET_ARCH).policy
+ endif
+else
+ LOCAL_SRC_FILES := seccomp_policy/codec2.vendor.base-$(TARGET_ARCH).policy
+endif
+include $(BUILD_PREBUILT)
+endif
+
+# system service seccomp policy
+ifeq ($(TARGET_ARCH), $(filter $(TARGET_ARCH), x86 x86_64 arm arm64))
+include $(CLEAR_VARS)
+LOCAL_MODULE := codec2.system.base.policy
+LOCAL_MODULE_CLASS := ETC
+LOCAL_MODULE_PATH := $(TARGET_OUT)/etc/seccomp_policy
+LOCAL_REQUIRED_MODULES := crash_dump.policy
+ifdef TARGET_2ND_ARCH
+ ifneq ($(TARGET_TRANSLATE_2ND_ARCH),true)
+ LOCAL_SRC_FILES := seccomp_policy/codec2.system.base-$(TARGET_2ND_ARCH).policy
+ else
+ LOCAL_SRC_FILES := seccomp_policy/codec2.system.base-$(TARGET_ARCH).policy
+ endif
+else
+ LOCAL_SRC_FILES := seccomp_policy/codec2.system.base-$(TARGET_ARCH).policy
+endif
+include $(BUILD_PREBUILT)
+endif
+
+include $(call all-makefiles-under, $(LOCAL_PATH))
+
diff --git a/services/codec2/seccomp_policy/codec2.system.base-arm.policy b/services/codec2/seccomp_policy/codec2.system.base-arm.policy
new file mode 100644
index 0000000..d5871d1
--- /dev/null
+++ b/services/codec2/seccomp_policy/codec2.system.base-arm.policy
@@ -0,0 +1,73 @@
+# 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.
+
+# Organized by frequency of systemcall - in descending order for
+# best performance.
+futex: 1
+ioctl: 1
+write: 1
+prctl: 1
+clock_gettime: 1
+getpriority: 1
+read: 1
+close: 1
+writev: 1
+dup: 1
+ppoll: 1
+mmap2: 1
+getrandom: 1
+
+# mremap: Ensure |flags| are (MREMAP_MAYMOVE | MREMAP_FIXED) TODO: Once minijail
+# parser support for '<' is in this needs to be modified to also prevent
+# |old_address| and |new_address| from touching the exception vector page, which
+# on ARM is statically loaded at 0xffff 0000. See
+# http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0211h/Babfeega.html
+# for more details.
+mremap: arg3 == 3
+munmap: 1
+mprotect: 1
+madvise: 1
+openat: 1
+sigaltstack: 1
+clone: 1
+setpriority: 1
+getuid32: 1
+fstat64: 1
+fstatfs64: 1
+pread64: 1
+faccessat: 1
+readlinkat: 1
+exit: 1
+rt_sigprocmask: 1
+set_tid_address: 1
+restart_syscall: 1
+exit_group: 1
+rt_sigreturn: 1
+pipe2: 1
+gettimeofday: 1
+sched_yield: 1
+nanosleep: 1
+lseek: 1
+_llseek: 1
+sched_get_priority_max: 1
+sched_get_priority_min: 1
+statfs64: 1
+sched_setscheduler: 1
+fstatat64: 1
+ugetrlimit: 1
+getdents64: 1
+getrandom: 1
+
+@include /system/etc/seccomp_policy/crash_dump.arm.policy
+
diff --git a/services/codec2/seccomp_policy/codec2.system.base-x86.policy b/services/codec2/seccomp_policy/codec2.system.base-x86.policy
new file mode 100644
index 0000000..20c7625
--- /dev/null
+++ b/services/codec2/seccomp_policy/codec2.system.base-x86.policy
@@ -0,0 +1,57 @@
+# 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.
+
+read: 1
+mprotect: 1
+prctl: 1
+openat: 1
+getuid32: 1
+writev: 1
+ioctl: 1
+close: 1
+mmap2: 1
+fstat64: 1
+madvise: 1
+fstatat64: 1
+futex: 1
+munmap: 1
+faccessat: 1
+_llseek: 1
+lseek: 1
+clone: 1
+sigaltstack: 1
+setpriority: 1
+restart_syscall: 1
+exit: 1
+exit_group: 1
+rt_sigreturn: 1
+ugetrlimit: 1
+readlinkat: 1
+_llseek: 1
+fstatfs64: 1
+pread64: 1
+mremap: 1
+dup: 1
+set_tid_address: 1
+write: 1
+nanosleep: 1
+
+# Required by AddressSanitizer
+gettid: 1
+sched_yield: 1
+getpid: 1
+gettid: 1
+
+@include /system/etc/seccomp_policy/crash_dump.x86.policy
+
diff --git a/services/codec2/seccomp_policy/codec2.vendor.base-arm.policy b/services/codec2/seccomp_policy/codec2.vendor.base-arm.policy
new file mode 100644
index 0000000..d5871d1
--- /dev/null
+++ b/services/codec2/seccomp_policy/codec2.vendor.base-arm.policy
@@ -0,0 +1,73 @@
+# 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.
+
+# Organized by frequency of systemcall - in descending order for
+# best performance.
+futex: 1
+ioctl: 1
+write: 1
+prctl: 1
+clock_gettime: 1
+getpriority: 1
+read: 1
+close: 1
+writev: 1
+dup: 1
+ppoll: 1
+mmap2: 1
+getrandom: 1
+
+# mremap: Ensure |flags| are (MREMAP_MAYMOVE | MREMAP_FIXED) TODO: Once minijail
+# parser support for '<' is in this needs to be modified to also prevent
+# |old_address| and |new_address| from touching the exception vector page, which
+# on ARM is statically loaded at 0xffff 0000. See
+# http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0211h/Babfeega.html
+# for more details.
+mremap: arg3 == 3
+munmap: 1
+mprotect: 1
+madvise: 1
+openat: 1
+sigaltstack: 1
+clone: 1
+setpriority: 1
+getuid32: 1
+fstat64: 1
+fstatfs64: 1
+pread64: 1
+faccessat: 1
+readlinkat: 1
+exit: 1
+rt_sigprocmask: 1
+set_tid_address: 1
+restart_syscall: 1
+exit_group: 1
+rt_sigreturn: 1
+pipe2: 1
+gettimeofday: 1
+sched_yield: 1
+nanosleep: 1
+lseek: 1
+_llseek: 1
+sched_get_priority_max: 1
+sched_get_priority_min: 1
+statfs64: 1
+sched_setscheduler: 1
+fstatat64: 1
+ugetrlimit: 1
+getdents64: 1
+getrandom: 1
+
+@include /system/etc/seccomp_policy/crash_dump.arm.policy
+
diff --git a/services/codec2/seccomp_policy/codec2.vendor.base-x86.policy b/services/codec2/seccomp_policy/codec2.vendor.base-x86.policy
new file mode 100644
index 0000000..20c7625
--- /dev/null
+++ b/services/codec2/seccomp_policy/codec2.vendor.base-x86.policy
@@ -0,0 +1,57 @@
+# 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.
+
+read: 1
+mprotect: 1
+prctl: 1
+openat: 1
+getuid32: 1
+writev: 1
+ioctl: 1
+close: 1
+mmap2: 1
+fstat64: 1
+madvise: 1
+fstatat64: 1
+futex: 1
+munmap: 1
+faccessat: 1
+_llseek: 1
+lseek: 1
+clone: 1
+sigaltstack: 1
+setpriority: 1
+restart_syscall: 1
+exit: 1
+exit_group: 1
+rt_sigreturn: 1
+ugetrlimit: 1
+readlinkat: 1
+_llseek: 1
+fstatfs64: 1
+pread64: 1
+mremap: 1
+dup: 1
+set_tid_address: 1
+write: 1
+nanosleep: 1
+
+# Required by AddressSanitizer
+gettid: 1
+sched_yield: 1
+getpid: 1
+gettid: 1
+
+@include /system/etc/seccomp_policy/crash_dump.x86.policy
+
diff --git a/services/codec2/system.cpp b/services/codec2/system.cpp
new file mode 100644
index 0000000..d6ec644
--- /dev/null
+++ b/services/codec2/system.cpp
@@ -0,0 +1,90 @@
+/*
+ * 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 "vendor.google.media.c2@1.0-service"
+
+#include <C2PlatformSupport.h>
+#include <C2V4l2Support.h>
+#include <cutils/properties.h>
+
+#include <codec2/hidl/1.0/ComponentStore.h>
+#include <hidl/HidlTransportSupport.h>
+#include <minijail.h>
+
+// TODO: Remove this once "setenv()" call is removed.
+#include <stdlib.h>
+
+// This is created by module "codec2.system.base.policy". This can be modified.
+static constexpr char kBaseSeccompPolicyPath[] =
+ "/system/etc/seccomp_policy/codec2.system.base.policy";
+
+// Additional device-specific seccomp permissions can be added in this file.
+static constexpr char kExtSeccompPolicyPath[] =
+ "/system/etc/seccomp_policy/codec2.system.ext.policy";
+
+int main(int /* argc */, char** /* argv */) {
+ ALOGD("vendor.google.media.c2@1.0-service-system starting...");
+
+ // TODO: Remove this when all the build settings and sepolicies are in place.
+ setenv("TREBLE_TESTING_OVERRIDE", "true", true);
+
+ signal(SIGPIPE, SIG_IGN);
+ android::SetUpMinijail(kBaseSeccompPolicyPath, kExtSeccompPolicyPath);
+
+ // Extra threads may be needed to handle a stacked IPC sequence that
+ // contains alternating binder and hwbinder calls. (See b/35283480.)
+ android::hardware::configureRpcThreadpool(8, true /* callerWillJoin */);
+
+ // Create IComponentStore service.
+ {
+ using namespace ::vendor::google::media::c2::V1_0;
+ android::sp<IComponentStore> store =
+ new implementation::ComponentStore(
+ android::GetCodec2PlatformComponentStore());
+ if (store == nullptr) {
+ ALOGE("Cannot create Codec2's IComponentStore system service.");
+ } else {
+ if (store->registerAsService("system") != android::OK) {
+ ALOGE("Cannot register Codec2's "
+ "IComponentStore system service.");
+ } else {
+ ALOGI("Codec2's IComponentStore system service created.");
+ }
+ }
+
+ // To enable the v4l2 service, set this sysprop and add "v4l2" instance
+ // to the system manifest file.
+ if (property_get_bool("debug.stagefright.ccodec_v4l2", false)) {
+ store = new implementation::ComponentStore(
+ android::GetCodec2VDAComponentStore());
+ if (store == nullptr) {
+ ALOGE("Cannot create Codec2's IComponentStore V4L2 service.");
+ } else {
+ if (store->registerAsService("v4l2") != android::OK) {
+ ALOGE("Cannot register Codec2's "
+ "IComponentStore V4L2 service.");
+ } else {
+ ALOGI("Codec2's IComponentStore V4L2 service created.");
+ }
+ }
+ }
+ }
+
+ android::hardware::joinRpcThreadpool();
+ return 0;
+}
+
diff --git a/services/codec2/vendor.cpp b/services/codec2/vendor.cpp
new file mode 100644
index 0000000..60b51e2
--- /dev/null
+++ b/services/codec2/vendor.cpp
@@ -0,0 +1,135 @@
+/*
+ * 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 "vendor.google.media.c2@1.0-service"
+
+#include <codec2/hidl/1.0/ComponentStore.h>
+#include <hidl/HidlTransportSupport.h>
+#include <minijail.h>
+
+#include <C2Component.h>
+
+// TODO: Remove this once "setenv()" call is removed.
+#include <stdlib.h>
+
+// This is created by module "codec2.vendor.base.policy". This can be modified.
+static constexpr char kBaseSeccompPolicyPath[] =
+ "/vendor/etc/seccomp_policy/codec2.vendor.base.policy";
+
+// Additional device-specific seccomp permissions can be added in this file.
+static constexpr char kExtSeccompPolicyPath[] =
+ "/vendor/etc/seccomp_policy/codec2.vendor.ext.policy";
+
+// TODO: Replace with a valid C2ComponentStore implementation.
+class DummyC2Store : public C2ComponentStore {
+public:
+ DummyC2Store() = default;
+
+ virtual ~DummyC2Store() override = default;
+
+ virtual C2String getName() const override {
+ return "default";
+ }
+
+ virtual c2_status_t createComponent(
+ C2String /*name*/,
+ std::shared_ptr<C2Component>* const /*component*/) override {
+ return C2_NOT_FOUND;
+ }
+
+ virtual c2_status_t createInterface(
+ C2String /* name */,
+ std::shared_ptr<C2ComponentInterface>* const /* interface */) override {
+ return C2_NOT_FOUND;
+ }
+
+ virtual std::vector<std::shared_ptr<const C2Component::Traits>>
+ listComponents() override {
+ return {};
+ }
+
+ virtual c2_status_t copyBuffer(
+ std::shared_ptr<C2GraphicBuffer> /* src */,
+ std::shared_ptr<C2GraphicBuffer> /* dst */) override {
+ return C2_OMITTED;
+ }
+
+ virtual c2_status_t query_sm(
+ const std::vector<C2Param*>& /* stackParams */,
+ const std::vector<C2Param::Index>& /* heapParamIndices */,
+ std::vector<std::unique_ptr<C2Param>>* const /* heapParams */) const override {
+ return C2_OMITTED;
+ }
+
+ virtual c2_status_t config_sm(
+ const std::vector<C2Param*>& /* params */,
+ std::vector<std::unique_ptr<C2SettingResult>>* const /* failures */) override {
+ return C2_OMITTED;
+ }
+
+ virtual std::shared_ptr<C2ParamReflector> getParamReflector() const override {
+ return nullptr;
+ }
+
+ virtual c2_status_t querySupportedParams_nb(
+ std::vector<std::shared_ptr<C2ParamDescriptor>>* const /* params */) const override {
+ return C2_OMITTED;
+ }
+
+ virtual c2_status_t querySupportedValues_sm(
+ std::vector<C2FieldSupportedValuesQuery>& /* fields */) const override {
+ return C2_OMITTED;
+ }
+};
+
+int main(int /* argc */, char** /* argv */) {
+ ALOGD("vendor.google.media.c2@1.0-service starting...");
+
+ // TODO: Remove this when all the build settings and sepolicies are in place.
+ setenv("TREBLE_TESTING_OVERRIDE", "true", true);
+
+ signal(SIGPIPE, SIG_IGN);
+ android::SetUpMinijail(kBaseSeccompPolicyPath, kExtSeccompPolicyPath);
+
+ // Extra threads may be needed to handle a stacked IPC sequence that
+ // contains alternating binder and hwbinder calls. (See b/35283480.)
+ android::hardware::configureRpcThreadpool(8, true /* callerWillJoin */);
+
+ // Create IComponentStore service.
+ {
+ using namespace ::vendor::google::media::c2::V1_0;
+ android::sp<IComponentStore> store =
+ new implementation::ComponentStore(
+ // TODO: Replace this with a valid C2ComponentStore
+ // implementation.
+ std::make_shared<DummyC2Store>());
+ if (store == nullptr) {
+ ALOGE("Cannot create Codec2's IComponentStore service.");
+ } else {
+ if (store->registerAsService("default") != android::OK) {
+ ALOGE("Cannot register Codec2's "
+ "IComponentStore service.");
+ } else {
+ ALOGI("Codec2's IComponentStore service created.");
+ }
+ }
+ }
+
+ android::hardware::joinRpcThreadpool();
+ return 0;
+}
+
diff --git a/services/codec2/vendor.google.media.c2@1.0-service-system.rc b/services/codec2/vendor.google.media.c2@1.0-service-system.rc
new file mode 100644
index 0000000..0577a1d
--- /dev/null
+++ b/services/codec2/vendor.google.media.c2@1.0-service-system.rc
@@ -0,0 +1,7 @@
+service vendor-google-media-c2-system-hal-1-0 /system/bin/hw/vendor.google.media.c2@1.0-service-system
+ class hal
+ user media
+ group mediadrm drmrpc
+ ioprio rt 4
+ writepid /dev/cpuset/foreground/tasks
+
diff --git a/services/codec2/vendor.google.media.c2@1.0-service.rc b/services/codec2/vendor.google.media.c2@1.0-service.rc
new file mode 100644
index 0000000..3e7e0a6
--- /dev/null
+++ b/services/codec2/vendor.google.media.c2@1.0-service.rc
@@ -0,0 +1,7 @@
+service vendor-google-media-c2-hal-1-0 /vendor/bin/hw/vendor.google.media.c2@1.0-service
+ class hal
+ user media
+ group mediadrm drmrpc
+ ioprio rt 4
+ writepid /dev/cpuset/foreground/tasks
+
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