Merge "Move MediaBufferXXX from foundation to libmediaextractor"
diff --git a/drm/libmediadrm/DrmHal.cpp b/drm/libmediadrm/DrmHal.cpp
index c4b197c..982b117 100644
--- a/drm/libmediadrm/DrmHal.cpp
+++ b/drm/libmediadrm/DrmHal.cpp
@@ -38,7 +38,6 @@
#include <media/stagefright/MediaErrors.h>
using drm::V1_0::KeyedVector;
-using drm::V1_0::KeyRequestType;
using drm::V1_0::KeyStatusType;
using drm::V1_0::KeyType;
using drm::V1_0::KeyValue;
@@ -567,23 +566,63 @@
status_t err = UNKNOWN_ERROR;
+ if (mPluginV1_1 != NULL) {
+ Return<void> hResult =
+ mPluginV1_1->getKeyRequest_1_1(
+ toHidlVec(sessionId), toHidlVec(initData),
+ toHidlString(mimeType), hKeyType, hOptionalParameters,
+ [&](Status status, const hidl_vec<uint8_t>& hRequest,
+ drm::V1_1::KeyRequestType hKeyRequestType,
+ const hidl_string& hDefaultUrl) {
+
+ if (status == Status::OK) {
+ request = toVector(hRequest);
+ defaultUrl = toString8(hDefaultUrl);
+
+ switch (hKeyRequestType) {
+ case drm::V1_1::KeyRequestType::INITIAL:
+ *keyRequestType = DrmPlugin::kKeyRequestType_Initial;
+ break;
+ case drm::V1_1::KeyRequestType::RENEWAL:
+ *keyRequestType = DrmPlugin::kKeyRequestType_Renewal;
+ break;
+ case drm::V1_1::KeyRequestType::RELEASE:
+ *keyRequestType = DrmPlugin::kKeyRequestType_Release;
+ break;
+ case drm::V1_1::KeyRequestType::NONE:
+ *keyRequestType = DrmPlugin::kKeyRequestType_None;
+ break;
+ case drm::V1_1::KeyRequestType::UPDATE:
+ *keyRequestType = DrmPlugin::kKeyRequestType_Update;
+ break;
+ default:
+ *keyRequestType = DrmPlugin::kKeyRequestType_Unknown;
+ break;
+ }
+ err = toStatusT(status);
+ }
+ });
+ return hResult.isOk() ? err : DEAD_OBJECT;
+ }
+
Return<void> hResult = mPlugin->getKeyRequest(toHidlVec(sessionId),
toHidlVec(initData), toHidlString(mimeType), hKeyType, hOptionalParameters,
[&](Status status, const hidl_vec<uint8_t>& hRequest,
- KeyRequestType hKeyRequestType, const hidl_string& hDefaultUrl) {
+ drm::V1_0::KeyRequestType hKeyRequestType,
+ const hidl_string& hDefaultUrl) {
if (status == Status::OK) {
request = toVector(hRequest);
defaultUrl = toString8(hDefaultUrl);
switch (hKeyRequestType) {
- case KeyRequestType::INITIAL:
+ case drm::V1_0::KeyRequestType::INITIAL:
*keyRequestType = DrmPlugin::kKeyRequestType_Initial;
break;
- case KeyRequestType::RENEWAL:
+ case drm::V1_0::KeyRequestType::RENEWAL:
*keyRequestType = DrmPlugin::kKeyRequestType_Renewal;
break;
- case KeyRequestType::RELEASE:
+ case drm::V1_0::KeyRequestType::RELEASE:
*keyRequestType = DrmPlugin::kKeyRequestType_Release;
break;
default:
diff --git a/media/libaaudio/src/binding/AAudioServiceMessage.h b/media/libaaudio/src/binding/AAudioServiceMessage.h
index 9779f24..3981454 100644
--- a/media/libaaudio/src/binding/AAudioServiceMessage.h
+++ b/media/libaaudio/src/binding/AAudioServiceMessage.h
@@ -36,7 +36,6 @@
AAUDIO_SERVICE_EVENT_PAUSED,
AAUDIO_SERVICE_EVENT_STOPPED,
AAUDIO_SERVICE_EVENT_FLUSHED,
- AAUDIO_SERVICE_EVENT_CLOSED,
AAUDIO_SERVICE_EVENT_DISCONNECTED,
AAUDIO_SERVICE_EVENT_VOLUME,
AAUDIO_SERVICE_EVENT_XRUN
diff --git a/media/libaaudio/src/client/AudioStreamInternal.cpp b/media/libaaudio/src/client/AudioStreamInternal.cpp
index 4980e97..b611160 100644
--- a/media/libaaudio/src/client/AudioStreamInternal.cpp
+++ b/media/libaaudio/src/client/AudioStreamInternal.cpp
@@ -340,8 +340,13 @@
}
}
-aaudio_result_t AudioStreamInternal::requestStopInternal()
+aaudio_result_t AudioStreamInternal::requestStop()
{
+ aaudio_result_t result = stopCallback();
+ if (result != AAUDIO_OK) {
+ return result;
+ }
+
if (mServiceStreamHandle == AAUDIO_HANDLE_INVALID) {
ALOGE("requestStopInternal() mServiceStreamHandle invalid = 0x%08X",
mServiceStreamHandle);
@@ -355,16 +360,6 @@
return mServiceInterface.stopStream(mServiceStreamHandle);
}
-aaudio_result_t AudioStreamInternal::requestStop()
-{
- aaudio_result_t result = stopCallback();
- if (result != AAUDIO_OK) {
- return result;
- }
- result = requestStopInternal();
- return result;
-}
-
aaudio_result_t AudioStreamInternal::registerThread() {
if (mServiceStreamHandle == AAUDIO_HANDLE_INVALID) {
ALOGE("registerThread() mServiceStreamHandle invalid");
@@ -483,10 +478,6 @@
onFlushFromServer();
}
break;
- case AAUDIO_SERVICE_EVENT_CLOSED:
- ALOGD("%s - got AAUDIO_SERVICE_EVENT_CLOSED", __func__);
- setState(AAUDIO_STREAM_STATE_CLOSED);
- break;
case AAUDIO_SERVICE_EVENT_DISCONNECTED:
// Prevent hardware from looping on old data and making buzzing sounds.
if (getDirection() == AAUDIO_DIRECTION_OUTPUT) {
diff --git a/media/libaaudio/src/client/AudioStreamInternal.h b/media/libaaudio/src/client/AudioStreamInternal.h
index 117756d..0f54f8c 100644
--- a/media/libaaudio/src/client/AudioStreamInternal.h
+++ b/media/libaaudio/src/client/AudioStreamInternal.h
@@ -121,8 +121,6 @@
aaudio_result_t processCommands();
- aaudio_result_t requestStopInternal();
-
aaudio_result_t stopCallback();
virtual void advanceClientToMatchServerPosition() = 0;
diff --git a/media/libaaudio/src/client/AudioStreamInternalPlay.cpp b/media/libaaudio/src/client/AudioStreamInternalPlay.cpp
index 5de6a11..5660c1b 100644
--- a/media/libaaudio/src/client/AudioStreamInternalPlay.cpp
+++ b/media/libaaudio/src/client/AudioStreamInternalPlay.cpp
@@ -38,9 +38,12 @@
AudioStreamInternalPlay::~AudioStreamInternalPlay() {}
-
-aaudio_result_t AudioStreamInternalPlay::requestPauseInternal()
+aaudio_result_t AudioStreamInternalPlay::requestPause()
{
+ aaudio_result_t result = stopCallback();
+ if (result != AAUDIO_OK) {
+ return result;
+ }
if (mServiceStreamHandle == AAUDIO_HANDLE_INVALID) {
ALOGE("AudioStreamInternal::requestPauseInternal() mServiceStreamHandle invalid = 0x%08X",
mServiceStreamHandle);
@@ -53,16 +56,6 @@
return mServiceInterface.pauseStream(mServiceStreamHandle);
}
-aaudio_result_t AudioStreamInternalPlay::requestPause()
-{
- aaudio_result_t result = stopCallback();
- if (result != AAUDIO_OK) {
- return result;
- }
- result = requestPauseInternal();
- return result;
-}
-
aaudio_result_t AudioStreamInternalPlay::requestFlush() {
if (mServiceStreamHandle == AAUDIO_HANDLE_INVALID) {
ALOGE("AudioStreamInternal::requestFlush() mServiceStreamHandle invalid = 0x%08X",
diff --git a/media/libaaudio/src/client/AudioStreamInternalPlay.h b/media/libaaudio/src/client/AudioStreamInternalPlay.h
index d5c1b1e..04e4a62 100644
--- a/media/libaaudio/src/client/AudioStreamInternalPlay.h
+++ b/media/libaaudio/src/client/AudioStreamInternalPlay.h
@@ -37,6 +37,16 @@
aaudio_result_t requestFlush() override;
+ bool isFlushSupported() const override {
+ // Only implement FLUSH for OUTPUT streams.
+ return true;
+ }
+
+ bool isPauseSupported() const override {
+ // Only implement PAUSE for OUTPUT streams.
+ return true;
+ }
+
aaudio_result_t write(const void *buffer,
int32_t numFrames,
int64_t timeoutNanoseconds) override;
@@ -52,8 +62,6 @@
protected:
- aaudio_result_t requestPauseInternal();
-
void advanceClientToMatchServerPosition() override;
void onFlushFromServer() override;
diff --git a/media/libaaudio/src/core/AAudioStreamParameters.cpp b/media/libaaudio/src/core/AAudioStreamParameters.cpp
index 9645ea8..d56701b 100644
--- a/media/libaaudio/src/core/AAudioStreamParameters.cpp
+++ b/media/libaaudio/src/core/AAudioStreamParameters.cpp
@@ -17,7 +17,7 @@
#define LOG_TAG "AAudioStreamParameters"
#include <utils/Log.h>
-#include <hardware/audio.h>
+#include <system/audio.h>
#include "AAudioStreamParameters.h"
diff --git a/media/libaaudio/src/core/AudioStream.cpp b/media/libaaudio/src/core/AudioStream.cpp
index 9a2405a..c4465fd 100644
--- a/media/libaaudio/src/core/AudioStream.cpp
+++ b/media/libaaudio/src/core/AudioStream.cpp
@@ -130,29 +130,106 @@
}
aaudio_result_t AudioStream::safePause() {
+ if (!isPauseSupported()) {
+ return AAUDIO_ERROR_UNIMPLEMENTED;
+ }
+
std::lock_guard<std::mutex> lock(mStreamLock);
if (collidesWithCallback()) {
ALOGE("%s cannot be called from a callback!", __func__);
return AAUDIO_ERROR_INVALID_STATE;
}
+
+ switch (getState()) {
+ // Proceed with pausing.
+ case AAUDIO_STREAM_STATE_STARTING:
+ case AAUDIO_STREAM_STATE_STARTED:
+ case AAUDIO_STREAM_STATE_DISCONNECTED:
+ break;
+
+ // Transition from one inactive state to another.
+ case AAUDIO_STREAM_STATE_OPEN:
+ case AAUDIO_STREAM_STATE_STOPPED:
+ case AAUDIO_STREAM_STATE_FLUSHED:
+ setState(AAUDIO_STREAM_STATE_PAUSED);
+ return AAUDIO_OK;
+
+ // Redundant?
+ case AAUDIO_STREAM_STATE_PAUSING:
+ case AAUDIO_STREAM_STATE_PAUSED:
+ return AAUDIO_OK;
+
+ // Don't interfere with transitional states or when closed.
+ case AAUDIO_STREAM_STATE_STOPPING:
+ case AAUDIO_STREAM_STATE_FLUSHING:
+ case AAUDIO_STREAM_STATE_CLOSING:
+ case AAUDIO_STREAM_STATE_CLOSED:
+ default:
+ ALOGW("safePause() stream not running, state = %s",
+ AAudio_convertStreamStateToText(getState()));
+ return AAUDIO_ERROR_INVALID_STATE;
+ }
+
return requestPause();
}
aaudio_result_t AudioStream::safeFlush() {
+ if (!isFlushSupported()) {
+ ALOGE("flush not supported for this stream");
+ return AAUDIO_ERROR_UNIMPLEMENTED;
+ }
+
std::lock_guard<std::mutex> lock(mStreamLock);
if (collidesWithCallback()) {
- ALOGE("%s cannot be called from a callback!", __func__);
+ ALOGE("stream cannot be flushed from a callback!");
return AAUDIO_ERROR_INVALID_STATE;
}
+
+ aaudio_result_t result = AAudio_isFlushAllowed(getState());
+ if (result != AAUDIO_OK) {
+ return result;
+ }
+
return requestFlush();
}
aaudio_result_t AudioStream::safeStop() {
std::lock_guard<std::mutex> lock(mStreamLock);
if (collidesWithCallback()) {
- ALOGE("%s cannot be called from a callback!", __func__);
+ ALOGE("stream cannot be stopped from a callback!");
return AAUDIO_ERROR_INVALID_STATE;
}
+
+ switch (getState()) {
+ // Proceed with stopping.
+ case AAUDIO_STREAM_STATE_STARTING:
+ case AAUDIO_STREAM_STATE_STARTED:
+ case AAUDIO_STREAM_STATE_DISCONNECTED:
+ break;
+
+ // Transition from one inactive state to another.
+ case AAUDIO_STREAM_STATE_OPEN:
+ case AAUDIO_STREAM_STATE_PAUSED:
+ case AAUDIO_STREAM_STATE_FLUSHED:
+ setState(AAUDIO_STREAM_STATE_STOPPED);
+ return AAUDIO_OK;
+
+ // Redundant?
+ case AAUDIO_STREAM_STATE_STOPPING:
+ case AAUDIO_STREAM_STATE_STOPPED:
+ return AAUDIO_OK;
+
+ // Don't interfere with transitional states or when closed.
+ case AAUDIO_STREAM_STATE_PAUSING:
+ case AAUDIO_STREAM_STATE_FLUSHING:
+ case AAUDIO_STREAM_STATE_CLOSING:
+ case AAUDIO_STREAM_STATE_CLOSED:
+ default:
+ ALOGW("requestStop() stream not running, state = %s",
+ AAudio_convertStreamStateToText(getState()));
+ return AAUDIO_ERROR_INVALID_STATE;
+ }
+
return requestStop();
}
@@ -238,6 +315,7 @@
if (err != 0) {
return AAudioConvert_androidToAAudioResult(-errno);
} else {
+ // TODO Use AAudioThread or maybe AndroidThread
// Name the thread with an increasing index, "AAudio_#", for debugging.
static std::atomic<uint32_t> nextThreadIndex{1};
char name[16]; // max length for a pthread_name
diff --git a/media/libaaudio/src/core/AudioStream.h b/media/libaaudio/src/core/AudioStream.h
index c59ee6c..c0db0f9 100644
--- a/media/libaaudio/src/core/AudioStream.h
+++ b/media/libaaudio/src/core/AudioStream.h
@@ -73,6 +73,24 @@
*/
virtual aaudio_result_t requestStart() = 0;
+ /**
+ * Check the state to see if Pause if currently legal.
+ *
+ * @param result pointer to return code
+ * @return true if OK to continue, if false then return result
+ */
+ bool checkPauseStateTransition(aaudio_result_t *result);
+
+ virtual bool isFlushSupported() const {
+ // Only implement FLUSH for OUTPUT streams.
+ return false;
+ }
+
+ virtual bool isPauseSupported() const {
+ // Only implement PAUSE for OUTPUT streams.
+ return false;
+ }
+
virtual aaudio_result_t requestPause()
{
// Only implement this for OUTPUT streams.
@@ -341,11 +359,13 @@
return mPlayerBase->getResult();
}
+ // Pass pause request through PlayerBase for tracking.
aaudio_result_t systemPause() {
mPlayerBase->pause();
return mPlayerBase->getResult();
}
+ // Pass stop request through PlayerBase for tracking.
aaudio_result_t systemStop() {
mPlayerBase->stop();
return mPlayerBase->getResult();
@@ -452,7 +472,14 @@
}
void setState(aaudio_stream_state_t state) {
- mState = state;
+ if (mState == AAUDIO_STREAM_STATE_CLOSED) {
+ ; // CLOSED is a final state
+ } else if (mState == AAUDIO_STREAM_STATE_DISCONNECTED
+ && state != AAUDIO_STREAM_STATE_CLOSED) {
+ ; // Once DISCONNECTED, we can only move to CLOSED state.
+ } else {
+ mState = state;
+ }
}
void setDeviceId(int32_t deviceId) {
diff --git a/media/libaaudio/src/legacy/AudioStreamTrack.cpp b/media/libaaudio/src/legacy/AudioStreamTrack.cpp
index 4d1b187..ee069ee 100644
--- a/media/libaaudio/src/legacy/AudioStreamTrack.cpp
+++ b/media/libaaudio/src/legacy/AudioStreamTrack.cpp
@@ -287,13 +287,8 @@
if (mAudioTrack.get() == nullptr) {
ALOGE("requestPause() no AudioTrack");
return AAUDIO_ERROR_INVALID_STATE;
- } else if (getState() != AAUDIO_STREAM_STATE_STARTING
- && getState() != AAUDIO_STREAM_STATE_STARTED) {
- // TODO What about DISCONNECTED?
- ALOGE("requestPause(), called when state is %s",
- AAudio_convertStreamStateToText(getState()));
- return AAUDIO_ERROR_INVALID_STATE;
}
+
setState(AAUDIO_STREAM_STATE_PAUSING);
mAudioTrack->pause();
mCallbackEnabled.store(false);
@@ -308,10 +303,8 @@
if (mAudioTrack.get() == nullptr) {
ALOGE("requestFlush() no AudioTrack");
return AAUDIO_ERROR_INVALID_STATE;
- } else if (getState() != AAUDIO_STREAM_STATE_PAUSED) {
- ALOGE("requestFlush() not paused");
- return AAUDIO_ERROR_INVALID_STATE;
}
+
setState(AAUDIO_STREAM_STATE_FLUSHING);
incrementFramesRead(getFramesWritten() - getFramesRead());
mAudioTrack->flush();
@@ -325,6 +318,7 @@
ALOGE("requestStop() no AudioTrack");
return AAUDIO_ERROR_INVALID_STATE;
}
+
setState(AAUDIO_STREAM_STATE_STOPPING);
incrementFramesRead(getFramesWritten() - getFramesRead()); // TODO review
mTimestampPosition.set(getFramesWritten());
diff --git a/media/libaaudio/src/legacy/AudioStreamTrack.h b/media/libaaudio/src/legacy/AudioStreamTrack.h
index a871db4..68608de 100644
--- a/media/libaaudio/src/legacy/AudioStreamTrack.h
+++ b/media/libaaudio/src/legacy/AudioStreamTrack.h
@@ -48,6 +48,16 @@
aaudio_result_t requestFlush() override;
aaudio_result_t requestStop() override;
+ bool isFlushSupported() const override {
+ // Only implement FLUSH for OUTPUT streams.
+ return true;
+ }
+
+ bool isPauseSupported() const override {
+ // Only implement PAUSE for OUTPUT streams.
+ return true;
+ }
+
aaudio_result_t getTimestamp(clockid_t clockId,
int64_t *framePosition,
int64_t *timeNanoseconds) override;
diff --git a/media/libaaudio/src/utility/AAudioUtilities.cpp b/media/libaaudio/src/utility/AAudioUtilities.cpp
index 2a34016..adc4904 100644
--- a/media/libaaudio/src/utility/AAudioUtilities.cpp
+++ b/media/libaaudio/src/utility/AAudioUtilities.cpp
@@ -441,3 +441,31 @@
}
return prop;
}
+
+aaudio_result_t AAudio_isFlushAllowed(aaudio_stream_state_t state) {
+ aaudio_result_t result = AAUDIO_OK;
+ switch (state) {
+// Proceed with flushing.
+ case AAUDIO_STREAM_STATE_OPEN:
+ case AAUDIO_STREAM_STATE_PAUSED:
+ case AAUDIO_STREAM_STATE_STOPPED:
+ case AAUDIO_STREAM_STATE_FLUSHED:
+ break;
+
+// Transition from one inactive state to another.
+ case AAUDIO_STREAM_STATE_STARTING:
+ case AAUDIO_STREAM_STATE_STARTED:
+ case AAUDIO_STREAM_STATE_STOPPING:
+ case AAUDIO_STREAM_STATE_PAUSING:
+ case AAUDIO_STREAM_STATE_FLUSHING:
+ case AAUDIO_STREAM_STATE_CLOSING:
+ case AAUDIO_STREAM_STATE_CLOSED:
+ case AAUDIO_STREAM_STATE_DISCONNECTED:
+ default:
+ ALOGE("can only flush stream when PAUSED, OPEN or STOPPED, state = %s",
+ AAudio_convertStreamStateToText(state));
+ result = AAUDIO_ERROR_INVALID_STATE;
+ break;
+ }
+ return result;
+}
diff --git a/media/libaaudio/src/utility/AAudioUtilities.h b/media/libaaudio/src/utility/AAudioUtilities.h
index 0c59f6d..3673c34 100644
--- a/media/libaaudio/src/utility/AAudioUtilities.h
+++ b/media/libaaudio/src/utility/AAudioUtilities.h
@@ -23,7 +23,7 @@
#include <sys/types.h>
#include <utils/Errors.h>
-#include <hardware/audio.h>
+#include <system/audio.h>
#include "aaudio/AAudio.h"
@@ -268,6 +268,14 @@
*/
int32_t AAudioProperty_getHardwareBurstMinMicros();
+
+/**
+ * Is flush allowed for the given state?
+ * @param state
+ * @return AAUDIO_OK if allowed or an error
+ */
+aaudio_result_t AAudio_isFlushAllowed(aaudio_stream_state_t state);
+
/**
* Try a function f until it returns true.
*
diff --git a/media/libaaudio/tests/test_various.cpp b/media/libaaudio/tests/test_various.cpp
index dc19985..4b065c9 100644
--- a/media/libaaudio/tests/test_various.cpp
+++ b/media/libaaudio/tests/test_various.cpp
@@ -26,7 +26,6 @@
#include <gtest/gtest.h>
#include <unistd.h>
-
// Callback function that does nothing.
aaudio_data_callback_result_t NoopDataCallbackProc(
AAudioStream *stream,
@@ -45,73 +44,376 @@
constexpr int64_t NANOS_PER_MILLISECOND = 1000 * 1000;
-//int foo() { // To fix Android Studio formatting when editing.
-TEST(test_various, aaudio_stop_when_open) {
+enum FunctionToCall {
+ CALL_START, CALL_STOP, CALL_PAUSE, CALL_FLUSH
+};
+
+void checkStateTransition(aaudio_performance_mode_t perfMode,
+ aaudio_stream_state_t originalState,
+ FunctionToCall functionToCall,
+ aaudio_result_t expectedResult,
+ aaudio_stream_state_t expectedState) {
AAudioStreamBuilder *aaudioBuilder = nullptr;
AAudioStream *aaudioStream = nullptr;
-// Use an AAudioStreamBuilder to contain requested parameters.
+ // Use an AAudioStreamBuilder to contain requested parameters.
ASSERT_EQ(AAUDIO_OK, AAudio_createStreamBuilder(&aaudioBuilder));
-// Request stream properties.
+ // Request stream properties.
AAudioStreamBuilder_setDataCallback(aaudioBuilder, NoopDataCallbackProc, nullptr);
- AAudioStreamBuilder_setPerformanceMode(aaudioBuilder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
+ AAudioStreamBuilder_setPerformanceMode(aaudioBuilder, perfMode);
-// Create an AAudioStream using the Builder.
- EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream));
+ // Create an AAudioStream using the Builder.
+ ASSERT_EQ(AAUDIO_OK, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream));
-
+ // Verify Open State
aaudio_stream_state_t state = AAUDIO_STREAM_STATE_UNKNOWN;
EXPECT_EQ(AAUDIO_OK, AAudioStream_waitForStateChange(aaudioStream,
AAUDIO_STREAM_STATE_UNKNOWN, &state,
1000 * NANOS_PER_MILLISECOND));
EXPECT_EQ(AAUDIO_STREAM_STATE_OPEN, state);
- EXPECT_EQ(AAUDIO_OK, AAudioStream_requestStop(aaudioStream));
+ // Put stream into desired state.
+ aaudio_stream_state_t inputState = AAUDIO_STREAM_STATE_UNINITIALIZED;
+ if (originalState != AAUDIO_STREAM_STATE_OPEN) {
- state = AAUDIO_STREAM_STATE_UNKNOWN;
+ ASSERT_EQ(AAUDIO_OK, AAudioStream_requestStart(aaudioStream));
+
+ if (originalState != AAUDIO_STREAM_STATE_STARTING) {
+
+ ASSERT_EQ(AAUDIO_OK, AAudioStream_waitForStateChange(aaudioStream,
+ AAUDIO_STREAM_STATE_STARTING,
+ &state,
+ 1000 * NANOS_PER_MILLISECOND));
+ ASSERT_EQ(AAUDIO_STREAM_STATE_STARTED, state);
+
+ if (originalState == AAUDIO_STREAM_STATE_STOPPING) {
+ ASSERT_EQ(AAUDIO_OK, AAudioStream_requestStop(aaudioStream));
+ } else if (originalState == AAUDIO_STREAM_STATE_STOPPED) {
+ ASSERT_EQ(AAUDIO_OK, AAudioStream_requestStop(aaudioStream));
+ inputState = AAUDIO_STREAM_STATE_STOPPING;
+ } else if (originalState == AAUDIO_STREAM_STATE_PAUSING) {
+ ASSERT_EQ(AAUDIO_OK, AAudioStream_requestPause(aaudioStream));
+ } else if (originalState == AAUDIO_STREAM_STATE_PAUSED) {
+ ASSERT_EQ(AAUDIO_OK, AAudioStream_requestPause(aaudioStream));
+ inputState = AAUDIO_STREAM_STATE_PAUSING;
+ }
+ }
+ }
+
+ // Wait until past transitional state.
+ if (inputState != AAUDIO_STREAM_STATE_UNINITIALIZED) {
+ ASSERT_EQ(AAUDIO_OK, AAudioStream_waitForStateChange(aaudioStream,
+ inputState,
+ &state,
+ 1000 * NANOS_PER_MILLISECOND));
+ ASSERT_EQ(originalState, state);
+ }
+
+ aaudio_stream_state_t transitionalState = originalState;
+ switch(functionToCall) {
+ case FunctionToCall::CALL_START:
+ EXPECT_EQ(expectedResult, AAudioStream_requestStart(aaudioStream));
+ transitionalState = AAUDIO_STREAM_STATE_STARTING;
+ break;
+ case FunctionToCall::CALL_STOP:
+ EXPECT_EQ(expectedResult, AAudioStream_requestStop(aaudioStream));
+ transitionalState = AAUDIO_STREAM_STATE_STOPPING;
+ break;
+ case FunctionToCall::CALL_PAUSE:
+ EXPECT_EQ(expectedResult, AAudioStream_requestPause(aaudioStream));
+ transitionalState = AAUDIO_STREAM_STATE_PAUSING;
+ break;
+ case FunctionToCall::CALL_FLUSH:
+ EXPECT_EQ(expectedResult, AAudioStream_requestFlush(aaudioStream));
+ transitionalState = AAUDIO_STREAM_STATE_FLUSHING;
+ break;
+ }
+
EXPECT_EQ(AAUDIO_OK, AAudioStream_waitForStateChange(aaudioStream,
- AAUDIO_STREAM_STATE_UNKNOWN, &state, 0));
- EXPECT_EQ(AAUDIO_STREAM_STATE_OPEN, state);
-
- AAudioStream_close(aaudioStream);
- AAudioStreamBuilder_delete(aaudioBuilder);
-}
-
-//int boo() { // To fix Android Studio formatting when editing.
-TEST(test_various, aaudio_flush_when_started) {
- AAudioStreamBuilder *aaudioBuilder = nullptr;
- AAudioStream *aaudioStream = nullptr;
-
-// Use an AAudioStreamBuilder to contain requested parameters.
- ASSERT_EQ(AAUDIO_OK, AAudio_createStreamBuilder(&aaudioBuilder));
-
-// Request stream properties.
- AAudioStreamBuilder_setDataCallback(aaudioBuilder, NoopDataCallbackProc, nullptr);
- AAudioStreamBuilder_setPerformanceMode(aaudioBuilder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
-
-// Create an AAudioStream using the Builder.
- EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream));
- EXPECT_EQ(AAUDIO_OK, AAudioStream_requestStart(aaudioStream));
-
- aaudio_stream_state_t state = AAUDIO_STREAM_STATE_UNKNOWN;
- EXPECT_EQ(AAUDIO_OK, AAudioStream_waitForStateChange(aaudioStream,
- AAUDIO_STREAM_STATE_STARTING, &state,
+ transitionalState,
+ &state,
1000 * NANOS_PER_MILLISECOND));
- EXPECT_EQ(AAUDIO_STREAM_STATE_STARTED, state);
-
- EXPECT_EQ(AAUDIO_ERROR_INVALID_STATE, AAudioStream_requestFlush(aaudioStream));
-
- state = AAUDIO_STREAM_STATE_UNKNOWN;
- EXPECT_EQ(AAUDIO_OK, AAudioStream_waitForStateChange(aaudioStream,
- AAUDIO_STREAM_STATE_UNKNOWN, &state, 0));
- EXPECT_EQ(AAUDIO_STREAM_STATE_STARTED, state);
+ // We should not change state when a function fails.
+ if (expectedResult != AAUDIO_OK) {
+ ASSERT_EQ(originalState, expectedState);
+ }
+ EXPECT_EQ(expectedState, state);
+ if (state != expectedState) {
+ printf("ERROR - expected %s, actual = %s\n",
+ AAudio_convertStreamStateToText(expectedState),
+ AAudio_convertStreamStateToText(state));
+ fflush(stdout);
+ }
AAudioStream_close(aaudioStream);
AAudioStreamBuilder_delete(aaudioBuilder);
}
-//int main() { // To fix Android Studio formatting when editing.
+// TODO Use parameterized tests instead of these individual specific tests.
+
+// OPEN =================================================================
+TEST(test_various, aaudio_state_lowlat_open_start) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+ AAUDIO_STREAM_STATE_OPEN,
+ FunctionToCall::CALL_START,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_STARTED);
+}
+
+TEST(test_various, aaudio_state_none_open_start) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_STREAM_STATE_OPEN,
+ FunctionToCall::CALL_START,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_STARTED);
+}
+
+TEST(test_various, aaudio_state_lowlat_open_stop) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+ AAUDIO_STREAM_STATE_OPEN,
+ FunctionToCall::CALL_STOP,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_STOPPED);
+}
+
+TEST(test_various, aaudio_state_none_open_stop) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_STREAM_STATE_OPEN,
+ FunctionToCall::CALL_STOP,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_STOPPED);
+}
+
+TEST(test_various, aaudio_state_lowlat_open_pause) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+ AAUDIO_STREAM_STATE_OPEN,
+ FunctionToCall::CALL_PAUSE,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_PAUSED);
+}
+
+TEST(test_various, aaudio_state_none_open_pause) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_STREAM_STATE_OPEN,
+ FunctionToCall::CALL_PAUSE,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_PAUSED);
+}
+
+TEST(test_various, aaudio_state_lowlat_open_flush) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+ AAUDIO_STREAM_STATE_OPEN,
+ FunctionToCall::CALL_FLUSH,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_FLUSHED);
+}
+
+TEST(test_various, aaudio_state_none_open_flush) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_STREAM_STATE_OPEN,
+ FunctionToCall::CALL_FLUSH,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_FLUSHED);
+}
+
+
+// STARTED =================================================================
+TEST(test_various, aaudio_state_lowlat_started_start) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+ AAUDIO_STREAM_STATE_STARTED,
+ FunctionToCall::CALL_START,
+ AAUDIO_ERROR_INVALID_STATE,
+ AAUDIO_STREAM_STATE_STARTED);
+}
+
+TEST(test_various, aaudio_state_none_started_start) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_STREAM_STATE_STARTED,
+ FunctionToCall::CALL_START,
+ AAUDIO_ERROR_INVALID_STATE,
+ AAUDIO_STREAM_STATE_STARTED);
+}
+
+TEST(test_various, aaudio_state_lowlat_started_stop) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+ AAUDIO_STREAM_STATE_STARTED,
+ FunctionToCall::CALL_STOP,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_STOPPED);
+}
+
+TEST(test_various, aaudio_state_none_started_stop) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_STREAM_STATE_STARTED,
+ FunctionToCall::CALL_STOP,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_STOPPED);
+}
+
+TEST(test_various, aaudio_state_lowlat_started_pause) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+ AAUDIO_STREAM_STATE_STARTED,
+ FunctionToCall::CALL_PAUSE,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_PAUSED);
+}
+
+TEST(test_various, aaudio_state_none_started_pause) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_STREAM_STATE_STARTED,
+ FunctionToCall::CALL_PAUSE,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_PAUSED);
+}
+
+TEST(test_various, aaudio_state_lowlat_started_flush) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+ AAUDIO_STREAM_STATE_STARTED,
+ FunctionToCall::CALL_FLUSH,
+ AAUDIO_ERROR_INVALID_STATE,
+ AAUDIO_STREAM_STATE_STARTED);
+}
+
+TEST(test_various, aaudio_state_none_started_flush) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_STREAM_STATE_STARTED,
+ FunctionToCall::CALL_FLUSH,
+ AAUDIO_ERROR_INVALID_STATE,
+ AAUDIO_STREAM_STATE_STARTED);
+}
+
+// STOPPED =================================================================
+TEST(test_various, aaudio_state_lowlat_stopped_start) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+ AAUDIO_STREAM_STATE_STOPPED,
+ FunctionToCall::CALL_START,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_STARTED);
+}
+
+TEST(test_various, aaudio_state_none_stopped_start) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_STREAM_STATE_STOPPED,
+ FunctionToCall::CALL_START,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_STARTED);
+}
+
+TEST(test_various, aaudio_state_lowlat_stopped_stop) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+ AAUDIO_STREAM_STATE_STOPPED,
+ FunctionToCall::CALL_STOP,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_STOPPED);
+}
+
+TEST(test_various, aaudio_state_none_stopped_stop) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_STREAM_STATE_STOPPED,
+ FunctionToCall::CALL_STOP,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_STOPPED);
+}
+
+TEST(test_various, aaudio_state_lowlat_stopped_pause) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+ AAUDIO_STREAM_STATE_STOPPED,
+ FunctionToCall::CALL_PAUSE,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_PAUSED);
+}
+
+TEST(test_various, aaudio_state_none_stopped_pause) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_STREAM_STATE_STOPPED,
+ FunctionToCall::CALL_PAUSE,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_PAUSED);
+}
+
+TEST(test_various, aaudio_state_lowlat_stopped_flush) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+ AAUDIO_STREAM_STATE_STOPPED,
+ FunctionToCall::CALL_FLUSH,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_FLUSHED);
+}
+
+TEST(test_various, aaudio_state_none_stopped_flush) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_STREAM_STATE_STOPPED,
+ FunctionToCall::CALL_FLUSH,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_FLUSHED);
+}
+
+// PAUSED =================================================================
+TEST(test_various, aaudio_state_lowlat_paused_start) {
+checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+ AAUDIO_STREAM_STATE_PAUSED,
+ FunctionToCall::CALL_START,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_STARTED);
+}
+
+TEST(test_various, aaudio_state_none_paused_start) {
+checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_STREAM_STATE_PAUSED,
+ FunctionToCall::CALL_START,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_STARTED);
+}
+
+TEST(test_various, aaudio_state_lowlat_paused_stop) {
+checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+ AAUDIO_STREAM_STATE_PAUSED,
+ FunctionToCall::CALL_STOP,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_STOPPED);
+}
+
+TEST(test_various, aaudio_state_none_paused_stop) {
+checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_STREAM_STATE_PAUSED,
+ FunctionToCall::CALL_STOP,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_STOPPED);
+}
+
+TEST(test_various, aaudio_state_lowlat_paused_pause) {
+checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+ AAUDIO_STREAM_STATE_PAUSED,
+ FunctionToCall::CALL_PAUSE,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_PAUSED);
+}
+
+TEST(test_various, aaudio_state_none_paused_pause) {
+checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_STREAM_STATE_PAUSED,
+ FunctionToCall::CALL_PAUSE,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_PAUSED);
+}
+
+TEST(test_various, aaudio_state_lowlat_paused_flush) {
+checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+ AAUDIO_STREAM_STATE_PAUSED,
+ FunctionToCall::CALL_FLUSH,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_FLUSHED);
+}
+
+TEST(test_various, aaudio_state_none_paused_flush) {
+checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_STREAM_STATE_PAUSED,
+ FunctionToCall::CALL_FLUSH,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_FLUSHED);
+}
+
+// ==========================================================================
TEST(test_various, aaudio_set_buffer_size) {
int32_t bufferCapacity;
@@ -174,7 +476,6 @@
AAudioStreamBuilder_delete(aaudioBuilder);
}
-
// ************************************************************
// Test to make sure that AAUDIO_CALLBACK_RESULT_STOP works.
diff --git a/media/libmedia/nuplayer2/Android.bp b/media/libmedia/nuplayer2/Android.bp
index d609ba0..700c840 100644
--- a/media/libmedia/nuplayer2/Android.bp
+++ b/media/libmedia/nuplayer2/Android.bp
@@ -22,7 +22,6 @@
],
include_dirs: [
- "frameworks/av/media/libmedia/include",
"frameworks/av/media/libstagefright",
"frameworks/av/media/libstagefright/httplive",
"frameworks/av/media/libstagefright/include",
@@ -54,6 +53,10 @@
"libpowermanager",
],
+ static_libs: [
+ "libmedia_helper",
+ ],
+
name: "libstagefright_nuplayer2",
tags: ["eng"],
diff --git a/media/libstagefright/codec2/include/C2.h b/media/libstagefright/codec2/include/C2.h
index fd99cce..442c89e 100644
--- a/media/libstagefright/codec2/include/C2.h
+++ b/media/libstagefright/codec2/include/C2.h
@@ -210,6 +210,219 @@
} \
DEFINE_OTHER_COMPARISON_OPERATORS(type)
+template<typename T, typename B>
+class C2_HIDE c2_cntr_t;
+
+/// \cond INTERNAL
+
+/// \defgroup utils_internal
+/// @{
+
+template<typename T>
+struct C2_HIDE _c2_cntr_compat_helper {
+ template<typename U, typename E=typename std::enable_if<std::is_integral<U>::value>::type>
+ __attribute__((no_sanitize("integer")))
+ inline static constexpr T get(const U &value) {
+ return T(value);
+ }
+
+ template<typename U, typename E=typename std::enable_if<(sizeof(U) >= sizeof(T))>::type>
+ __attribute__((no_sanitize("integer")))
+ inline static constexpr T get(const c2_cntr_t<U, void> &value) {
+ return T(value.mValue);
+ }
+};
+
+/// @}
+
+/// \endcond
+
+/**
+ * Integral counter type.
+ *
+ * This is basically an unsigned integral type that is NEVER checked for overflow/underflow - and
+ * comparison operators are redefined.
+ *
+ * \note Comparison of counter types is not fully transitive, e.g.
+ * it could be that a > b > c but a !> c.
+ * std::less<>, greater<>, less_equal<> and greater_equal<> specializations yield total ordering,
+ * but may not match semantic ordering of the values.
+ *
+ * Technically: counter types represent integer values: A * 2^N + value, where A can be arbitrary.
+ * This makes addition, subtraction, multiplication (as well as bitwise operations) well defined.
+ * However, division is in general not well defined, as the result may depend on A. This is also
+ * true for logical operators and boolean conversion.
+ *
+ * Even though well defined, bitwise operators are not implemented for counter types as they are not
+ * meaningful.
+ */
+template<typename T, typename B=typename std::enable_if<std::is_integral<T>::value && std::is_unsigned<T>::value>::type>
+class C2_HIDE c2_cntr_t {
+ using compat = _c2_cntr_compat_helper<T>;
+
+ T mValue;
+ constexpr static T HALF_RANGE = T(~0) ^ (T(~0) >> 1);
+
+ template<typename U>
+ friend struct _c2_cntr_compat_helper;
+public:
+
+ /**
+ * Default constructor. Initialized counter to 0.
+ */
+ inline constexpr c2_cntr_t() : mValue(T(0)) {}
+
+ /**
+ * Construct from a compatible type.
+ */
+ template<typename U>
+ inline constexpr c2_cntr_t(const U &value) : mValue(compat::get(value)) {}
+
+ /**
+ * Peek as underlying signed type.
+ */
+ __attribute__((no_sanitize("integer")))
+ inline constexpr typename std::make_signed<T>::type peek() const {
+ return static_cast<typename std::make_signed<T>::type>(mValue);
+ }
+
+ /**
+ * Peek as underlying unsigned type.
+ */
+ inline constexpr T peeku() const {
+ return mValue;
+ }
+
+ /**
+ * Peek as long long - e.g. for printing.
+ */
+ __attribute__((no_sanitize("integer")))
+ inline constexpr long long peekll() const {
+ return (long long)mValue;
+ }
+
+ /**
+ * Peek as unsigned long long - e.g. for printing.
+ */
+ __attribute__((no_sanitize("integer")))
+ inline constexpr unsigned long long peekull() const {
+ return (unsigned long long)mValue;
+ }
+
+ /**
+ * Convert to a smaller counter type. This is always safe.
+ */
+ template<typename U, typename E=typename std::enable_if<sizeof(U) < sizeof(T)>::type>
+ inline operator c2_cntr_t<U>() {
+ return c2_cntr_t<U>(mValue);
+ }
+
+ /**
+ * Arithmetic operators
+ */
+
+#define DEFINE_C2_CNTR_BINARY_OP(attrib, op, op_assign) \
+ template<typename U> \
+ attrib inline c2_cntr_t<T>& operator op_assign(const U &value) { \
+ mValue op_assign compat::get(value); \
+ return *this; \
+ } \
+ \
+ template<typename U, typename E=decltype(compat::get(U(0)))> \
+ attrib inline constexpr c2_cntr_t<T> operator op(const U &value) const { \
+ return c2_cntr_t<T>(mValue op compat::get(value)); \
+ } \
+ \
+ template<typename U, typename E=typename std::enable_if<sizeof(U) < sizeof(T)>::type> \
+ attrib inline constexpr c2_cntr_t<U> operator op(const c2_cntr_t<U> &value) const { \
+ return c2_cntr_t<U>(U(mValue) op value.peeku()); \
+ }
+
+#define DEFINE_C2_CNTR_UNARY_OP(attrib, op) \
+ attrib inline constexpr c2_cntr_t<T> operator op() const { \
+ return c2_cntr_t<T>(op mValue); \
+ }
+
+#define DEFINE_C2_CNTR_CREMENT_OP(attrib, op) \
+ attrib inline c2_cntr_t<T> &operator op() { \
+ op mValue; \
+ return *this; \
+ } \
+ attrib inline c2_cntr_t<T> operator op(int) { \
+ return c2_cntr_t<T, void>(mValue op); \
+ }
+
+ DEFINE_C2_CNTR_BINARY_OP(__attribute__((no_sanitize("integer"))), +, +=)
+ DEFINE_C2_CNTR_BINARY_OP(__attribute__((no_sanitize("integer"))), -, -=)
+ DEFINE_C2_CNTR_BINARY_OP(__attribute__((no_sanitize("integer"))), *, *=)
+
+ DEFINE_C2_CNTR_UNARY_OP(__attribute__((no_sanitize("integer"))), -)
+ DEFINE_C2_CNTR_UNARY_OP(__attribute__((no_sanitize("integer"))), +)
+
+ DEFINE_C2_CNTR_CREMENT_OP(__attribute__((no_sanitize("integer"))), ++)
+ DEFINE_C2_CNTR_CREMENT_OP(__attribute__((no_sanitize("integer"))), --)
+
+ template<typename U, typename E=typename std::enable_if<std::is_unsigned<U>::value>::type>
+ __attribute__((no_sanitize("integer")))
+ inline constexpr c2_cntr_t<T> operator<<(const U &value) const {
+ return c2_cntr_t<T>(mValue << value);
+ }
+
+ template<typename U, typename E=typename std::enable_if<std::is_unsigned<U>::value>::type>
+ __attribute__((no_sanitize("integer")))
+ inline c2_cntr_t<T> &operator<<=(const U &value) {
+ mValue <<= value;
+ return *this;
+ }
+
+ /**
+ * Comparison operators
+ */
+ __attribute__((no_sanitize("integer")))
+ inline constexpr bool operator<=(const c2_cntr_t<T> &other) const {
+ return T(other.mValue - mValue) < HALF_RANGE;
+ }
+
+ __attribute__((no_sanitize("integer")))
+ inline constexpr bool operator>=(const c2_cntr_t<T> &other) const {
+ return T(mValue - other.mValue) < HALF_RANGE;
+ }
+
+ inline constexpr bool operator==(const c2_cntr_t<T> &other) const {
+ return mValue == other.mValue;
+ }
+
+ inline constexpr bool operator!=(const c2_cntr_t<T> &other) const {
+ return !(*this == other);
+ }
+
+ inline constexpr bool operator<(const c2_cntr_t<T> &other) const {
+ return *this <= other && *this != other;
+ }
+
+ inline constexpr bool operator>(const c2_cntr_t<T> &other) const {
+ return *this >= other && *this != other;
+ }
+};
+
+template<typename U, typename T, typename E=typename std::enable_if<std::is_integral<U>::value>::type>
+inline constexpr c2_cntr_t<T> operator+(const U &a, const c2_cntr_t<T> &b) {
+ return b + a;
+}
+
+template<typename U, typename T, typename E=typename std::enable_if<std::is_integral<U>::value>::type>
+inline constexpr c2_cntr_t<T> operator-(const U &a, const c2_cntr_t<T> &b) {
+ return c2_cntr_t<T>(a) - b;
+}
+
+template<typename U, typename T, typename E=typename std::enable_if<std::is_integral<U>::value>::type>
+inline constexpr c2_cntr_t<T> operator*(const U &a, const c2_cntr_t<T> &b) {
+ return b * a;
+}
+
+typedef c2_cntr_t<uint32_t> c2_cntr32_t; /** 32-bit counter type */
+typedef c2_cntr_t<uint64_t> c2_cntr64_t; /** 64-bit counter type */
+
/// \cond INTERNAL
/// \defgroup utils_internal
@@ -326,4 +539,30 @@
} // namespace android
#endif
+#include <functional>
+template<typename T>
+struct std::less<::android::c2_cntr_t<T>> {
+ constexpr bool operator()(const ::android::c2_cntr_t<T> &lh, const ::android::c2_cntr_t<T> &rh) const {
+ return lh.peeku() < rh.peeku();
+ }
+};
+template<typename T>
+struct std::less_equal<::android::c2_cntr_t<T>> {
+ constexpr bool operator()(const ::android::c2_cntr_t<T> &lh, const ::android::c2_cntr_t<T> &rh) const {
+ return lh.peeku() <= rh.peeku();
+ }
+};
+template<typename T>
+struct std::greater<::android::c2_cntr_t<T>> {
+ constexpr bool operator()(const ::android::c2_cntr_t<T> &lh, const ::android::c2_cntr_t<T> &rh) const {
+ return lh.peeku() > rh.peeku();
+ }
+};
+template<typename T>
+struct std::greater_equal<::android::c2_cntr_t<T>> {
+ constexpr bool operator()(const ::android::c2_cntr_t<T> &lh, const ::android::c2_cntr_t<T> &rh) const {
+ return lh.peeku() >= rh.peeku();
+ }
+};
+
#endif // C2_H_
diff --git a/media/libstagefright/codec2/include/C2Param.h b/media/libstagefright/codec2/include/C2Param.h
index 9785c87..0540155 100644
--- a/media/libstagefright/codec2/include/C2Param.h
+++ b/media/libstagefright/codec2/include/C2Param.h
@@ -735,18 +735,22 @@
public:
/// A union of supported primitive types.
union Primitive {
- int32_t i32; ///< int32_t value
- uint32_t u32; ///< uint32_t value
- int64_t i64; ///< int64_t value
- uint64_t u64; ///< uint64_t value
- float fp; ///< float value
+ int32_t i32; ///< int32_t value
+ uint32_t u32; ///< uint32_t value
+ c2_cntr32_t c32; ///< c2_cntr32_t value
+ int64_t i64; ///< int64_t value
+ uint64_t u64; ///< uint64_t value
+ c2_cntr64_t c64; ///< c2_cntr64_t value
+ float fp; ///< float value
// constructors - implicit
- Primitive(int32_t value) : i32(value) { }
- Primitive(uint32_t value) : u32(value) { }
- Primitive(int64_t value) : i64(value) { }
- Primitive(uint64_t value) : u64(value) { }
- Primitive(float value) : fp(value) { }
+ Primitive(int32_t value) : i32(value) { }
+ Primitive(uint32_t value) : u32(value) { }
+ Primitive(c2_cntr32_t value) : c32(value) { }
+ Primitive(int64_t value) : i64(value) { }
+ Primitive(uint64_t value) : u64(value) { }
+ Primitive(c2_cntr64_t value) : c64(value) { }
+ Primitive(float value) : fp(value) { }
Primitive() : u64(0) { }
@@ -759,8 +763,10 @@
NO_INIT,
INT32,
UINT32,
+ CNTR32,
INT64,
UINT64,
+ CNTR64,
FLOAT,
};
@@ -792,12 +798,16 @@
template<> inline const int64_t &C2Value::Primitive::ref<int64_t>() const { return i64; }
template<> inline const uint32_t &C2Value::Primitive::ref<uint32_t>() const { return u32; }
template<> inline const uint64_t &C2Value::Primitive::ref<uint64_t>() const { return u64; }
+template<> inline const c2_cntr32_t &C2Value::Primitive::ref<c2_cntr32_t>() const { return c32; }
+template<> inline const c2_cntr64_t &C2Value::Primitive::ref<c2_cntr64_t>() const { return c64; }
template<> inline const float &C2Value::Primitive::ref<float>() const { return fp; }
template<> constexpr C2Value::type_t C2Value::typeFor<int32_t>() { return INT32; }
template<> constexpr C2Value::type_t C2Value::typeFor<int64_t>() { return INT64; }
template<> constexpr C2Value::type_t C2Value::typeFor<uint32_t>() { return UINT32; }
template<> constexpr C2Value::type_t C2Value::typeFor<uint64_t>() { return UINT64; }
+template<> constexpr C2Value::type_t C2Value::typeFor<c2_cntr32_t>() { return CNTR32; }
+template<> constexpr C2Value::type_t C2Value::typeFor<c2_cntr64_t>() { return CNTR64; }
template<> constexpr C2Value::type_t C2Value::typeFor<float>() { return FLOAT; }
/**
@@ -817,8 +827,10 @@
// primitive types
INT32 = C2Value::INT32, ///< 32-bit signed integer
UINT32 = C2Value::UINT32, ///< 32-bit unsigned integer
+ CNTR32 = C2Value::CNTR32, ///< 32-bit counter
INT64 = C2Value::INT64, ///< 64-bit signed integer
UINT64 = C2Value::UINT64, ///< 64-bit signed integer
+ CNTR64 = C2Value::CNTR64, ///< 64-bit counter
FLOAT = C2Value::FLOAT, ///< 32-bit floating point
// array types
@@ -903,13 +915,15 @@
// 2) this is at parameter granularity.
// type resolution
- inline static type_t getType(int32_t*) { return INT32; }
- inline static type_t getType(uint32_t*) { return UINT32; }
- inline static type_t getType(int64_t*) { return INT64; }
- inline static type_t getType(uint64_t*) { return UINT64; }
- inline static type_t getType(float*) { return FLOAT; }
- inline static type_t getType(char*) { return STRING; }
- inline static type_t getType(uint8_t*) { return BLOB; }
+ inline static type_t getType(int32_t*) { return INT32; }
+ inline static type_t getType(uint32_t*) { return UINT32; }
+ inline static type_t getType(c2_cntr32_t*) { return CNTR32; }
+ inline static type_t getType(int64_t*) { return INT64; }
+ inline static type_t getType(uint64_t*) { return UINT64; }
+ inline static type_t getType(c2_cntr64_t*) { return CNTR64; }
+ inline static type_t getType(float*) { return FLOAT; }
+ inline static type_t getType(char*) { return STRING; }
+ inline static type_t getType(uint8_t*) { return BLOB; }
template<typename T,
class=typename std::enable_if<std::is_enum<T>::value>::type>
@@ -936,8 +950,10 @@
// non-enumerated integral types.
DEFINE_NO_NAMED_VALUES_FOR(int32_t)
DEFINE_NO_NAMED_VALUES_FOR(uint32_t)
+DEFINE_NO_NAMED_VALUES_FOR(c2_cntr32_t)
DEFINE_NO_NAMED_VALUES_FOR(int64_t)
DEFINE_NO_NAMED_VALUES_FOR(uint64_t)
+DEFINE_NO_NAMED_VALUES_FOR(c2_cntr64_t)
DEFINE_NO_NAMED_VALUES_FOR(uint8_t)
DEFINE_NO_NAMED_VALUES_FOR(char)
DEFINE_NO_NAMED_VALUES_FOR(float)
diff --git a/media/libstagefright/codec2/tests/C2_test.cpp b/media/libstagefright/codec2/tests/C2_test.cpp
index 92a3d91..46f545f 100644
--- a/media/libstagefright/codec2/tests/C2_test.cpp
+++ b/media/libstagefright/codec2/tests/C2_test.cpp
@@ -75,4 +75,88 @@
static_assert(c2_const_checker<max_u32_u64>::num() == 3, "should be 3");
static_assert(c2_const_checker<max_u32_u8>::num() == 0x7fffffff, "should be 0x7fffffff");
+/* ======================================= COUNTER TESTS ======================================= */
+
+void c2_cntr_static_test() {
+ // sanity checks for construction/assignment
+ constexpr c2_cntr32_t c32_a(123);
+ constexpr c2_cntr64_t c64_a(-456);
+ c2_cntr32_t c32_b __unused = c64_a;
+ // c32_b = 64.; // DISALLOWED
+ // c2_cntr64_t c64_b = c32_a; // DISALLOWED
+
+ // sanity checks for some constexpr operators
+ static_assert(std::is_same<decltype(c32_a + c64_a), decltype(c64_a + c32_a)>::value, "+ should result same type");
+ static_assert(c32_a + c64_a == c2_cntr32_t(-333), "123 + -456 = -333");
+ static_assert(c32_a + c32_a == c2_cntr32_t(246), "123 + 123 = 246");
+ static_assert(c64_a + c32_a == c2_cntr32_t(-333), "-456 + 123 = 579");
+ static_assert(std::is_same<decltype(c32_a + 1), decltype(1 + c32_a)>::value, "+ should result same type");
+ static_assert(c32_a + 456 == c2_cntr32_t(579), "123 + 456 = 579");
+ static_assert(456 + c64_a == c2_cntr64_t(0), "456 + -456 = 0");
+ static_assert(std::is_same<decltype(c32_a - c64_a), decltype(c64_a - c32_a)>::value, "- should result same type");
+ static_assert(c32_a - c64_a == c2_cntr32_t(579), "123 - -456 = 579");
+ static_assert(c32_a - c32_a == c2_cntr32_t(0), "123 - 123 = 0");
+ static_assert(c64_a - c32_a == c2_cntr32_t(-579), "-456 - 123 = -579");
+ static_assert(std::is_same<decltype(c32_a - 1), decltype(1 - c32_a)>::value, "- should result same type");
+ static_assert(c32_a - 456 == c2_cntr32_t(-333), "123 - 456 = -333");
+ static_assert(456 - c64_a == c2_cntr64_t(912), "456 - -456 = 912");
+ static_assert(std::is_same<decltype(c32_a * c64_a), decltype(c64_a * c32_a)>::value, "* should result same type");
+ static_assert(c32_a * c64_a == c2_cntr32_t(-56088), "123 * -456 = -56088");
+ static_assert(c32_a * c32_a == c2_cntr32_t(15129), "123 * 123 = 15129");
+ static_assert(c64_a * c32_a == c2_cntr32_t(-56088), "-456 * 123 = -56088");
+ static_assert(std::is_same<decltype(c32_a * 1), decltype(1 * c32_a)>::value, "* should result same type");
+ static_assert(c32_a * 456 == c2_cntr32_t(56088), "123 * 456 = 56088");
+ static_assert(456 * c64_a == c2_cntr64_t(-207936), "456 * -456 = -207936");
+
+ static_assert((c32_a << 26u) == c2_cntr32_t(0xEC000000), "123 << 26 = 0xEC000000");
+
+ // sanity checks for unary operators
+ static_assert(c2_cntr32_t(1) == +c2_cntr32_t(1), "1 == +1");
+ static_assert(c2_cntr32_t(1) == -c2_cntr32_t(-1), "1 == --1");
+
+ // sanity checks for comparison
+ using c8_t = c2_cntr_t<uint8_t>;
+ static_assert(c8_t(-0x80) > c8_t(0x7f), "80 > 7F");
+ static_assert(c8_t(-0x80) >= c8_t(0x7f), "80 >= 7F");
+ static_assert(c8_t(0x7f) > c8_t(0x7e), "7F > 7E");
+ static_assert(c8_t(0x7f) >= c8_t(0x7e), "7F >= 7E");
+ static_assert(!(c8_t(-0x80) > c8_t(0)), "80 !> 00");
+ static_assert(!(c8_t(-0x80) >= c8_t(0)), "80 !>= 00");
+ static_assert(!(c8_t(-0x80) > c8_t(-0x80)), "80 !> 80");
+ static_assert(c8_t(-0x80) >= c8_t(-0x80), "80 >= 80");
+
+ static_assert(c8_t(-0x80) == c8_t(0x80), "80 == 80");
+ static_assert(!(c8_t(-0x80) == c8_t(0)), "80 != 0");
+ static_assert(c8_t(-0x80) != c8_t(0x7f), "80 != 7F");
+ static_assert(!(c8_t(0x7f) != c8_t(0x7f)), "80 != 7F");
+
+ static_assert(c8_t(0x7f) < c8_t(-0x80), "7F < 80");
+ static_assert(c8_t(0x7f) <= c8_t(-0x80), "7F < 80");
+ static_assert(c8_t(0x7e) < c8_t(0x7f), "7E < 7F");
+ static_assert(c8_t(0x7e) <= c8_t(0x7f), "7E < 7F");
+ static_assert(!(c8_t(-0x40) < c8_t(0x40)), "-40 !< 40");
+ static_assert(!(c8_t(-0x40) <= c8_t(0x40)), "-40 !<= 40");
+ static_assert(!(c8_t(-0x40) < c8_t(-0x40)), "-40 !< -40");
+ static_assert(c8_t(-0x40) <= c8_t(-0x40), "-40 <= -40");
+
+ static_assert(c2_cntr32_t(-0x7fffffff - 1) > c2_cntr32_t(0x7fffffff), "80 > 7F");
+ static_assert(!(c2_cntr32_t(-0x7fffffff - 1) > c2_cntr32_t(0)), "80 !> 00");
+ static_assert(c2_cntr32_t(1) == c2_cntr32_t(c2_cntr64_t(0x100000001ul)), "1 == 1");
+}
+
+class C2Test : public ::testing::Test {
+};
+
+TEST_F(C2Test, CounterTest) {
+ c2_cntr32_t c32_a(123);
+ c2_cntr64_t c64_a(-456);
+ EXPECT_EQ(c32_a += 3, c2_cntr32_t(126));
+ EXPECT_EQ(c32_a += c64_a, c2_cntr32_t(-330));
+ EXPECT_EQ(c32_a <<= 2u, c2_cntr32_t(-1320));
+ EXPECT_EQ(c64_a *= 3, c2_cntr64_t(-1368));
+ EXPECT_EQ(c32_a -= c64_a, c2_cntr32_t(48));
+ EXPECT_EQ(c32_a -= 40, c2_cntr32_t(8));
+ EXPECT_EQ(c32_a *= c32_a, c2_cntr32_t(64));
+}
+
} // namespace android
diff --git a/media/libstagefright/include/media/stagefright/MetaData.h b/media/libstagefright/include/media/stagefright/MetaData.h
index c6b3f4d..5959e86 100644
--- a/media/libstagefright/include/media/stagefright/MetaData.h
+++ b/media/libstagefright/include/media/stagefright/MetaData.h
@@ -228,7 +228,7 @@
kTypeD263 = 'd263',
};
-class MetaData : public RefBase {
+class MetaData final : public RefBase {
public:
MetaData();
MetaData(const MetaData &from);
@@ -278,14 +278,18 @@
String8 toString() const;
void dumpToLog() const;
- status_t writeToParcel(Parcel &parcel);
- status_t updateFromParcel(const Parcel &parcel);
- static sp<MetaData> createFromParcel(const Parcel &parcel);
-
protected:
virtual ~MetaData();
private:
+ friend class BpMediaSource;
+ friend class BnMediaSource;
+ friend class BpMediaExtractor;
+ friend class BnMediaExtractor;
+
+ status_t writeToParcel(Parcel &parcel);
+ status_t updateFromParcel(const Parcel &parcel);
+ static sp<MetaData> createFromParcel(const Parcel &parcel);
struct typed_data;
struct Rect;
struct MetaDataInternal;
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_media_cc_disabled.png b/packages/MediaComponents/res/drawable-hdpi/ic_media_subtitle_disabled.png
similarity index 100%
rename from packages/MediaComponents/res/drawable-hdpi/ic_media_cc_disabled.png
rename to packages/MediaComponents/res/drawable-hdpi/ic_media_subtitle_disabled.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_media_cc_enabled.png b/packages/MediaComponents/res/drawable-hdpi/ic_media_subtitle_enabled.png
similarity index 100%
rename from packages/MediaComponents/res/drawable-hdpi/ic_media_cc_enabled.png
rename to packages/MediaComponents/res/drawable-hdpi/ic_media_subtitle_enabled.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_media_cc_disabled.png b/packages/MediaComponents/res/drawable-mdpi/ic_media_subtitle_disabled.png
similarity index 100%
rename from packages/MediaComponents/res/drawable-mdpi/ic_media_cc_disabled.png
rename to packages/MediaComponents/res/drawable-mdpi/ic_media_subtitle_disabled.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_media_cc_enabled.png b/packages/MediaComponents/res/drawable-mdpi/ic_media_subtitle_enabled.png
similarity index 100%
rename from packages/MediaComponents/res/drawable-mdpi/ic_media_cc_enabled.png
rename to packages/MediaComponents/res/drawable-mdpi/ic_media_subtitle_enabled.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable/ic_aspect_ratio.xml b/packages/MediaComponents/res/drawable/ic_aspect_ratio.xml
new file mode 100644
index 0000000..c6228e6
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_aspect_ratio.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M19,12h-2v3h-3v2h5v-5zM7,9h3L10,7L5,7v5h2L7,9zM21,3L3,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2L23,5c0,-1.1 -0.9,-2 -2,-2zM21,19.01L3,19.01L3,4.99h18v14.02z"
+ android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_mute.xml b/packages/MediaComponents/res/drawable/ic_mute.xml
new file mode 100644
index 0000000..560aaec
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_mute.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M16.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v2.21l2.45,2.45c0.03,-0.2 0.05,-0.41 0.05,-0.63zM19,12c0,0.94 -0.2,1.82 -0.54,2.64l1.51,1.51C20.63,14.91 21,13.5 21,12c0,-4.28 -2.99,-7.86 -7,-8.77v2.06c2.89,0.86 5,3.54 5,6.71zM4.27,3L3,4.27 7.73,9L3,9v6h4l5,5v-6.73l4.25,4.25c-0.67,0.52 -1.42,0.93 -2.25,1.18v2.06c1.38,-0.31 2.63,-0.95 3.69,-1.81L19.73,21 21,19.73l-9,-9L4.27,3zM12,4L9.91,6.09 12,8.18L12,4z"
+ android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_settings.xml b/packages/MediaComponents/res/drawable/ic_settings.xml
new file mode 100644
index 0000000..a59ecc1
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_settings.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M19.43,12.98c0.04,-0.32 0.07,-0.64 0.07,-0.98s-0.03,-0.66 -0.07,-0.98l2.11,-1.65c0.19,-0.15 0.24,-0.42 0.12,-0.64l-2,-3.46c-0.12,-0.22 -0.39,-0.3 -0.61,-0.22l-2.49,1c-0.52,-0.4 -1.08,-0.73 -1.69,-0.98l-0.38,-2.65C14.46,2.18 14.25,2 14,2h-4c-0.25,0 -0.46,0.18 -0.49,0.42l-0.38,2.65c-0.61,0.25 -1.17,0.59 -1.69,0.98l-2.49,-1c-0.23,-0.09 -0.49,0 -0.61,0.22l-2,3.46c-0.13,0.22 -0.07,0.49 0.12,0.64l2.11,1.65c-0.04,0.32 -0.07,0.65 -0.07,0.98s0.03,0.66 0.07,0.98l-2.11,1.65c-0.19,0.15 -0.24,0.42 -0.12,0.64l2,3.46c0.12,0.22 0.39,0.3 0.61,0.22l2.49,-1c0.52,0.4 1.08,0.73 1.69,0.98l0.38,2.65c0.03,0.24 0.24,0.42 0.49,0.42h4c0.25,0 0.46,-0.18 0.49,-0.42l0.38,-2.65c0.61,-0.25 1.17,-0.59 1.69,-0.98l2.49,1c0.23,0.09 0.49,0 0.61,-0.22l2,-3.46c0.12,-0.22 0.07,-0.49 -0.12,-0.64l-2.11,-1.65zM12,15.5c-1.93,0 -3.5,-1.57 -3.5,-3.5s1.57,-3.5 3.5,-3.5 3.5,1.57 3.5,3.5 -1.57,3.5 -3.5,3.5z"
+ android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_unmute.xml b/packages/MediaComponents/res/drawable/ic_unmute.xml
new file mode 100644
index 0000000..9dfb2b9
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_unmute.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M3,9v6h4l5,5L12,4L7,9L3,9zM16.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v8.05c1.48,-0.73 2.5,-2.25 2.5,-4.02zM14,3.23v2.06c2.89,0.86 5,3.54 5,6.71s-2.11,5.85 -5,6.71v2.06c4.01,-0.91 7,-4.49 7,-8.77s-2.99,-7.86 -7,-8.77z"
+ android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/layout/media_controller.xml b/packages/MediaComponents/res/layout/media_controller.xml
index 069fbdd..4d05546 100644
--- a/packages/MediaComponents/res/layout/media_controller.xml
+++ b/packages/MediaComponents/res/layout/media_controller.xml
@@ -46,7 +46,6 @@
android:paddingStart="4dp"
android:paddingEnd="4dp"
android:textSize="20sp"
- android:text="North by Northwest"
android:textColor="#FFFFFFFF" />
<view class="com.android.support.mediarouter.app.MediaRouteButton" android:id="@+id/cast"
@@ -84,7 +83,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="15dp"
- android:paddingRight="15dp"
android:orientation="horizontal">
<TextView
@@ -111,25 +109,50 @@
android:textStyle="bold"
android:textColor="#BBBBBB" />
- <ImageButton
- android:id="@+id/overflow"
- android:layout_alignParentEnd="true"
+ <LinearLayout
+ android:id="@+id/basic_controls"
+ android:layout_alignParentRight="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
android:layout_centerVertical="true"
- style="@style/BottomBarButton.Overflow"/>
+ android:orientation="horizontal" >
- <ImageButton
- android:id="@+id/fullscreen"
- android:layout_toLeftOf="@id/overflow"
- android:layout_centerVertical="true"
- style="@style/BottomBarButton.FullScreen"/>
+ <ImageButton
+ android:id="@+id/subtitle"
+ android:scaleType="fitCenter"
+ style="@style/BottomBarButton.CC" />
+ <ImageButton
+ android:id="@+id/fullscreen"
+ style="@style/BottomBarButton.FullScreen"/>
+ <ImageButton
+ android:id="@+id/overflow_right"
+ style="@style/BottomBarButton.OverflowRight"/>
+ </LinearLayout>
- <ImageButton
- android:id="@+id/cc"
- android:scaleType="fitCenter"
- android:layout_toLeftOf="@id/fullscreen"
+ <LinearLayout
+ android:id="@+id/extra_controls"
+ android:layout_alignParentRight="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
android:layout_centerVertical="true"
- style="@style/BottomBarButton.CC" />
+ android:visibility="gone"
+ android:orientation="horizontal" >
+
+ <ImageButton
+ android:id="@+id/mute"
+ style="@style/BottomBarButton.Mute" />
+ <ImageButton
+ android:id="@+id/aspect_ratio"
+ style="@style/BottomBarButton.AspectRatio" />
+ <ImageButton
+ android:id="@+id/settings"
+ style="@style/BottomBarButton.Settings" />
+ <ImageButton
+ android:id="@+id/overflow_left"
+ style="@style/BottomBarButton.OverflowLeft"/>
+ </LinearLayout>
</RelativeLayout>
</LinearLayout>
+
diff --git a/packages/MediaComponents/res/values/colors.xml b/packages/MediaComponents/res/values/colors.xml
new file mode 100644
index 0000000..9e071d7
--- /dev/null
+++ b/packages/MediaComponents/res/values/colors.xml
@@ -0,0 +1,19 @@
+<?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 name="gray">0xff444444</integer>
+</resources>
\ No newline at end of file
diff --git a/packages/MediaComponents/res/values/style.xml b/packages/MediaComponents/res/values/style.xml
index 8363dd2..c59380c 100644
--- a/packages/MediaComponents/res/values/style.xml
+++ b/packages/MediaComponents/res/values/style.xml
@@ -36,22 +36,40 @@
<style name="BottomBarButton">
<item name="android:background">@null</item>
- <item name="android:layout_width">24dp</item>
- <item name="android:layout_height">24dp</item>
- <item name="android:layout_margin">10dp</item>
+ <item name="android:layout_width">44dp</item>
+ <item name="android:layout_height">34dp</item>
+ <item name="android:layout_marginTop">5dp</item>
+ <item name="android:layout_marginBottom">5dp</item>
+ <item name="android:paddingLeft">5dp</item>
+ <item name="android:paddingRight">5dp</item>
</style>
<style name="BottomBarButton.CC">
- <item name="android:src">@drawable/ic_media_cc_disabled</item>
+ <item name="android:src">@drawable/ic_media_subtitle_disabled</item>
</style>
<style name="BottomBarButton.FullScreen">
<item name="android:src">@drawable/ic_fullscreen</item>
</style>
- <style name="BottomBarButton.Overflow">
+ <style name="BottomBarButton.OverflowRight">
<item name="android:src">@drawable/ic_chevron_right</item>
</style>
-</resources>
+ <style name="BottomBarButton.OverflowLeft">
+ <item name="android:src">@drawable/ic_chevron_left</item>
+ </style>
+ <style name="BottomBarButton.Settings">
+ <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>
+ </style>
+
+</resources>
diff --git a/packages/MediaComponents/src/com/android/media/MediaBrowser2Impl.java b/packages/MediaComponents/src/com/android/media/MediaBrowser2Impl.java
new file mode 100644
index 0000000..f159398
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/media/MediaBrowser2Impl.java
@@ -0,0 +1,76 @@
+/*
+ * 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.IMediaSession2;
+import android.media.MediaBrowser2;
+import android.media.MediaBrowser2.BrowserCallback;
+import android.media.MediaSession2.CommandButton;
+import android.media.SessionToken;
+import android.media.update.MediaBrowser2Provider;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+
+public class MediaBrowser2Impl extends MediaController2Impl implements MediaBrowser2Provider {
+ private final String TAG = "MediaBrowser2";
+ private final boolean DEBUG = true; // TODO(jaewan): change.
+
+ private final MediaBrowser2 mInstance;
+ private final MediaBrowser2.BrowserCallback mCallback;
+
+ public MediaBrowser2Impl(MediaBrowser2 instance, Context context, SessionToken token,
+ BrowserCallback callback, Executor executor) {
+ super(instance, context, token, callback, executor);
+ mInstance = instance;
+ mCallback = callback;
+ }
+
+ @Override
+ public void getBrowserRoot_impl(Bundle rootHints) {
+ final IMediaSession2 binder = getSessionBinder();
+ if (binder != null) {
+ try {
+ binder.getBrowserRoot(getControllerStub(), rootHints);
+ } catch (RemoteException e) {
+ // TODO(jaewan): Handle disconnect.
+ if (DEBUG) {
+ Log.w(TAG, "Cannot connect to the service or the session is gone", e);
+ }
+ }
+ } else {
+ Log.w(TAG, "Session isn't active", new IllegalStateException());
+ }
+ }
+
+ public void onGetRootResult(
+ final Bundle rootHints, final String rootMediaId, final Bundle rootExtra) {
+ getCallbackExecutor().execute(() -> {
+ mCallback.onGetRootResult(rootHints, rootMediaId, rootExtra);
+ });
+ }
+
+ public void onCustomLayoutChanged(final List<CommandButton> layout) {
+ getCallbackExecutor().execute(() -> {
+ mCallback.onCustomLayoutChanged(layout);
+ });
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/media/MediaController2Impl.java b/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
index 1a27056..08a7165 100644
--- a/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
@@ -24,6 +24,7 @@
import android.media.IMediaSession2Callback;
import android.media.MediaSession2;
import android.media.MediaSession2.Command;
+import android.media.MediaSession2.CommandButton;
import android.media.MediaSession2.CommandGroup;
import android.media.MediaController2;
import android.media.MediaController2.ControllerCallback;
@@ -105,7 +106,7 @@
mCallback = callback;
mCallbackExecutor = executor;
mDeathRecipient = () -> {
- mInstance.release();
+ mInstance.close();
};
mSessionBinder = null;
@@ -158,7 +159,10 @@
}
@Override
- public void release_impl() {
+ public void close_impl() {
+ if (DEBUG) {
+ Log.d(TAG, "relese from " + mToken);
+ }
final IMediaSession2 binder;
synchronized (mLock) {
if (mIsReleased) {
@@ -188,6 +192,18 @@
});
}
+ IMediaSession2 getSessionBinder() {
+ return mSessionBinder;
+ }
+
+ MediaSession2CallbackStub getControllerStub() {
+ return mSessionCallbackStub;
+ }
+
+ Executor getCallbackExecutor() {
+ return mCallbackExecutor;
+ }
+
@Override
public SessionToken getSessionToken_impl() {
return mToken;
@@ -335,7 +351,8 @@
private void onConnectionChangedNotLocked(IMediaSession2 sessionBinder,
CommandGroup commandGroup) {
if (DEBUG) {
- Log.d(TAG, "onConnectionChangedNotLocked sessionBinder=" + sessionBinder);
+ Log.d(TAG, "onConnectionChangedNotLocked sessionBinder=" + sessionBinder
+ + ", commands=" + commandGroup);
}
boolean release = false;
try {
@@ -361,6 +378,9 @@
// so can be used without worrying about deadlock.
mSessionBinder.asBinder().linkToDeath(mDeathRecipient, 0);
} catch (RemoteException e) {
+ if (DEBUG) {
+ Log.d(TAG, "Session died too early.", e);
+ }
release = true;
return;
}
@@ -377,12 +397,14 @@
if (release) {
// Trick to call release() without holding the lock, to prevent potential deadlock
// with the developer's custom lock within the ControllerCallback.onDisconnected().
- mInstance.release();
+ mInstance.close();
}
}
}
- private static class MediaSession2CallbackStub extends IMediaSession2Callback.Stub {
+ // TODO(jaewan): Pull out this from the controller2, and rename it to the MediaController2Stub
+ // or MediaBrowser2Stub.
+ static class MediaSession2CallbackStub extends IMediaSession2Callback.Stub {
private final WeakReference<MediaController2Impl> mController;
private MediaSession2CallbackStub(MediaController2Impl controller) {
@@ -397,6 +419,15 @@
return controller;
}
+ // TODO(jaewan): Refactor code to get rid of these pattern.
+ private MediaBrowser2Impl getBrowser() throws IllegalStateException {
+ final MediaController2Impl controller = getController();
+ if (controller instanceof MediaBrowser2Impl) {
+ return (MediaBrowser2Impl) controller;
+ }
+ return null;
+ }
+
public void destroy() {
mController.clear();
}
@@ -420,6 +451,50 @@
controller.onConnectionChangedNotLocked(
sessionBinder, CommandGroup.fromBundle(commandGroup));
}
+
+ @Override
+ public void onGetRootResult(Bundle rootHints, String rootMediaId, Bundle rootExtra)
+ throws RuntimeException {
+ final MediaBrowser2Impl browser;
+ try {
+ browser = getBrowser();
+ } catch (IllegalStateException e) {
+ Log.w(TAG, "Don't fail silently here. Highly likely a bug");
+ return;
+ }
+ if (browser == null) {
+ // TODO(jaewan): Revisit here. Could be a bug
+ return;
+ }
+ browser.onGetRootResult(rootHints, rootMediaId, rootExtra);
+ }
+
+ @Override
+ public void onCustomLayoutChanged(List<Bundle> commandButtonlist) {
+ if (commandButtonlist == null) {
+ // Illegal call. Ignore
+ return;
+ }
+ final MediaBrowser2Impl browser;
+ try {
+ browser = getBrowser();
+ } catch (IllegalStateException e) {
+ Log.w(TAG, "Don't fail silently here. Highly likely a bug");
+ return;
+ }
+ if (browser == null) {
+ // TODO(jaewan): Revisit here. Could be a bug
+ return;
+ }
+ List<CommandButton> layout = new ArrayList<>();
+ for (int i = 0; i < commandButtonlist.size(); i++) {
+ CommandButton button = CommandButton.fromBundle(commandButtonlist.get(i));
+ if (button != null) {
+ layout.add(button);
+ }
+ }
+ browser.onCustomLayoutChanged(layout);
+ }
}
// This will be called on the main thread.
@@ -455,7 +530,7 @@
// Permanent lose of the binding because of the service package update or removed.
// This SessionServiceRecord will be removed accordingly, but forget session binder here
// for sure.
- mInstance.release();
+ mInstance.close();
}
}
}
diff --git a/packages/MediaComponents/src/com/android/media/MediaLibraryService2Impl.java b/packages/MediaComponents/src/com/android/media/MediaLibraryService2Impl.java
new file mode 100644
index 0000000..430ab4c
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/media/MediaLibraryService2Impl.java
@@ -0,0 +1,54 @@
+/*
+ * 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.Intent;
+import android.media.MediaLibraryService2;
+import android.media.MediaLibraryService2.MediaLibrarySession;
+import android.media.MediaSession2;
+import android.media.MediaSessionService2;
+import android.media.update.MediaLibraryService2Provider;
+
+public class MediaLibraryService2Impl extends MediaSessionService2Impl implements
+ MediaLibraryService2Provider {
+ private final MediaSessionService2 mInstance;
+ private MediaLibrarySession mLibrarySession;
+
+ public MediaLibraryService2Impl(MediaLibraryService2 instance) {
+ super(instance);
+ mInstance = instance;
+ }
+
+ @Override
+ public void onCreate_impl() {
+ super.onCreate_impl();
+
+ // Effectively final
+ MediaSession2 session = getSession();
+ if (!(session instanceof MediaLibrarySession)) {
+ throw new RuntimeException("Expected MediaLibrarySession, but returned MediaSession2");
+ }
+ mLibrarySession = (MediaLibrarySession) getSession();
+ }
+
+ @Override
+ Intent createServiceIntent() {
+ Intent serviceIntent = new Intent(mInstance, mInstance.getClass());
+ serviceIntent.setAction(MediaLibraryService2.SERVICE_INTERFACE);
+ return serviceIntent;
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java b/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
index 09d7adc..8c276cd 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
@@ -20,11 +20,13 @@
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.media.AudioAttributes;
import android.media.IMediaSession2Callback;
import android.media.MediaController2;
import android.media.MediaPlayerBase;
import android.media.MediaSession2;
import android.media.MediaSession2.Builder;
+import android.media.MediaSession2.CommandButton;
import android.media.MediaSession2.ControllerInfo;
import android.media.MediaSession2.SessionCallback;
import android.media.SessionToken;
@@ -48,7 +50,6 @@
private final Context mContext;
private final String mId;
private final Handler mHandler;
- private final SessionCallback mCallback;
private final MediaSession2Stub mSessionStub;
private final SessionToken mSessionToken;
@@ -76,8 +77,7 @@
mContext = context;
mId = id;
mHandler = new Handler(Looper.myLooper());
- mCallback = callback;
- mSessionStub = new MediaSession2Stub(this);
+ mSessionStub = new MediaSession2Stub(this, callback);
// Ask server to create session token for following reasons.
// 1. Make session ID unique per package.
// Server can only know if the package has another process and has another session
@@ -105,46 +105,42 @@
@Override
public void setPlayer_impl(MediaPlayerBase player) throws IllegalArgumentException {
ensureCallingThread();
- // TODO(jaewan): Remove this when we don't inherits MediaPlayerBase.
- if (player instanceof MediaSession2 || player instanceof MediaController2) {
- throw new IllegalArgumentException("player doesn't accept MediaSession2 nor"
- + " MediaController2");
- }
- if (player != null && mPlayer == player) {
- // Player didn't changed. No-op.
- return;
+ if (player == null) {
+ throw new IllegalArgumentException("player shouldn't be null");
}
setPlayerInternal(player);
}
private void setPlayerInternal(MediaPlayerBase player) {
- mHandler.removeCallbacksAndMessages(null);
- if (mPlayer == null && player != null) {
- if (DEBUG) {
- Log.d(TAG, "session is ready to use, id=" + mId);
- }
- } else if (mPlayer != null && player == null) {
- if (DEBUG) {
- Log.d(TAG, "session is now unavailable, id=" + mId);
- }
- if (mSessionStub != null) {
- // Invalidate previously published session stub.
- mSessionStub.destroyNotLocked();
- }
+ if (mPlayer == player) {
+ // Player didn't changed. No-op.
+ return;
}
+ mHandler.removeCallbacksAndMessages(null);
if (mPlayer != null && mListener != null) {
// This might not work for a poorly implemented player.
mPlayer.removePlaybackListener(mListener);
}
- if (player != null) {
- mListener = new MyPlaybackListener(this, player);
- player.addPlaybackListener(mListener, mHandler);
- notifyPlaybackStateChanged(player.getPlaybackState());
- }
+ mListener = new MyPlaybackListener(this, player);
+ player.addPlaybackListener(mListener, mHandler);
+ notifyPlaybackStateChanged(player.getPlaybackState());
mPlayer = player;
}
@Override
+ public void close_impl() {
+ // Flush any pending messages.
+ mHandler.removeCallbacksAndMessages(null);
+ if (mSessionStub != null) {
+ if (DEBUG) {
+ Log.d(TAG, "session is now unavailable, id=" + mId);
+ }
+ // Invalidate previously published session stub.
+ mSessionStub.destroyNotLocked();
+ }
+ }
+
+ @Override
public MediaPlayerBase getPlayer_impl() {
return getPlayer();
}
@@ -161,6 +157,16 @@
}
@Override
+ public void setAudioAttributes_impl(AudioAttributes attributes) {
+ // implement
+ }
+
+ @Override
+ public void setAudioFocusRequest_impl(int focusGain) {
+ // implement
+ }
+
+ @Override
public void play_impl() {
ensureCallingThread();
ensurePlayer();
@@ -232,6 +238,18 @@
}
}
+ @Override
+ public void setCustomLayout_impl(ControllerInfo controller, List<CommandButton> layout) {
+ ensureCallingThread();
+ if (controller == null) {
+ throw new IllegalArgumentException("controller shouldn't be null");
+ }
+ if (layout == null) {
+ throw new IllegalArgumentException("layout shouldn't be null");
+ }
+ mSessionStub.notifyCustomLayoutNotLocked(controller, layout);
+ }
+
///////////////////////////////////////////////////
// Protected or private methods
///////////////////////////////////////////////////
@@ -284,10 +302,6 @@
return mInstance;
}
- SessionCallback getCallback() {
- return mCallback;
- }
-
MediaPlayerBase getPlayer() {
return mPlayer;
}
@@ -420,5 +434,9 @@
public void removeFlag(int flag) {
mFlag &= ~flag;
}
+
+ public static ControllerInfoImpl from(ControllerInfo controller) {
+ return (ControllerInfoImpl) controller.getProvider();
+ }
}
}
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java b/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
index bae02e5..f2772ed 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
@@ -21,10 +21,13 @@
import android.content.Context;
import android.media.IMediaSession2;
import android.media.IMediaSession2Callback;
+import android.media.MediaLibraryService2.MediaLibrarySessionCallback;
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.SessionCallback;
import android.media.session.PlaybackState;
import android.os.Binder;
import android.os.Bundle;
@@ -33,6 +36,7 @@
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
+import android.service.media.MediaBrowserService.BrowserRoot;
import android.support.annotation.GuardedBy;
import android.util.ArrayMap;
import android.util.Log;
@@ -41,9 +45,6 @@
import java.util.ArrayList;
import java.util.List;
-// TODO(jaewan): Add a hook for media apps to log which app requested specific command.
-// TODO(jaewan): Add a way to block specific command from a specific app. Also add supported
-// command per apps.
public class MediaSession2Stub extends IMediaSession2.Stub {
private static final String TAG = "MediaSession2Stub";
private static final boolean DEBUG = true; // TODO(jaewan): Rename.
@@ -52,14 +53,20 @@
private final CommandHandler mCommandHandler;
private final WeakReference<MediaSession2Impl> mSession;
private final Context mContext;
+ private final SessionCallback mSessionCallback;
+ private final MediaLibrarySessionCallback mLibraryCallback;
@GuardedBy("mLock")
private final ArrayMap<IBinder, ControllerInfo> mControllers = new ArrayMap<>();
- public MediaSession2Stub(MediaSession2Impl session) {
+ public MediaSession2Stub(MediaSession2Impl session, SessionCallback callback) {
mSession = new WeakReference<>(session);
mContext = session.getContext();
+ // TODO(jaewan): Should be executor from the session builder
mCommandHandler = new CommandHandler(session.getHandler().getLooper());
+ mSessionCallback = callback;
+ mLibraryCallback = (callback instanceof MediaLibrarySessionCallback)
+ ? (MediaLibrarySessionCallback) callback : null;
}
public void destroyNotLocked() {
@@ -124,6 +131,25 @@
mCommandHandler.postCommand(controller, Command.fromBundle(command), args);
}
+ @Override
+ public void getBrowserRoot(IMediaSession2Callback caller, Bundle rootHints)
+ throws RuntimeException {
+ if (mLibraryCallback == null) {
+ if (DEBUG) {
+ Log.d(TAG, "Session cannot hand getBrowserRoot()");
+ }
+ return;
+ }
+ final ControllerInfo controller = getController(caller);
+ if (controller == null) {
+ if (DEBUG) {
+ Log.d(TAG, "getBrowerRoot from a controller that hasn't connected. Ignore");
+ }
+ return;
+ }
+ mCommandHandler.postOnGetRoot(controller, rootHints);
+ }
+
@Deprecated
@Override
public PlaybackState getPlaybackState() throws RemoteException {
@@ -142,7 +168,7 @@
if (controllerInfo == null) {
return;
}
- ((ControllerInfoImpl) controllerInfo.getProvider()).addFlag(callbackFlag);
+ ControllerInfoImpl.from(controllerInfo).addFlag(callbackFlag);
}
}
@@ -156,9 +182,7 @@
if (controllerInfo == null) {
return;
}
- ControllerInfoImpl impl =
- ((ControllerInfoImpl) controllerInfo.getProvider());
- impl.removeFlag(callbackFlag);
+ ControllerInfoImpl.from(controllerInfo).removeFlag(callbackFlag);
}
}
@@ -183,7 +207,7 @@
synchronized (mLock) {
for (int i = 0; i < mControllers.size(); i++) {
ControllerInfo controllerInfo = mControllers.valueAt(i);
- if (((ControllerInfoImpl) controllerInfo.getProvider()).containsFlag(flag)) {
+ if (ControllerInfoImpl.from(controllerInfo).containsFlag(flag)) {
controllers.add(controllerInfo);
}
}
@@ -196,8 +220,7 @@
final List<ControllerInfo> list = getControllersWithFlag(CALLBACK_FLAG_PLAYBACK);
for (int i = 0; i < list.size(); i++) {
IMediaSession2Callback callbackBinder =
- ((ControllerInfoImpl) list.get(i).getProvider())
- .getControllerBinder();
+ ControllerInfoImpl.from(list.get(i)).getControllerBinder();
try {
callbackBinder.onPlaybackStateChanged(state);
} catch (RemoteException e) {
@@ -207,9 +230,31 @@
}
}
+ public void notifyCustomLayoutNotLocked(ControllerInfo controller, List<CommandButton> layout) {
+ // TODO(jaewan): It's OK to be called while it's connecting, but not OK if the connection
+ // is rejected. Handle the case.
+ IMediaSession2Callback callbackBinder =
+ ControllerInfoImpl.from(controller).getControllerBinder();
+ try {
+ List<Bundle> layoutBundles = new ArrayList<>();
+ for (int i = 0; i < layout.size(); i++) {
+ Bundle bundle = layout.get(i).toBundle();
+ if (bundle != null) {
+ layoutBundles.add(bundle);
+ }
+ }
+ callbackBinder.onCustomLayoutChanged(layoutBundles);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Controller is gone", e);
+ // TODO(jaewan): What to do when the controller is gone?
+ }
+ }
+
+ // TODO(jaewan): Remove this. We should use Executor given by the session builder.
private class CommandHandler extends Handler {
public static final int MSG_CONNECT = 1000;
public static final int MSG_COMMAND = 1001;
+ public static final int MSG_ON_GET_ROOT = 2000;
public CommandHandler(Looper looper) {
super(looper);
@@ -223,18 +268,22 @@
}
switch (msg.what) {
- case MSG_CONNECT:
+ case MSG_CONNECT: {
ControllerInfo request = (ControllerInfo) msg.obj;
- CommandGroup allowedCommands = session.getCallback().onConnect(request);
+ CommandGroup allowedCommands = mSessionCallback.onConnect(request);
// Don't reject connection for the request from trusted app.
// Otherwise server will fail to retrieve session's information to dispatch
// media keys to.
boolean accept = allowedCommands != null || request.isTrusted();
- ControllerInfoImpl impl = (ControllerInfoImpl) request.getProvider();
+ ControllerInfoImpl impl = ControllerInfoImpl.from(request);
if (accept) {
synchronized (mLock) {
mControllers.put(impl.getId(), request);
}
+ if (allowedCommands == null) {
+ // For trusted apps, send non-null allowed commands to keep connection.
+ allowedCommands = new CommandGroup();
+ }
}
if (DEBUG) {
Log.d(TAG, "onConnectResult, request=" + request
@@ -248,10 +297,11 @@
// Controller may be died prematurely.
}
break;
- case MSG_COMMAND:
+ }
+ case MSG_COMMAND: {
CommandParam param = (CommandParam) msg.obj;
Command command = param.command;
- boolean accepted = session.getCallback().onCommandRequest(
+ boolean accepted = mSessionCallback.onCommandRequest(
param.controller, command);
if (!accepted) {
// Don't run rejected command.
@@ -282,6 +332,21 @@
// TODO(jaewan): Handle custom command.
}
break;
+ }
+ case MSG_ON_GET_ROOT: {
+ final CommandParam param = (CommandParam) msg.obj;
+ final ControllerInfoImpl controller = ControllerInfoImpl.from(param.controller);
+ BrowserRoot root = mLibraryCallback.onGetRoot(param.controller, param.args);
+ try {
+ controller.getControllerBinder().onGetRootResult(param.args,
+ root == null ? null : root.getRootId(),
+ root == null ? null : root.getExtras());
+ } catch (RemoteException e) {
+ // Controller may be died prematurely.
+ // TODO(jaewan): Handle this.
+ }
+ break;
+ }
}
}
@@ -293,6 +358,11 @@
CommandParam param = new CommandParam(controller, command, args);
obtainMessage(MSG_COMMAND, param).sendToTarget();
}
+
+ public void postOnGetRoot(ControllerInfo controller, Bundle rootHints) {
+ CommandParam param = new CommandParam(controller, null, rootHints);
+ obtainMessage(MSG_ON_GET_ROOT, param).sendToTarget();
+ }
}
private static class CommandParam {
diff --git a/packages/MediaComponents/src/com/android/media/MediaSessionService2Impl.java b/packages/MediaComponents/src/com/android/media/MediaSessionService2Impl.java
index 6a4760d..773a06f 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSessionService2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSessionService2Impl.java
@@ -17,11 +17,7 @@
package com.android.media;
import static android.content.Context.NOTIFICATION_SERVICE;
-import static android.media.MediaSessionService2.DEFAULT_MEDIA_NOTIFICATION_CHANNEL_ID;
-import static android.media.MediaSessionService2.DEFAULT_MEDIA_NOTIFICATION_ID;
-import android.app.Notification;
-import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -37,7 +33,7 @@
import android.support.annotation.GuardedBy;
import android.util.Log;
-// Need a test for session service itself.
+// TODO(jaewan): Need a test for session service itself.
public class MediaSessionService2Impl implements MediaSessionService2Provider {
private static final String TAG = "MPSessionService"; // to meet 23 char limit in Log tag
@@ -53,7 +49,6 @@
private Intent mStartSelfIntent;
private boolean mIsRunningForeground;
- private NotificationChannel mDefaultNotificationChannel;
private MediaSession2 mSession;
public MediaSessionService2Impl(MediaSessionService2 instance) {
@@ -65,6 +60,10 @@
@Override
public MediaSession2 getSession_impl() {
+ return getSession();
+ }
+
+ MediaSession2 getSession() {
synchronized (mLock) {
return mSession;
}
@@ -72,43 +71,23 @@
@Override
public MediaNotification onUpdateNotification_impl(PlaybackState state) {
- return createDefaultNotification(state);
+ // Provide default notification UI later.
+ return null;
}
- // TODO(jaewan): Remove this for framework release.
- private MediaNotification createDefaultNotification(PlaybackState state) {
- // TODO(jaewan): Place better notification here.
- 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(
- mInstance, DEFAULT_MEDIA_NOTIFICATION_CHANNEL_ID)
- .setContentTitle(mInstance.getPackageName())
- .setContentText("Playback state: " + state.getState())
- .setSmallIcon(android.R.drawable.sym_def_app_icon).build();
- return MediaNotification.create(DEFAULT_MEDIA_NOTIFICATION_ID, notification);
- }
-
-
@Override
public void onCreate_impl() {
mNotificationManager = (NotificationManager) mInstance.getSystemService(
NOTIFICATION_SERVICE);
mStartSelfIntent = new Intent(mInstance, mInstance.getClass());
- Intent serviceIntent = new Intent(mInstance, mInstance.getClass());
- serviceIntent.setAction(MediaSessionService2.SERVICE_INTERFACE);
+ Intent serviceIntent = createServiceIntent();
ResolveInfo resolveInfo = mInstance.getPackageManager()
- .resolveService(serviceIntent,
- PackageManager.GET_META_DATA);
+ .resolveService(serviceIntent, PackageManager.GET_META_DATA);
String id;
if (resolveInfo == null || resolveInfo.serviceInfo == null) {
throw new IllegalArgumentException("service " + mInstance + " doesn't implement"
- + MediaSessionService2.SERVICE_INTERFACE);
+ + serviceIntent.getAction());
} else if (resolveInfo.serviceInfo.metaData == null) {
if (DEBUG) {
Log.d(TAG, "Failed to resolve ID for " + mInstance + ". Using empty id");
@@ -122,6 +101,14 @@
if (mSession == null || !id.equals(mSession.getToken().getId())) {
throw new RuntimeException("Expected session with id " + id + ", but got " + mSession);
}
+ // TODO(jaewan): Uncomment here.
+ // mSession.addPlaybackListener(mListener, mSession.getExecutor());
+ }
+
+ Intent createServiceIntent() {
+ Intent serviceIntent = new Intent(mInstance, mInstance.getClass());
+ serviceIntent.setAction(MediaSessionService2.SERVICE_INTERFACE);
+ return serviceIntent;
}
public IBinder onBind_impl(Intent intent) {
@@ -134,7 +121,7 @@
private void updateNotification(PlaybackState state) {
MediaNotification mediaNotification = mInstance.onUpdateNotification(state);
if (mediaNotification == null) {
- mediaNotification = createDefaultNotification(state);
+ return;
}
switch((int) state.getState()) {
case PlaybackState.STATE_PLAYING:
diff --git a/packages/MediaComponents/src/com/android/media/update/ApiFactory.java b/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
index cfef77f..07b565e 100644
--- a/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
+++ b/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
@@ -19,7 +19,10 @@
import android.content.Context;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
+import android.media.MediaBrowser2;
+import android.media.MediaBrowser2.BrowserCallback;
import android.media.MediaController2;
+import android.media.MediaLibraryService2;
import android.media.MediaPlayerBase;
import android.media.MediaSession2;
import android.media.MediaSession2.ControllerInfo;
@@ -27,6 +30,7 @@
import android.media.MediaSessionService2;
import android.media.IMediaSession2Callback;
import android.media.SessionToken;
+import android.media.update.MediaBrowser2Provider;
import android.media.update.MediaControlView2Provider;
import android.media.update.MediaController2Provider;
import android.media.update.MediaSession2Provider;
@@ -39,7 +43,9 @@
import android.widget.MediaControlView2;
import android.widget.VideoView2;
+import com.android.media.MediaBrowser2Impl;
import com.android.media.MediaController2Impl;
+import com.android.media.MediaLibraryService2Impl;
import com.android.media.MediaSession2Impl;
import com.android.media.MediaSessionService2Impl;
import com.android.widget.MediaControlView2Impl;
@@ -62,6 +68,12 @@
}
@Override
+ public MediaBrowser2Provider createMediaBrowser2(MediaBrowser2 instance, Context context,
+ SessionToken token, BrowserCallback callback, Executor executor) {
+ return new MediaBrowser2Impl(instance, context, token, callback, executor);
+ }
+
+ @Override
public MediaSession2Provider createMediaSession2(MediaSession2 instance, Context context,
MediaPlayerBase player, String id, SessionCallback callback) {
return new MediaSession2Impl(instance, context, player, id, callback);
@@ -82,6 +94,12 @@
}
@Override
+ public MediaSessionService2Provider createMediaLibraryService2(
+ MediaLibraryService2 instance) {
+ return new MediaLibraryService2Impl(instance);
+ }
+
+ @Override
public MediaControlView2Provider createMediaControlView2(
MediaControlView2 instance, ViewProvider superProvider) {
return new MediaControlView2Impl(instance, superProvider);
diff --git a/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java b/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
index a814d5e..303efb9 100644
--- a/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
+++ b/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright 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.
@@ -22,11 +22,12 @@
import android.media.session.PlaybackState;
import android.media.update.MediaControlView2Provider;
import android.media.update.ViewProvider;
+import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
-import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewGroup;
import android.view.accessibility.AccessibilityManager;
import android.widget.ImageButton;
import android.widget.MediaControlView2;
@@ -47,8 +48,14 @@
private final MediaControlView2 mInstance;
private final ViewProvider mSuperProvider;
- static final String ACTION_SHOW_SUBTITLE = "showSubtitle";
- static final String ACTION_HIDE_SUBTITLE = "hideSubtitle";
+ static final String COMMAND_SHOW_SUBTITLE = "showSubtitle";
+ static final String COMMAND_HIDE_SUBTITLE = "hideSubtitle";
+ static final String COMMAND_SET_FULLSCREEN = "setFullscreen";
+
+ static final String ARGUMENT_KEY_FULLSCREEN = "fullScreen";
+
+ static final String KEY_STATE_CONTAINS_SUBTITLE = "StateContainsSubtitle";
+ static final String EVENT_UPDATE_SUBTITLE_STATUS = "UpdateSubtitleStatus";
private static final int MAX_PROGRESS = 1000;
private static final int DEFAULT_PROGRESS_UPDATE_TIME_MS = 1000;
@@ -57,7 +64,6 @@
private static final int REWIND_TIME_MS = 10000;
private static final int FORWARD_TIME_MS = 30000;
- private final boolean mUseClosedCaption;
private final AccessibilityManager mAccessibilityManager;
private MediaController mController;
@@ -66,20 +72,36 @@
private MediaMetadata mMetadata;
private ProgressBar mProgress;
private TextView mEndTime, mCurrentTime;
+ private TextView mTitleView;
private int mDuration;
private int mPrevState;
+ private long mPlaybackActions;
private boolean mShowing;
private boolean mDragging;
- private boolean mListenersSet;
- private boolean mCCIsEnabled;
+ private boolean mIsFullScreen;
+ private boolean mOverflowExpanded;
private boolean mIsStopped;
+ private boolean mSubtitleIsEnabled;
+ private boolean mContainsSubtitle;
+ private boolean mSeekAvailable;
private View.OnClickListener mNextListener, mPrevListener;
private ImageButton mPlayPauseButton;
private ImageButton mFfwdButton;
private ImageButton mRewButton;
private ImageButton mNextButton;
private ImageButton mPrevButton;
- private ImageButton mCCButton;
+
+ private ViewGroup mBasicControls;
+ private ImageButton mSubtitleButton;
+ private ImageButton mFullScreenButton;
+ private ImageButton mOverflowButtonRight;
+
+ private ViewGroup mExtraControls;
+ private ImageButton mOverflowButtonLeft;
+ private ImageButton mMuteButton;
+ private ImageButton mAspectRationButton;
+ private ImageButton mSettingsButton;
+
private CharSequence mPlayDescription;
private CharSequence mPauseDescription;
private CharSequence mReplayDescription;
@@ -87,10 +109,10 @@
private StringBuilder mFormatBuilder;
private Formatter mFormatter;
- public MediaControlView2Impl(MediaControlView2 instance, ViewProvider superProvider) {
+ public MediaControlView2Impl(
+ MediaControlView2 instance, ViewProvider superProvider) {
mInstance = instance;
mSuperProvider = superProvider;
- mUseClosedCaption = true;
mAccessibilityManager = AccessibilityManager.getInstance(mInstance.getContext());
// Inflate MediaControlView2 from XML
@@ -108,6 +130,7 @@
mPlaybackState = mController.getPlaybackState();
mMetadata = mController.getMetadata();
updateDuration();
+ updateTitle();
mController.registerCallback(new MediaControllerCallback());
}
@@ -161,13 +184,6 @@
}
@Override
- public void showCCButton_impl() {
- if (mCCButton != null) {
- mCCButton.setVisibility(View.VISIBLE);
- }
- }
-
- @Override
public boolean isPlaying_impl() {
if (mPlaybackState != null) {
return mPlaybackState.getState() == PlaybackState.STATE_PLAYING;
@@ -206,7 +222,7 @@
@Override
public boolean canSeekBackward_impl() {
- if (mPlaybackState != null) {
+ if (mPlaybackState!= null) {
return (mPlaybackState.getActions() & PlaybackState.ACTION_REWIND) != 0;
}
return true;
@@ -222,18 +238,103 @@
@Override
public void showSubtitle_impl() {
- mController.sendCommand(ACTION_SHOW_SUBTITLE, null, null);
+ mController.sendCommand(COMMAND_SHOW_SUBTITLE, null, null);
}
@Override
public void hideSubtitle_impl() {
- mController.sendCommand(ACTION_HIDE_SUBTITLE, null, null);
+ mController.sendCommand(COMMAND_HIDE_SUBTITLE, null, null);
+ }
+
+ @Override
+ public void setPrevNextListeners_impl(View.OnClickListener next, View.OnClickListener prev) {
+ mNextListener = next;
+ mPrevListener = prev;
+
+ if (mNextButton != null) {
+ mNextButton.setOnClickListener(mNextListener);
+ mNextButton.setEnabled(mNextListener != null);
+ mNextButton.setVisibility(View.VISIBLE);
+ }
+ if (mPrevButton != null) {
+ mPrevButton.setOnClickListener(mPrevListener);
+ mPrevButton.setEnabled(mPrevListener != null);
+ mPrevButton.setVisibility(View.VISIBLE);
+ }
+ }
+
+ @Override
+ public void setButtonVisibility_impl(int button, boolean visible) {
+ switch (button) {
+ case MediaControlView2.BUTTON_PLAY_PAUSE:
+ if (mPlayPauseButton != null && mInstance.canPause()) {
+ mPlayPauseButton.setVisibility((visible) ? View.VISIBLE : View.GONE);
+ }
+ break;
+ case MediaControlView2.BUTTON_FFWD:
+ if (mFfwdButton != null && mInstance.canSeekForward()) {
+ mFfwdButton.setVisibility((visible) ? View.VISIBLE : View.GONE);
+ }
+ break;
+ case MediaControlView2.BUTTON_REW:
+ if (mRewButton != null && mInstance.canSeekBackward()) {
+ mRewButton.setVisibility((visible) ? View.VISIBLE : View.GONE);
+ }
+ 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((visible) ? View.VISIBLE : View.GONE);
+ }
+ 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((visible) ? View.VISIBLE : View.GONE);
+ }
+ break;
+ case MediaControlView2.BUTTON_SUBTITLE:
+ if (mSubtitleButton != null && mContainsSubtitle) {
+ mSubtitleButton.setVisibility((visible) ? View.VISIBLE : View.GONE);
+ }
+ break;
+ case MediaControlView2.BUTTON_FULL_SCREEN:
+ if (mFullScreenButton != null) {
+ mFullScreenButton.setVisibility((visible) ? View.VISIBLE : View.GONE);
+ }
+ break;
+ case MediaControlView2.BUTTON_OVERFLOW:
+ if (mOverflowButtonRight != null) {
+ mOverflowButtonRight.setVisibility((visible) ? View.VISIBLE : View.GONE);
+ }
+ break;
+ case MediaControlView2.BUTTON_MUTE:
+ if (mMuteButton != null) {
+ mMuteButton.setVisibility((visible) ? View.VISIBLE : View.GONE);
+ }
+ break;
+ case MediaControlView2.BUTTON_ASPECT_RATIO:
+ if (mAspectRationButton != null) {
+ mAspectRationButton.setVisibility((visible) ? View.VISIBLE : View.GONE);
+ }
+ break;
+ case MediaControlView2.BUTTON_SETTINGS:
+ if (mSettingsButton != null) {
+ mSettingsButton.setVisibility((visible) ? View.VISIBLE : View.GONE);
+ }
+ break;
+ default:
+ break;
+ }
}
@Override
public void onAttachedToWindow_impl() {
mSuperProvider.onAttachedToWindow_impl();
}
+
@Override
public void onDetachedFromWindow_impl() {
mSuperProvider.onDetachedFromWindow_impl();
@@ -324,10 +425,10 @@
mRewButton.setEnabled(enabled);
}
if (mNextButton != null) {
- mNextButton.setEnabled(enabled && mNextListener != null);
+ mNextButton.setEnabled(enabled);
}
if (mPrevButton != null) {
- mPrevButton.setEnabled(enabled && mPrevListener != null);
+ mPrevButton.setEnabled(enabled);
}
if (mProgress != null) {
mProgress.setEnabled(enabled);
@@ -358,42 +459,63 @@
mPlayDescription = res.getText(R.string.lockscreen_play_button_content_description);
mPauseDescription = res.getText(R.string.lockscreen_pause_button_content_description);
mReplayDescription = res.getText(R.string.lockscreen_replay_button_content_description);
+
mPlayPauseButton = v.findViewById(R.id.pause);
if (mPlayPauseButton != null) {
mPlayPauseButton.requestFocus();
mPlayPauseButton.setOnClickListener(mPlayPauseListener);
+ mPlayPauseButton.setColorFilter(R.integer.gray);
+ mPlayPauseButton.setEnabled(false);
}
-
- // TODO: make the following buttons visible based upon whether they are supported for
- // individual media files
mFfwdButton = v.findViewById(R.id.ffwd);
if (mFfwdButton != null) {
mFfwdButton.setOnClickListener(mFfwdListener);
- mFfwdButton.setVisibility(View.GONE);
+ mFfwdButton.setColorFilter(R.integer.gray);
+ mFfwdButton.setEnabled(false);
}
mRewButton = v.findViewById(R.id.rew);
if (mRewButton != null) {
mRewButton.setOnClickListener(mRewListener);
- mRewButton.setVisibility(View.GONE);
+ mRewButton.setColorFilter(R.integer.gray);
+ mRewButton.setEnabled(false);
}
mNextButton = v.findViewById(R.id.next);
- if (mNextButton != null && !mListenersSet) {
+ if (mNextButton != null) {
mNextButton.setVisibility(View.GONE);
}
mPrevButton = v.findViewById(R.id.prev);
- if (mPrevButton != null && !mListenersSet) {
+ if (mPrevButton != null) {
mPrevButton.setVisibility(View.GONE);
}
- // TODO: make CC button visible if the media file has a subtitle track
- mCCButton = v.findViewById(R.id.cc);
- if (mCCButton != null) {
- mCCButton.setOnClickListener(mCCListener);
- mCCButton.setVisibility(mUseClosedCaption ? View.VISIBLE : View.GONE);
+ mBasicControls = v.findViewById(R.id.basic_controls);
+ mSubtitleButton = v.findViewById(R.id.subtitle);
+ if (mSubtitleButton != null) {
+ mSubtitleButton.setOnClickListener(mSubtitleListener);
+ mSubtitleButton.setColorFilter(R.integer.gray);
+ mSubtitleButton.setEnabled(false);
+ }
+ mFullScreenButton = v.findViewById(R.id.fullscreen);
+ if (mFullScreenButton != null) {
+ mFullScreenButton.setOnClickListener(mFullScreenListener);
+ // TODO: Show Fullscreen button when only it is possible.
+ }
+ mOverflowButtonRight = v.findViewById(R.id.overflow_right);
+ if (mOverflowButtonRight != null) {
+ mOverflowButtonRight.setOnClickListener(mOverflowRightListener);
}
- mProgress =
- v.findViewById(R.id.mediacontroller_progress);
+ // TODO: should these buttons be shown as default?
+ mExtraControls = v.findViewById(R.id.extra_controls);
+ mOverflowButtonLeft = v.findViewById(R.id.overflow_left);
+ if (mOverflowButtonLeft != null) {
+ mOverflowButtonLeft.setOnClickListener(mOverflowLeftListener);
+ }
+ mMuteButton = v.findViewById(R.id.mute);
+ mAspectRationButton = v.findViewById(R.id.aspect_ratio);
+ mSettingsButton = v.findViewById(R.id.settings);
+
+ mProgress = v.findViewById(R.id.mediacontroller_progress);
if (mProgress != null) {
if (mProgress instanceof SeekBar) {
SeekBar seeker = (SeekBar) mProgress;
@@ -402,12 +524,12 @@
mProgress.setMax(MAX_PROGRESS);
}
+ mTitleView = v.findViewById(R.id.title_text);
+
mEndTime = v.findViewById(R.id.time);
mCurrentTime = v.findViewById(R.id.time_current);
mFormatBuilder = new StringBuilder();
mFormatter = new Formatter(mFormatBuilder, Locale.getDefault());
-
- installPrevNextListeners();
}
/**
@@ -446,7 +568,9 @@
private final Runnable mFadeOut = new Runnable() {
@Override
public void run() {
- mInstance.hide();
+ if (mInstance.isPlaying()) {
+ mInstance.hide();
+ }
}
};
@@ -504,11 +628,15 @@
private void togglePausePlayState() {
if (mInstance.isPlaying()) {
mControls.pause();
- mPlayPauseButton.setImageResource(R.drawable.ic_play_circle_filled);
+ mPlayPauseButton.setImageDrawable(
+ ApiHelper.getLibResources().getDrawable(
+ R.drawable.ic_play_circle_filled, null));
mPlayPauseButton.setContentDescription(mPlayDescription);
} else {
mControls.play();
- mPlayPauseButton.setImageResource(R.drawable.ic_pause_circle_filled);
+ mPlayPauseButton.setImageDrawable(
+ ApiHelper.getLibResources().getDrawable(
+ R.drawable.ic_pause_circle_filled, null));
mPlayPauseButton.setContentDescription(mPauseDescription);
}
}
@@ -527,6 +655,9 @@
private final OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() {
@Override
public void onStartTrackingTouch(SeekBar bar) {
+ if (!mSeekAvailable) {
+ return;
+ }
mInstance.show(3600000);
mDragging = true;
@@ -538,10 +669,12 @@
// this ensures that there will be exactly one message queued up.
mInstance.removeCallbacks(mShowProgress);
- // Check if playback is currently stopped. In this case, update the pause button to show
- // the play image instead of the replay image.
+ // Check if playback is currently stopped. In this case, update the pause button to
+ // show the play image instead of the replay image.
if (mIsStopped) {
- mPlayPauseButton.setImageResource(R.drawable.ic_play_circle_filled);
+ mPlayPauseButton.setImageDrawable(
+ ApiHelper.getLibResources().getDrawable(
+ R.drawable.ic_play_circle_filled, null));
mPlayPauseButton.setContentDescription(mPlayDescription);
mIsStopped = false;
}
@@ -549,6 +682,9 @@
@Override
public void onProgressChanged(SeekBar bar, int progress, boolean fromUser) {
+ if (!mSeekAvailable) {
+ return;
+ }
if (!fromUser) {
// We're not interested in programmatically generated changes to
// the progress bar's position.
@@ -566,6 +702,9 @@
@Override
public void onStopTrackingTouch(SeekBar bar) {
+ if (!mSeekAvailable) {
+ return;
+ }
mDragging = false;
setProgress();
@@ -608,32 +747,64 @@
}
};
- private final View.OnClickListener mCCListener = new View.OnClickListener() {
+ private final View.OnClickListener mSubtitleListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
- if (!mCCIsEnabled) {
- mCCButton.setImageResource(R.drawable.ic_media_cc_enabled);
- mCCIsEnabled = true;
+ if (!mSubtitleIsEnabled) {
+ mSubtitleButton.setImageDrawable(
+ ApiHelper.getLibResources().getDrawable(
+ R.drawable.ic_media_subtitle_enabled, null));
mInstance.showSubtitle();
+ mSubtitleIsEnabled = true;
} else {
- mCCButton.setImageResource(R.drawable.ic_media_cc_disabled);
- mCCIsEnabled = false;
+ mSubtitleButton.setImageDrawable(
+ ApiHelper.getLibResources().getDrawable(
+ R.drawable.ic_media_subtitle_disabled, null));
mInstance.hideSubtitle();
+ mSubtitleIsEnabled = false;
}
+ mInstance.show(DEFAULT_TIMEOUT_MS);
}
};
- private void installPrevNextListeners() {
- if (mNextButton != null) {
- mNextButton.setOnClickListener(mNextListener);
- mNextButton.setEnabled(mNextListener != null);
- }
+ private final View.OnClickListener mFullScreenListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ final boolean isEnteringFullScreen = !mIsFullScreen;
+ // TODO: Re-arrange the button layouts according to the UX.
+ if (isEnteringFullScreen) {
+ mFullScreenButton.setImageDrawable(
+ ApiHelper.getLibResources().getDrawable(
+ R.drawable.ic_fullscreen_exit, null));
+ } else {
+ mFullScreenButton.setImageDrawable(
+ ApiHelper.getLibResources().getDrawable(R.drawable.ic_fullscreen, null));
+ }
+ Bundle args = new Bundle();
+ args.putBoolean(ARGUMENT_KEY_FULLSCREEN, isEnteringFullScreen);
+ mController.sendCommand(COMMAND_SET_FULLSCREEN, args, null);
- if (mPrevButton != null) {
- mPrevButton.setOnClickListener(mPrevListener);
- mPrevButton.setEnabled(mPrevListener != null);
+ mIsFullScreen = isEnteringFullScreen;
+ mInstance.show(DEFAULT_TIMEOUT_MS);
}
- }
+ };
+
+ private final View.OnClickListener mOverflowRightListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mBasicControls.setVisibility(View.GONE);
+ mExtraControls.setVisibility(View.VISIBLE);
+ mInstance.show(DEFAULT_TIMEOUT_MS);
+ }
+ };
+
+ private final View.OnClickListener mOverflowLeftListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mBasicControls.setVisibility(View.VISIBLE);
+ mExtraControls.setVisibility(View.GONE);
+ }
+ };
private void updateDuration() {
if (mMetadata != null) {
@@ -645,6 +816,14 @@
}
}
+ private void updateTitle() {
+ if (mMetadata != null) {
+ if (mMetadata.containsKey(MediaMetadata.METADATA_KEY_TITLE)) {
+ mTitleView.setText(mMetadata.getString(MediaMetadata.METADATA_KEY_TITLE));
+ }
+ }
+ }
+
private class MediaControllerCallback extends MediaController.Callback {
@Override
public void onPlaybackStateChanged(PlaybackState state) {
@@ -657,15 +836,21 @@
if (mPlaybackState.getState() != mPrevState) {
switch (mPlaybackState.getState()) {
case PlaybackState.STATE_PLAYING:
- mPlayPauseButton.setImageResource(R.drawable.ic_pause_circle_filled);
+ mPlayPauseButton.setImageDrawable(
+ ApiHelper.getLibResources().getDrawable(
+ R.drawable.ic_pause_circle_filled, null));
mPlayPauseButton.setContentDescription(mPauseDescription);
break;
case PlaybackState.STATE_PAUSED:
- mPlayPauseButton.setImageResource(R.drawable.ic_play_circle_filled);
+ mPlayPauseButton.setImageDrawable(
+ ApiHelper.getLibResources().getDrawable(
+ R.drawable.ic_play_circle_filled, null));
mPlayPauseButton.setContentDescription(mPlayDescription);
break;
case PlaybackState.STATE_STOPPED:
- mPlayPauseButton.setImageResource(R.drawable.ic_replay);
+ mPlayPauseButton.setImageDrawable(
+ ApiHelper.getLibResources().getDrawable(
+ R.drawable.ic_replay, null));
mPlayPauseButton.setContentDescription(mReplayDescription);
mIsStopped = true;
break;
@@ -674,12 +859,52 @@
}
mPrevState = mPlaybackState.getState();
}
+
+ if (mPlaybackActions != mPlaybackState.getActions()) {
+ long newActions = mPlaybackState.getActions();
+ if ((newActions & PlaybackState.ACTION_PAUSE) != 0) {
+ mPlayPauseButton.clearColorFilter();
+ mPlayPauseButton.setEnabled(true);
+ }
+ if ((newActions & PlaybackState.ACTION_REWIND) != 0) {
+ mRewButton.clearColorFilter();
+ mRewButton.setEnabled(true);
+ }
+ if ((newActions & PlaybackState.ACTION_FAST_FORWARD) != 0) {
+ mFfwdButton.clearColorFilter();
+ mFfwdButton.setEnabled(true);
+ }
+ if ((newActions & PlaybackState.ACTION_SEEK_TO) != 0) {
+ mSeekAvailable = true;
+ } else {
+ mSeekAvailable = false;
+ }
+ mPlaybackActions = newActions;
+ }
}
@Override
public void onMetadataChanged(MediaMetadata metadata) {
mMetadata = metadata;
updateDuration();
+ updateTitle();
+ }
+
+ @Override
+ public void onSessionEvent(String event, Bundle extras) {
+ if (event.equals(EVENT_UPDATE_SUBTITLE_STATUS)) {
+ boolean newSubtitleStatus = extras.getBoolean(KEY_STATE_CONTAINS_SUBTITLE);
+ if (newSubtitleStatus != mContainsSubtitle) {
+ if (newSubtitleStatus) {
+ mSubtitleButton.clearColorFilter();
+ mSubtitleButton.setEnabled(true);
+ } else {
+ mSubtitleButton.setColorFilter(R.integer.gray);
+ mSubtitleButton.setEnabled(false);
+ }
+ mContainsSubtitle = newSubtitleStatus;
+ }
+ }
}
}
-}
\ No newline at end of file
+}
diff --git a/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java b/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
index 57534f1..4c312f8 100644
--- a/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
+++ b/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
@@ -25,15 +25,18 @@
import android.media.AudioManager;
import android.media.MediaMetadata;
import android.media.MediaPlayer;
+import android.media.MediaPlayerBase;
import android.media.Cea708CaptionRenderer;
import android.media.ClosedCaptionRenderer;
+import android.media.MediaMetadata;
+import android.media.MediaPlayer;
import android.media.Metadata;
import android.media.PlaybackParams;
import android.media.SubtitleController;
-import android.media.session.MediaSession;
-import android.media.session.PlaybackState;
import android.media.TtmlRenderer;
import android.media.WebVttRenderer;
+import android.media.session.MediaSession;
+import android.media.session.PlaybackState;
import android.media.update.VideoView2Provider;
import android.media.update.ViewProvider;
import android.net.Uri;
@@ -50,14 +53,14 @@
import android.widget.MediaControlView2;
import android.widget.VideoView2;
+import com.android.media.update.ApiHelper;
+import com.android.media.update.R;
+
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
-import com.android.media.update.ApiHelper;
-import com.android.media.update.R;
-
public class VideoView2Impl implements VideoView2Provider, VideoViewInterface.SurfaceListener {
private static final String TAG = "VideoView2";
private static final boolean DEBUG = true; // STOPSHIP: Log.isLoggable(TAG, Log.DEBUG);
@@ -83,6 +86,7 @@
private VideoView2.OnErrorListener mOnErrorListener;
private VideoView2.OnInfoListener mOnInfoListener;
private VideoView2.OnViewTypeChangedListener mOnViewTypeChangedListener;
+ private VideoView2.OnFullScreenChangedListener mOnFullScreenChangedListener;
private VideoViewInterface mCurrentView;
private VideoTextureView mTextureView;
@@ -91,6 +95,8 @@
private MediaPlayer mMediaPlayer;
private MediaControlView2 mMediaControlView;
private MediaSession mMediaSession;
+ private Metadata mMetadata;
+ private String mTitle;
private PlaybackState.Builder mStateBuilder;
private int mTargetState = STATE_IDLE;
@@ -110,8 +116,7 @@
// Refer: https://docs.google.com/document/d/1nzAfns6i2hJ3RkaUre3QMT6wsDedJ5ONLiA_OOBFFX8/edit
private float mFallbackSpeed; // keep the original speed before 'pause' is called.
- public VideoView2Impl(
- VideoView2 instance, ViewProvider superProvider,
+ public VideoView2Impl(VideoView2 instance, ViewProvider superProvider,
@Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
mInstance = instance;
mSuperProvider = superProvider;
@@ -285,6 +290,13 @@
}
@Override
+ public void setFullScreen_impl(boolean fullScreen) {
+ if (mOnFullScreenChangedListener != null) {
+ mOnFullScreenChangedListener.onFullScreenChanged(fullScreen);
+ }
+ }
+
+ @Override
public void setSpeed_impl(float speed) {
if (speed <= 0.0f) {
Log.e(TAG, "Unsupported speed (" + speed + ") is ignored.");
@@ -331,6 +343,11 @@
}
@Override
+ public void setRouteAttributes_impl(List<String> routeCategories, MediaPlayerBase player) {
+ // TODO: implement this.
+ }
+
+ @Override
public void setVideoPath_impl(String path) {
mInstance.setVideoURI(Uri.parse(path));
}
@@ -402,6 +419,11 @@
}
@Override
+ public void setFullScreenChangedListener_impl(VideoView2.OnFullScreenChangedListener l) {
+ mOnFullScreenChangedListener = l;
+ }
+
+ @Override
public void onAttachedToWindow_impl() {
mSuperProvider.onAttachedToWindow_impl();
@@ -419,7 +441,6 @@
mMediaSession = null;
}
-
@Override
public CharSequence getAccessibilityClassName_impl() {
return VideoView2.class.getName();
@@ -559,9 +580,6 @@
///////////////////////////////////////////////////
private void attachMediaControlView() {
- // TODO: change this so that the CC button appears only where there is a subtitle track.
- // mMediaControlView.showCCButton();
-
// Get MediaController from MediaSession and set it inside MediaControlView
mMediaControlView.setController(mMediaSession.getController());
@@ -632,6 +650,13 @@
mCurrentState = STATE_PREPARING;
mMediaPlayer.prepareAsync();
+ // Save file name as title since the file may not have a title Metadata.
+ mTitle = uri.getPath();
+ String scheme = uri.getScheme();
+ if (scheme != null && scheme.equals("file")) {
+ mTitle = uri.getLastPathSegment();
+ }
+
if (DEBUG) {
Log.d(TAG, "openVideo(). mCurrentState=" + mCurrentState
+ ", mTargetState=" + mTargetState);
@@ -680,26 +705,26 @@
private void updatePlaybackState() {
if (mStateBuilder == null) {
// Get the capabilities of the player for this stream
- Metadata data = mMediaPlayer.getMetadata(MediaPlayer.METADATA_ALL,
+ mMetadata = mMediaPlayer.getMetadata(MediaPlayer.METADATA_ALL,
MediaPlayer.BYPASS_METADATA_FILTER);
// Add Play action as default
long playbackActions = PlaybackState.ACTION_PLAY;
- if (data != null) {
- if (!data.has(Metadata.PAUSE_AVAILABLE)
- || data.getBoolean(Metadata.PAUSE_AVAILABLE)) {
+ if (mMetadata != null) {
+ if (!mMetadata.has(Metadata.PAUSE_AVAILABLE)
+ || mMetadata.getBoolean(Metadata.PAUSE_AVAILABLE)) {
playbackActions |= PlaybackState.ACTION_PAUSE;
}
- if (!data.has(Metadata.SEEK_BACKWARD_AVAILABLE)
- || data.getBoolean(Metadata.SEEK_BACKWARD_AVAILABLE)) {
+ if (!mMetadata.has(Metadata.SEEK_BACKWARD_AVAILABLE)
+ || mMetadata.getBoolean(Metadata.SEEK_BACKWARD_AVAILABLE)) {
playbackActions |= PlaybackState.ACTION_REWIND;
}
- if (!data.has(Metadata.SEEK_FORWARD_AVAILABLE)
- || data.getBoolean(Metadata.SEEK_FORWARD_AVAILABLE)) {
+ if (!mMetadata.has(Metadata.SEEK_FORWARD_AVAILABLE)
+ || mMetadata.getBoolean(Metadata.SEEK_FORWARD_AVAILABLE)) {
playbackActions |= PlaybackState.ACTION_FAST_FORWARD;
}
- if (!data.has(Metadata.SEEK_AVAILABLE)
- || data.getBoolean(Metadata.SEEK_AVAILABLE)) {
+ if (!mMetadata.has(Metadata.SEEK_AVAILABLE)
+ || mMetadata.getBoolean(Metadata.SEEK_AVAILABLE)) {
playbackActions |= PlaybackState.ACTION_SEEK_TO;
}
} else {
@@ -709,8 +734,8 @@
}
mStateBuilder = new PlaybackState.Builder();
mStateBuilder.setActions(playbackActions);
- mStateBuilder.addCustomAction(MediaControlView2Impl.ACTION_SHOW_SUBTITLE, null, -1);
- mStateBuilder.addCustomAction(MediaControlView2Impl.ACTION_HIDE_SUBTITLE, null, -1);
+ mStateBuilder.addCustomAction(MediaControlView2Impl.COMMAND_SHOW_SUBTITLE, null, -1);
+ mStateBuilder.addCustomAction(MediaControlView2Impl.COMMAND_HIDE_SUBTITLE, null, -1);
}
mStateBuilder.setState(getCorrespondingPlaybackState(),
mInstance.getCurrentPosition(), 1.0f);
@@ -733,7 +758,7 @@
case STATE_PREPARING:
return PlaybackState.STATE_CONNECTING;
case STATE_PREPARED:
- return PlaybackState.STATE_STOPPED;
+ return PlaybackState.STATE_PAUSED;
case STATE_PLAYING:
return PlaybackState.STATE_PLAYING;
case STATE_PAUSED:
@@ -817,16 +842,6 @@
mInstance.seekTo(seekToPosition);
}
- // Create and set playback state for MediaControlView2
- updatePlaybackState();
-
- // Get and set duration value as MediaMetadata for MediaControlView2
- MediaMetadata.Builder builder = new MediaMetadata.Builder();
- builder.putLong(MediaMetadata.METADATA_KEY_DURATION, mInstance.getDuration());
- if (mMediaSession != null) {
- mMediaSession.setMetadata(builder.build());
- }
-
if (videoWidth != 0 && videoHeight != 0) {
if (videoWidth != mVideoWidth || videoHeight != mVideoHeight) {
if (DEBUG) {
@@ -842,6 +857,7 @@
mVideoHeight = videoHeight;
mInstance.requestLayout();
}
+
if (needToStart()) {
mInstance.start();
if (mMediaControlView != null) {
@@ -862,6 +878,20 @@
mInstance.start();
}
}
+ // Create and set playback state for MediaControlView2
+ updatePlaybackState();
+
+ // 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, mInstance.getDuration());
+
+ if (mMediaSession != null) {
+ mMediaSession.setMetadata(builder.build());
+ }
}
};
@@ -872,9 +902,6 @@
mTargetState = STATE_PLAYBACK_COMPLETED;
updatePlaybackState();
- if (mMediaControlView != null) {
- mMediaControlView.hide();
- }
if (mOnCompletionListener != null) {
mOnCompletionListener.onCompletion();
}
@@ -936,7 +963,7 @@
.setPositiveButton(res.getString(R.string.VideoView2_error_button),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
- int whichButton) {
+ int whichButton) {
/* If we get here, there is no onError listener, so
* at least inform them that the video is over.
*/
@@ -964,12 +991,16 @@
@Override
public void onCommand(String command, Bundle args, ResultReceiver receiver) {
switch (command) {
- case MediaControlView2Impl.ACTION_SHOW_SUBTITLE:
+ case MediaControlView2Impl.COMMAND_SHOW_SUBTITLE:
mInstance.showSubtitle();
break;
- case MediaControlView2Impl.ACTION_HIDE_SUBTITLE:
+ case MediaControlView2Impl.COMMAND_HIDE_SUBTITLE:
mInstance.hideSubtitle();
break;
+ case MediaControlView2Impl.COMMAND_SET_FULLSCREEN:
+ mInstance.setFullScreen(
+ args.getBoolean(MediaControlView2Impl.ARGUMENT_KEY_FULLSCREEN));
+ break;
}
}
diff --git a/packages/MediaComponents/test/AndroidManifest.xml b/packages/MediaComponents/test/AndroidManifest.xml
index fe16583..30bac87 100644
--- a/packages/MediaComponents/test/AndroidManifest.xml
+++ b/packages/MediaComponents/test/AndroidManifest.xml
@@ -32,10 +32,17 @@
<!-- Keep the test services synced together with the TestUtils.java -->
<service android:name="android.media.MockMediaSessionService2">
<intent-filter>
- <action android:name="android.media.session.MediaSessionService2" />
+ <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="TestBrowser" />
+ </service>
</application>
<instrumentation
diff --git a/packages/MediaComponents/test/src/android/media/MediaBrowser2Test.java b/packages/MediaComponents/test/src/android/media/MediaBrowser2Test.java
new file mode 100644
index 0000000..fe8aeb9
--- /dev/null
+++ b/packages/MediaComponents/test/src/android/media/MediaBrowser2Test.java
@@ -0,0 +1,141 @@
+/*
+ * 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.assertTrue;
+
+import android.content.Context;
+import android.media.MediaBrowser2.BrowserCallback;
+import android.media.MediaSession2.CommandGroup;
+import android.os.Bundle;
+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.Test;
+import org.junit.runner.RunWith;
+
+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 SessionToken token,
+ @NonNull TestControllerCallbackInterface callback) {
+ return new TestMediaBrowser(mContext, token, new TestBrowserCallback(callback));
+ }
+
+ @Test
+ public void testGetBrowserRoot() throws InterruptedException {
+ final Bundle param = new Bundle();
+ param.putString(TAG, TAG);
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ final TestControllerCallbackInterface callback = new TestControllerCallbackInterface() {
+ @Override
+ public void onGetRootResult(Bundle rootHints, String rootMediaId, Bundle rootExtra) {
+ assertTrue(TestUtils.equals(param, rootHints));
+ assertEquals(MockMediaLibraryService2.ROOT_ID, rootMediaId);
+ assertTrue(TestUtils.equals(MockMediaLibraryService2.EXTRA, rootExtra));
+ latch.countDown();
+ }
+ };
+
+ final SessionToken token = MockMediaLibraryService2.getToken(mContext);
+ MediaBrowser2 browser =
+ (MediaBrowser2) createController(token,true, callback);
+ browser.getBrowserRoot(param);
+ assertTrue(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) {
+ mCallbackProxy = callbackProxy;
+ }
+
+ @CallSuper
+ @Override
+ public void onConnected(CommandGroup commands) {
+ super.onConnected(commands);
+ connectLatch.countDown();
+ }
+
+ @CallSuper
+ @Override
+ public void onDisconnected() {
+ super.onDisconnected();
+ disconnectLatch.countDown();
+ }
+
+ @Override
+ public void onGetRootResult(Bundle rootHints, String rootMediaId, Bundle rootExtra) {
+ mCallbackProxy.onGetRootResult(rootHints, rootMediaId, rootExtra);
+ }
+
+ @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 SessionToken token,
+ @NonNull ControllerCallback callback) {
+ super(context, token, (BrowserCallback) callback, sHandlerExecutor);
+ mCallback = (BrowserCallback) callback;
+ }
+
+ @Override
+ public BrowserCallback getCallback() {
+ return mCallback;
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/MediaComponents/test/src/android/media/MediaController2Test.java b/packages/MediaComponents/test/src/android/media/MediaController2Test.java
index 38d34cc..ac0bd73 100644
--- a/packages/MediaComponents/test/src/android/media/MediaController2Test.java
+++ b/packages/MediaComponents/test/src/android/media/MediaController2Test.java
@@ -51,9 +51,9 @@
public class MediaController2Test extends MediaSession2TestBase {
private static final String TAG = "MediaController2Test";
- private MediaSession2 mSession;
- private MediaController2Wrapper mController;
- private MockPlayer mPlayer;
+ MediaSession2 mSession;
+ MediaController2 mController;
+ MockPlayer mPlayer;
@Before
@Override
@@ -75,7 +75,7 @@
super.cleanUp();
sHandler.postAndSync(() -> {
if (mSession != null) {
- mSession.setPlayer(null);
+ mSession.close();
}
});
TestServiceRegistry.getInstance().cleanUp();
@@ -210,35 +210,36 @@
}
};
sHandler.postAndSync(() -> {
- mSession.setPlayer(null);
+ mSession.close();
mSession = new MediaSession2.Builder(mContext, mPlayer)
.setSessionCallback(sessionCallback).build();
});
- MediaController2Wrapper controller = createController(mSession.getToken(), false, null);
+ MediaController2 controller =
+ createController(mSession.getToken(), false, null);
assertNotNull(controller);
- controller.waitForConnect(false);
- controller.waitForDisconnect(true);
+ waitForConnect(controller, false);
+ waitForDisconnect(controller, true);
}
@Test
public void testControllerCallback_releaseSession() throws InterruptedException {
sHandler.postAndSync(() -> {
- mSession.setPlayer(null);
+ mSession.close();
});
- mController.waitForDisconnect(true);
+ waitForDisconnect(mController, true);
}
@Test
public void testControllerCallback_release() throws InterruptedException {
- mController.release();
- mController.waitForDisconnect(true);
+ mController.close();
+ waitForDisconnect(mController, true);
}
@Test
public void testIsConnected() throws InterruptedException {
assertTrue(mController.isConnected());
sHandler.postAndSync(()->{
- mSession.setPlayer(null);
+ mSession.close();
});
// postAndSync() to wait until the disconnection is propagated.
sHandler.postAndSync(()->{
@@ -252,7 +253,7 @@
@Test
public void testDeadlock() throws InterruptedException {
sHandler.postAndSync(() -> {
- mSession.setPlayer(null);
+ mSession.close();
mSession = null;
});
@@ -300,7 +301,7 @@
if (mSession != null) {
sessionHandler.postAndSync(() -> {
// Clean up here because sessionHandler will be removed afterwards.
- mSession.setPlayer(null);
+ mSession.close();
mSession = null;
});
}
@@ -330,11 +331,23 @@
mPlayer = (MockPlayer) mSession.getPlayer();
}
+ // TODO(jaewan): Reenable when session manager detects app installs
@Ignore
@Test
- public void testConnectToService() throws InterruptedException {
+ public void testConnectToService_sessionService() throws InterruptedException {
connectToService(TestUtils.getServiceToken(mContext, MockMediaSessionService2.ID));
+ testConnectToService();
+ }
+ // TODO(jaewan): Reenable when session manager detects app installs
+ @Ignore
+ @Test
+ public void testConnectToService_libraryService() throws InterruptedException {
+ connectToService(TestUtils.getServiceToken(mContext, MockMediaLibraryService2.ID));
+ testConnectToService();
+ }
+
+ public void testConnectToService() throws InterruptedException {
TestServiceRegistry serviceInfo = TestServiceRegistry.getInstance();
ControllerInfo info = serviceInfo.getOnConnectControllerInfo();
assertEquals(mContext.getPackageName(), info.getPackageName());
@@ -371,34 +384,48 @@
}
@Test
- public void testRelease_beforeConnected() throws InterruptedException {
+ public void testClose_beforeConnected() throws InterruptedException {
MediaController2 controller =
createController(mSession.getToken(), false, null);
- controller.release();
+ controller.close();
}
@Test
- public void testRelease_twice() throws InterruptedException {
- mController.release();
- mController.release();
+ public void testClose_twice() throws InterruptedException {
+ mController.close();
+ mController.close();
}
@Test
- public void testRelease_session() throws InterruptedException {
+ public void testClose_session() throws InterruptedException {
final String id = mSession.getToken().getId();
- mController.release();
- // Release is done immediately for session.
+ mController.close();
+ // close is done immediately for session.
testNoInteraction();
- // Test whether the controller is notified about later release of the session or
+ // Test whether the controller is notified about later close of the session or
// re-creation.
testControllerAfterSessionIsGone(id);
}
+ // TODO(jaewan): Reenable when session manager detects app installs
@Ignore
@Test
- public void testRelease_sessionService() throws InterruptedException {
+ public void testClose_sessionService() throws InterruptedException {
connectToService(TestUtils.getServiceToken(mContext, MockMediaSessionService2.ID));
+ testCloseFromService();
+ }
+
+ // TODO(jaewan): Reenable when session manager detects app installs
+ @Ignore
+ @Test
+ public void testClose_libraryService() throws InterruptedException {
+ connectToService(TestUtils.getServiceToken(mContext, MockMediaSessionService2.ID));
+ testCloseFromService();
+ }
+
+ private void testCloseFromService() throws InterruptedException {
+ final String id = mController.getSessionToken().getId();
final CountDownLatch latch = new CountDownLatch(1);
TestServiceRegistry.getInstance().setServiceInstanceChangedCallback((service) -> {
if (service == null) {
@@ -406,23 +433,23 @@
latch.countDown();
}
});
- mController.release();
- // Wait until release triggers onDestroy() of the session service.
+ 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 release of the session or
+ // Test whether the controller is notified about later close of the session or
// re-creation.
- testControllerAfterSessionIsGone(MockMediaSessionService2.ID);
+ testControllerAfterSessionIsGone(id);
}
private void testControllerAfterSessionIsGone(final String id) throws InterruptedException {
sHandler.postAndSync(() -> {
- // TODO(jaewan): Use Session.release later when we add the API.
- mSession.setPlayer(null);
+ // TODO(jaewan): Use Session.close later when we add the API.
+ mSession.close();
});
- mController.waitForDisconnect(true);
+ waitForDisconnect(mController, true);
testNoInteraction();
// Test with the newly created session.
@@ -434,11 +461,10 @@
testNoInteraction();
}
-
private void testNoInteraction() throws InterruptedException {
final CountDownLatch latch = new CountDownLatch(1);
final PlaybackListener playbackListener = (state) -> {
- fail("Controller shouldn't be notified about change in session after the release.");
+ fail("Controller shouldn't be notified about change in session after the close.");
latch.countDown();
};
mController.addPlaybackListener(playbackListener, sHandler);
diff --git a/packages/MediaComponents/test/src/android/media/MediaSession2Test.java b/packages/MediaComponents/test/src/android/media/MediaSession2Test.java
index f7224ea..c5408e8 100644
--- a/packages/MediaComponents/test/src/android/media/MediaSession2Test.java
+++ b/packages/MediaComponents/test/src/android/media/MediaSession2Test.java
@@ -65,7 +65,7 @@
public void cleanUp() throws Exception {
super.cleanUp();
sHandler.postAndSync(() -> {
- mSession.setPlayer(null);
+ mSession.close();
});
}
@@ -93,7 +93,7 @@
// Test if setPlayer doesn't crash with various situations.
mSession.setPlayer(mPlayer);
mSession.setPlayer(player);
- mSession.setPlayer(null);
+ mSession.close();
});
}
@@ -199,7 +199,7 @@
public void testOnCommandCallback() throws InterruptedException {
final MockOnCommandCallback callback = new MockOnCommandCallback();
sHandler.postAndSync(() -> {
- mSession.setPlayer(null);
+ mSession.close();
mPlayer = new MockPlayer(1);
mSession = new MediaSession2.Builder(mContext, mPlayer)
.setSessionCallback(callback).build();
@@ -224,14 +224,15 @@
public void testOnConnectCallback() throws InterruptedException {
final MockOnConnectCallback sessionCallback = new MockOnConnectCallback();
sHandler.postAndSync(() -> {
- mSession.setPlayer(null);
+ mSession.close();
mSession = new MediaSession2.Builder(mContext, mPlayer)
.setSessionCallback(sessionCallback).build();
});
- MediaController2Wrapper controller = createController(mSession.getToken(), false, null);
+ MediaController2 controller =
+ createController(mSession.getToken(), false, null);
assertNotNull(controller);
- controller.waitForConnect(false);
- controller.waitForDisconnect(true);
+ waitForConnect(controller, false);
+ waitForDisconnect(controller, true);
}
public class MockOnConnectCallback extends SessionCallback {
diff --git a/packages/MediaComponents/test/src/android/media/MediaSession2TestBase.java b/packages/MediaComponents/test/src/android/media/MediaSession2TestBase.java
index ab842c4..2965c82 100644
--- a/packages/MediaComponents/test/src/android/media/MediaSession2TestBase.java
+++ b/packages/MediaComponents/test/src/android/media/MediaSession2TestBase.java
@@ -20,7 +20,9 @@
import static junit.framework.Assert.assertTrue;
import android.content.Context;
+import android.media.MediaController2.ControllerCallback;
import android.media.MediaSession2.CommandGroup;
+import android.os.Bundle;
import android.os.HandlerThread;
import android.support.annotation.CallSuper;
import android.support.annotation.NonNull;
@@ -50,6 +52,22 @@
Context mContext;
private List<MediaController2> mControllers = new ArrayList<>();
+ interface TestControllerInterface {
+ ControllerCallback getCallback();
+ }
+
+ interface TestControllerCallbackInterface {
+ // Currently empty. Add methods in ControllerCallback/BrowserCallback that you want to test.
+
+ // Browser specific callbacks
+ default void onGetRootResult(Bundle rootHints, String rootMediaId, Bundle rootExtra) {}
+ }
+
+ interface WaitForConnectionInterface {
+ void waitForConnect(boolean expect) throws InterruptedException;
+ void waitForDisconnect(boolean expect) throws InterruptedException;
+ }
+
@BeforeClass
public static void setUpThread() {
if (sHandler == null) {
@@ -79,32 +97,69 @@
@CallSuper
public void cleanUp() throws Exception {
for (int i = 0; i < mControllers.size(); i++) {
- mControllers.get(i).release();
+ mControllers.get(i).close();
}
}
- MediaController2Wrapper createController(SessionToken token) throws InterruptedException {
+ final MediaController2 createController(SessionToken token) throws InterruptedException {
return createController(token, true, null);
}
- MediaController2Wrapper createController(@NonNull SessionToken token, boolean waitForConnect,
- @Nullable TestControllerCallback callback)
+ final MediaController2 createController(@NonNull SessionToken token,
+ boolean waitForConnect, @Nullable TestControllerCallbackInterface callback)
throws InterruptedException {
- if (callback == null) {
- callback = new TestControllerCallback();
+ TestControllerInterface instance = onCreateController(token, callback);
+ if (!(instance instanceof MediaController2)) {
+ throw new RuntimeException("Test has a bug. Expected MediaController2 but returned "
+ + instance);
}
- MediaController2Wrapper controller = new MediaController2Wrapper(mContext, token, callback);
+ MediaController2 controller = (MediaController2) instance;
mControllers.add(controller);
if (waitForConnect) {
- controller.waitForConnect(true);
+ waitForConnect(controller, true);
}
return controller;
}
- public static class TestControllerCallback extends MediaController2.ControllerCallback {
+ 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 SessionToken token,
+ @NonNull TestControllerCallbackInterface callback) {
+ 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(TestControllerCallbackInterface callbackProxy) {
+ mCallbackProxy = callbackProxy;
+ }
+
@CallSuper
@Override
public void onConnected(CommandGroup commands) {
@@ -118,31 +173,38 @@
super.onDisconnected();
disconnectLatch.countDown();
}
+
+ @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 MediaController2Wrapper extends MediaController2 {
- private final TestControllerCallback mCallback;
+ public class TestMediaController extends MediaController2 implements TestControllerInterface {
+ private final ControllerCallback mCallback;
- public MediaController2Wrapper(@NonNull Context context, @NonNull SessionToken token,
- @NonNull TestControllerCallback callback) {
+ public TestMediaController(@NonNull Context context, @NonNull SessionToken token,
+ @NonNull ControllerCallback callback) {
super(context, token, callback, sHandlerExecutor);
mCallback = callback;
}
- public void waitForConnect(boolean expect) throws InterruptedException {
- if (expect) {
- assertTrue(mCallback.connectLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
- } else {
- assertFalse(mCallback.connectLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
- }
- }
-
- public void waitForDisconnect(boolean expect) throws InterruptedException {
- if (expect) {
- assertTrue(mCallback.disconnectLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
- } else {
- assertFalse(mCallback.disconnectLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
- }
+ @Override
+ public ControllerCallback getCallback() {
+ return mCallback;
}
}
}
diff --git a/packages/MediaComponents/test/src/android/media/MediaSessionManager_MediaSession2.java b/packages/MediaComponents/test/src/android/media/MediaSessionManager_MediaSession2.java
index 0ee37b1..bfed7d0 100644
--- a/packages/MediaComponents/test/src/android/media/MediaSessionManager_MediaSession2.java
+++ b/packages/MediaComponents/test/src/android/media/MediaSessionManager_MediaSession2.java
@@ -71,7 +71,7 @@
super.cleanUp();
sHandler.removeCallbacksAndMessages(null);
sHandler.postAndSync(() -> {
- mSession.setPlayer(null);
+ mSession.close();
});
}
@@ -111,7 +111,7 @@
@Test
public void testGetSessionTokens_sessionRejected() throws InterruptedException {
sHandler.postAndSync(() -> {
- mSession.setPlayer(null);
+ mSession.close();
mSession = new MediaSession2.Builder(mContext, new MockPlayer(0)).setId(TAG)
.setSessionCallback(new SessionCallback() {
@Override
@@ -141,7 +141,7 @@
public void testGetMediaSession2Tokens_playerRemoved() throws InterruptedException {
// Release
sHandler.postAndSync(() -> {
- mSession.setPlayer(null);
+ mSession.close();
});
ensureChangeInSession();
@@ -158,6 +158,7 @@
@Test
public void testGetMediaSessionService2Token() throws InterruptedException {
boolean foundTestSessionService = false;
+ boolean foundTestLibraryService = false;
List<SessionToken> tokens = mManager.getSessionServiceTokens();
for (int i = 0; i < tokens.size(); i++) {
SessionToken token = tokens.get(i);
@@ -167,15 +168,23 @@
assertEquals(SessionToken.TYPE_SESSION_SERVICE, token.getType());
assertNull(token.getSessionBinder());
foundTestSessionService = true;
+ } else if (mContext.getPackageName().equals(token.getPackageName())
+ && MockMediaLibraryService2.ID.equals(token.getId())) {
+ assertFalse(foundTestLibraryService);
+ assertEquals(SessionToken.TYPE_LIBRARY_SERVICE, token.getType());
+ assertNull(token.getSessionBinder());
+ foundTestLibraryService = true;
}
}
assertTrue(foundTestSessionService);
+ assertTrue(foundTestLibraryService);
}
@Test
public void testGetAllSessionTokens() throws InterruptedException {
boolean foundTestSession = false;
boolean foundTestSessionService = false;
+ boolean foundTestLibraryService = false;
List<SessionToken> tokens = mManager.getAllSessionTokens();
for (int i = 0; i < tokens.size(); i++) {
SessionToken token = tokens.get(i);
@@ -192,12 +201,18 @@
foundTestSessionService = true;
assertEquals(SessionToken.TYPE_SESSION_SERVICE, token.getType());
break;
+ case MockMediaLibraryService2.ID:
+ assertFalse(foundTestLibraryService);
+ assertEquals(SessionToken.TYPE_LIBRARY_SERVICE, token.getType());
+ foundTestLibraryService = true;
+ break;
default:
fail("Unexpected session " + token + " exists in the package");
}
}
assertTrue(foundTestSession);
assertTrue(foundTestSessionService);
+ assertTrue(foundTestLibraryService);
}
// Ensures if the session creation/release is notified to the server.
diff --git a/packages/MediaComponents/test/src/android/media/MockMediaLibraryService2.java b/packages/MediaComponents/test/src/android/media/MockMediaLibraryService2.java
new file mode 100644
index 0000000..7a16127
--- /dev/null
+++ b/packages/MediaComponents/test/src/android/media/MockMediaLibraryService2.java
@@ -0,0 +1,99 @@
+/*
+* 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.content.Context;
+import android.media.MediaSession2.CommandGroup;
+import android.media.MediaSession2.ControllerInfo;
+import android.media.TestUtils.SyncHandler;
+import android.os.Bundle;
+import android.os.Process;
+import android.service.media.MediaBrowserService.BrowserRoot;
+
+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 EXTRA = new Bundle();
+ static {
+ EXTRA.putString(ROOT_ID, ROOT_ID);
+ }
+ @GuardedBy("MockMediaLibraryService2.class")
+ private static SessionToken sToken;
+
+ private MediaLibrarySession mSession;
+
+ @Override
+ public MediaLibrarySession onCreateSession(String sessionId) {
+ final MockPlayer player = new MockPlayer(1);
+ SyncHandler handler = (SyncHandler) TestServiceRegistry.getInstance().getHandler();
+ try {
+ handler.postAndSync(() -> {
+ TestLibrarySessionCallback callback = new TestLibrarySessionCallback();
+ mSession = new MediaLibrarySessionBuilder(
+ MockMediaLibraryService2.this, player, callback)
+ .setId(sessionId).build();
+ });
+ } catch (InterruptedException e) {
+ fail(e.toString());
+ }
+ return mSession;
+ }
+
+ @Override
+ public void onDestroy() {
+ TestServiceRegistry.getInstance().cleanUp();
+ super.onDestroy();
+ }
+
+ public static SessionToken getToken(Context context) {
+ synchronized (MockMediaLibraryService2.class) {
+ if (sToken == null) {
+ sToken = new SessionToken(SessionToken.TYPE_LIBRARY_SERVICE,
+ context.getPackageName(), ID,
+ MockMediaLibraryService2.class.getName(), null);
+ }
+ return sToken;
+ }
+ }
+
+ private class TestLibrarySessionCallback extends MediaLibrarySessionCallback {
+ @Override
+ public CommandGroup onConnect(ControllerInfo controller) {
+ if (Process.myUid() != controller.getUid()) {
+ // It's system app wants to listen changes. Ignore.
+ return super.onConnect(controller);
+ }
+ TestServiceRegistry.getInstance().setServiceInstance(
+ MockMediaLibraryService2.this, controller);
+ return super.onConnect(controller);
+ }
+
+ @Override
+ public BrowserRoot onGetRoot(ControllerInfo controller, Bundle rootHints) {
+ return new BrowserRoot(ROOT_ID, EXTRA);
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/MediaComponents/test/src/android/media/MockMediaSessionService2.java b/packages/MediaComponents/test/src/android/media/MockMediaSessionService2.java
index e4a7485..9cf4911 100644
--- a/packages/MediaComponents/test/src/android/media/MockMediaSessionService2.java
+++ b/packages/MediaComponents/test/src/android/media/MockMediaSessionService2.java
@@ -18,9 +18,14 @@
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.ControllerInfo;
import android.media.MediaSession2.SessionCallback;
import android.media.TestUtils.SyncHandler;
+import android.media.session.PlaybackState;
import android.os.Process;
/**
@@ -29,7 +34,13 @@
public class MockMediaSessionService2 extends MediaSessionService2 {
// Keep in sync with the AndroidManifest.xml
public static final String ID = "TestSession";
- public MediaSession2 mSession;
+
+ 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 MediaSession2 onCreateSession(String sessionId) {
@@ -49,6 +60,7 @@
@Override
public void onCreate() {
super.onCreate();
+ mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
}
@Override
@@ -57,6 +69,23 @@
super.onDestroy();
}
+ @Override
+ public MediaNotification onUpdateNotification(PlaybackState state) {
+ 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("Playback state: " + state.getState())
+ .setSmallIcon(android.R.drawable.sym_def_app_icon).build();
+ return MediaNotification.create(DEFAULT_MEDIA_NOTIFICATION_ID, notification);
+ }
+
private class MySessionCallback extends SessionCallback {
@Override
public MediaSession2.CommandGroup onConnect(ControllerInfo controller) {
diff --git a/packages/MediaComponents/test/src/android/media/TestServiceRegistry.java b/packages/MediaComponents/test/src/android/media/TestServiceRegistry.java
index 378a6c4..6f5512e 100644
--- a/packages/MediaComponents/test/src/android/media/TestServiceRegistry.java
+++ b/packages/MediaComponents/test/src/android/media/TestServiceRegistry.java
@@ -106,17 +106,17 @@
if (mService != null) {
try {
if (mHandler.getLooper() == Looper.myLooper()) {
- mService.getSession().setPlayer(null);
+ mService.getSession().close();
} else {
mHandler.postAndSync(() -> {
- mService.getSession().setPlayer(null);
+ mService.getSession().close();
});
}
} catch (InterruptedException e) {
// No-op. Service containing session will die, but shouldn't be a huge issue.
}
// stopSelf() would not kill service while the binder connection established by
- // bindService() exists, and setPlayer(null) above will do the job instead.
+ // bindService() exists, and close() above will do the job instead.
// So stopSelf() isn't really needed, but just for sure.
mService.stopSelf();
mService = null;
diff --git a/packages/MediaComponents/test/src/android/media/TestUtils.java b/packages/MediaComponents/test/src/android/media/TestUtils.java
index 0cca12c..1372f01 100644
--- a/packages/MediaComponents/test/src/android/media/TestUtils.java
+++ b/packages/MediaComponents/test/src/android/media/TestUtils.java
@@ -19,11 +19,13 @@
import android.content.Context;
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;
@@ -47,6 +49,14 @@
return new PlaybackState.Builder().setState(state, 0, 1.0f).build();
}
+ /**
+ * Finds the session with id in this test package.
+ *
+ * @param context
+ * @param id
+ * @return
+ */
+ // TODO(jaewan): Currently not working.
public static SessionToken getServiceToken(Context context, String id) {
MediaSessionManager manager =
(MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);
@@ -63,6 +73,33 @@
}
/**
+ * 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;
+ }
+
+ /**
* Handler that always waits until the Runnable finishes.
*/
public static class SyncHandler extends Handler {
diff --git a/services/audiopolicy/common/managerdefinitions/include/AudioOutputDescriptor.h b/services/audiopolicy/common/managerdefinitions/include/AudioOutputDescriptor.h
index 2803ec1..cd2174d 100644
--- a/services/audiopolicy/common/managerdefinitions/include/AudioOutputDescriptor.h
+++ b/services/audiopolicy/common/managerdefinitions/include/AudioOutputDescriptor.h
@@ -187,6 +187,15 @@
bool isStreamActiveRemotely(audio_stream_type_t stream, uint32_t inPastMs = 0) const;
/**
+ * return whether a stream is playing, but not on a "remote" device.
+ * Override to change the definition of a local/remote playback.
+ * Used for instance by policy manager to alter the speaker playback ("speaker safe" behavior)
+ * when media plays or not locally.
+ * For the base implementation, "remotely" means playing during screen mirroring.
+ */
+ bool isStreamActiveLocally(audio_stream_type_t stream, uint32_t inPastMs = 0) const;
+
+ /**
* returns the A2DP output handle if it is open or 0 otherwise
*/
audio_io_handle_t getA2dpOutput() const;
diff --git a/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp b/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp
index d5e8e1b..17fc272 100644
--- a/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp
+++ b/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp
@@ -579,6 +579,19 @@
return false;
}
+bool SwAudioOutputCollection::isStreamActiveLocally(audio_stream_type_t stream, uint32_t inPastMs) const
+{
+ nsecs_t sysTime = systemTime();
+ for (size_t i = 0; i < this->size(); i++) {
+ const sp<SwAudioOutputDescriptor> outputDesc = this->valueAt(i);
+ if (outputDesc->isStreamActive(stream, inPastMs, sysTime)
+ && ((outputDesc->device() & APM_AUDIO_OUT_DEVICE_REMOTE_ALL) == 0)) {
+ return true;
+ }
+ }
+ return false;
+}
+
bool SwAudioOutputCollection::isStreamActiveRemotely(audio_stream_type_t stream,
uint32_t inPastMs) const
{
diff --git a/services/audiopolicy/enginedefault/src/Engine.cpp b/services/audiopolicy/enginedefault/src/Engine.cpp
index 9bdb98c..5ec0475 100644
--- a/services/audiopolicy/enginedefault/src/Engine.cpp
+++ b/services/audiopolicy/enginedefault/src/Engine.cpp
@@ -238,18 +238,19 @@
const SwAudioOutputCollection &outputs = mApmObserver->getOutputs();
return getDeviceForStrategyInt(strategy, availableOutputDevices,
- availableInputDevices, outputs);
+ availableInputDevices, outputs, (uint32_t)AUDIO_DEVICE_NONE);
}
-
audio_devices_t Engine::getDeviceForStrategyInt(routing_strategy strategy,
- DeviceVector availableOutputDevices,
- DeviceVector availableInputDevices,
- const SwAudioOutputCollection &outputs) const
+ DeviceVector availableOutputDevices,
+ DeviceVector availableInputDevices,
+ const SwAudioOutputCollection &outputs,
+ uint32_t outputDeviceTypesToIgnore) const
{
uint32_t device = AUDIO_DEVICE_NONE;
- uint32_t availableOutputDevicesType = availableOutputDevices.types();
+ uint32_t availableOutputDevicesType =
+ availableOutputDevices.types() & ~outputDeviceTypesToIgnore;
switch (strategy) {
@@ -260,38 +261,24 @@
case STRATEGY_SONIFICATION_RESPECTFUL:
if (isInCall()) {
device = getDeviceForStrategyInt(
- STRATEGY_SONIFICATION, availableOutputDevices, availableInputDevices, outputs);
- } else if (outputs.isStreamActiveRemotely(AUDIO_STREAM_MUSIC,
- SONIFICATION_RESPECTFUL_AFTER_MUSIC_DELAY)) {
- // while media is playing on a remote device, use the the sonification behavior.
- // Note that we test this usecase before testing if media is playing because
- // the isStreamActive() method only informs about the activity of a stream, not
- // if it's for local playback. Note also that we use the same delay between both tests
- device = getDeviceForStrategyInt(
- STRATEGY_SONIFICATION, availableOutputDevices, availableInputDevices, outputs);
- //user "safe" speaker if available instead of normal speaker to avoid triggering
- //other acoustic safety mechanisms for notification
- if ((device & AUDIO_DEVICE_OUT_SPEAKER) &&
- (availableOutputDevicesType & AUDIO_DEVICE_OUT_SPEAKER_SAFE)) {
- device |= AUDIO_DEVICE_OUT_SPEAKER_SAFE;
- device &= ~AUDIO_DEVICE_OUT_SPEAKER;
- }
- } else if (outputs.isStreamActive(
- AUDIO_STREAM_MUSIC, SONIFICATION_RESPECTFUL_AFTER_MUSIC_DELAY)
- || outputs.isStreamActive(
- AUDIO_STREAM_ACCESSIBILITY, SONIFICATION_RESPECTFUL_AFTER_MUSIC_DELAY))
- {
- // while media/a11y is playing (or has recently played), use the same device
- device = getDeviceForStrategyInt(
- STRATEGY_MEDIA, availableOutputDevices, availableInputDevices, outputs);
+ STRATEGY_SONIFICATION, availableOutputDevices, availableInputDevices, outputs,
+ outputDeviceTypesToIgnore);
} else {
- // when media is not playing anymore, fall back on the sonification behavior
- device = getDeviceForStrategyInt(
- STRATEGY_SONIFICATION, availableOutputDevices, availableInputDevices, outputs);
- //user "safe" speaker if available instead of normal speaker to avoid triggering
- //other acoustic safety mechanisms for notification
- if ((device & AUDIO_DEVICE_OUT_SPEAKER) &&
- (availableOutputDevicesType & AUDIO_DEVICE_OUT_SPEAKER_SAFE)) {
+ bool media_active_locally =
+ outputs.isStreamActiveLocally(
+ AUDIO_STREAM_MUSIC, SONIFICATION_RESPECTFUL_AFTER_MUSIC_DELAY)
+ || outputs.isStreamActiveLocally(
+ AUDIO_STREAM_ACCESSIBILITY, SONIFICATION_RESPECTFUL_AFTER_MUSIC_DELAY);
+ // routing is same as media without the "remote" device
+ device = getDeviceForStrategyInt(STRATEGY_MEDIA,
+ availableOutputDevices,
+ availableInputDevices, outputs,
+ AUDIO_DEVICE_OUT_REMOTE_SUBMIX | outputDeviceTypesToIgnore);
+ // if no media is playing on the device, check for mandatory use of "safe" speaker
+ // when media would have played on speaker, and the safe speaker path is available
+ if (!media_active_locally
+ && (device & AUDIO_DEVICE_OUT_SPEAKER)
+ && (availableOutputDevicesType & AUDIO_DEVICE_OUT_SPEAKER_SAFE)) {
device |= AUDIO_DEVICE_OUT_SPEAKER_SAFE;
device &= ~AUDIO_DEVICE_OUT_SPEAKER;
}
@@ -302,7 +289,8 @@
if (!isInCall()) {
// when off call, DTMF strategy follows the same rules as MEDIA strategy
device = getDeviceForStrategyInt(
- STRATEGY_MEDIA, availableOutputDevices, availableInputDevices, outputs);
+ STRATEGY_MEDIA, availableOutputDevices, availableInputDevices, outputs,
+ outputDeviceTypesToIgnore);
break;
}
// when in call, DTMF and PHONE strategies follow the same rules
@@ -408,7 +396,8 @@
// handleIncallSonification().
if (isInCall()) {
device = getDeviceForStrategyInt(
- STRATEGY_PHONE, availableOutputDevices, availableInputDevices, outputs);
+ STRATEGY_PHONE, availableOutputDevices, availableInputDevices, outputs,
+ outputDeviceTypesToIgnore);
break;
}
// FALL THROUGH
@@ -463,11 +452,13 @@
if (outputs.isStreamActive(AUDIO_STREAM_RING) ||
outputs.isStreamActive(AUDIO_STREAM_ALARM)) {
return getDeviceForStrategyInt(
- STRATEGY_SONIFICATION, availableOutputDevices, availableInputDevices, outputs);
+ STRATEGY_SONIFICATION, availableOutputDevices, availableInputDevices, outputs,
+ outputDeviceTypesToIgnore);
}
if (isInCall()) {
return getDeviceForStrategyInt(
- STRATEGY_PHONE, availableOutputDevices, availableInputDevices, outputs);
+ STRATEGY_PHONE, availableOutputDevices, availableInputDevices, outputs,
+ outputDeviceTypesToIgnore);
}
}
// For other cases, STRATEGY_ACCESSIBILITY behaves like STRATEGY_MEDIA
@@ -486,7 +477,8 @@
}
if (isInCall() && (strategy == STRATEGY_MEDIA)) {
device = getDeviceForStrategyInt(
- STRATEGY_PHONE, availableOutputDevices, availableInputDevices, outputs);
+ STRATEGY_PHONE, availableOutputDevices, availableInputDevices, outputs,
+ outputDeviceTypesToIgnore);
break;
}
if ((device2 == AUDIO_DEVICE_NONE) &&
diff --git a/services/audiopolicy/enginedefault/src/Engine.h b/services/audiopolicy/enginedefault/src/Engine.h
index 57538c4..06186c1 100644
--- a/services/audiopolicy/enginedefault/src/Engine.h
+++ b/services/audiopolicy/enginedefault/src/Engine.h
@@ -126,9 +126,10 @@
routing_strategy getStrategyForUsage(audio_usage_t usage);
audio_devices_t getDeviceForStrategy(routing_strategy strategy) const;
audio_devices_t getDeviceForStrategyInt(routing_strategy strategy,
- DeviceVector availableOutputDevices,
- DeviceVector availableInputDevices,
- const SwAudioOutputCollection &outputs) const;
+ DeviceVector availableOutputDevices,
+ DeviceVector availableInputDevices,
+ const SwAudioOutputCollection &outputs,
+ uint32_t outputDeviceTypesToIgnore) const;
audio_devices_t getDeviceForInputSource(audio_source_t inputSource) const;
audio_mode_t mPhoneState; /**< current phone state. */
diff --git a/services/camera/libcameraservice/device3/Camera3Device.cpp b/services/camera/libcameraservice/device3/Camera3Device.cpp
index 7e71c3b..999e258 100644
--- a/services/camera/libcameraservice/device3/Camera3Device.cpp
+++ b/services/camera/libcameraservice/device3/Camera3Device.cpp
@@ -55,8 +55,6 @@
#include "device3/Camera3SharedOutputStream.h"
#include "CameraService.h"
-#include <android/hardware/camera/device/3.4/ICameraDeviceSession.h>
-
using namespace android::camera3;
using namespace android::hardware::camera;
using namespace android::hardware::camera::device::V3_2;
@@ -669,13 +667,15 @@
}
if (dumpTemplates) {
- const char *templateNames[] = {
+ const char *templateNames[CAMERA3_TEMPLATE_COUNT] = {
"TEMPLATE_PREVIEW",
"TEMPLATE_STILL_CAPTURE",
"TEMPLATE_VIDEO_RECORD",
"TEMPLATE_VIDEO_SNAPSHOT",
"TEMPLATE_ZERO_SHUTTER_LAG",
- "TEMPLATE_MANUAL"
+ "TEMPLATE_MANUAL",
+ "TEMPLATE_MOTION_TRACKING_PREVIEW",
+ "TEMPALTE_MOTION_TRACKING_BEST"
};
for (int i = 1; i < CAMERA3_TEMPLATE_COUNT; i++) {
@@ -1668,10 +1668,16 @@
mStatusChanged.broadcast();
}
+void Camera3Device::pauseStateNotify(bool enable) {
+ Mutex::Autolock il(mInterfaceLock);
+ Mutex::Autolock l(mLock);
+
+ mPauseStateNotify = enable;
+}
+
// Pause to reconfigure
status_t Camera3Device::internalPauseAndWaitLocked(nsecs_t maxExpectedDuration) {
mRequestThread->setPaused(true);
- mPauseStateNotify = true;
ALOGV("%s: Camera %s: Internal wait until idle (% " PRIi64 " ns)", __FUNCTION__, mId.string(),
maxExpectedDuration);
@@ -1690,6 +1696,8 @@
mRequestThread->setPaused(false);
+ ALOGV("%s: Camera %s: Internal wait until active (% " PRIi64 " ns)", __FUNCTION__, mId.string(),
+ kActiveTimeout);
res = waitUntilStateThenRelock(/*active*/ true, kActiveTimeout);
if (res != OK) {
SET_ERR_L("Can't transition to active in %f seconds!",
@@ -1970,8 +1978,8 @@
if (mStatus != STATUS_ACTIVE && mStatus != STATUS_CONFIGURED) {
return;
}
- ALOGV("%s: Camera %s: Now %s", __FUNCTION__, mId.string(),
- idle ? "idle" : "active");
+ ALOGV("%s: Camera %s: Now %s, pauseState: %s", __FUNCTION__, mId.string(),
+ idle ? "idle" : "active", mPauseStateNotify ? "true" : "false");
internalUpdateStatusLocked(idle ? STATUS_CONFIGURED : STATUS_ACTIVE);
// Skip notifying listener if we're doing some user-transparent
@@ -3238,7 +3246,18 @@
sp<ICameraDeviceSession> &session,
std::shared_ptr<RequestMetadataQueue> queue) :
mHidlSession(session),
- mRequestMetadataQueue(queue) {}
+ mRequestMetadataQueue(queue) {
+ // Check with hardware service manager if we can downcast these interfaces
+ // Somewhat expensive, so cache the results at startup
+ auto castResult_3_4 = device::V3_4::ICameraDeviceSession::castFrom(mHidlSession);
+ if (castResult_3_4.isOk()) {
+ mHidlSession_3_4 = castResult_3_4;
+ }
+ auto castResult_3_3 = device::V3_3::ICameraDeviceSession::castFrom(mHidlSession);
+ if (castResult_3_3.isOk()) {
+ mHidlSession_3_3 = castResult_3_3;
+ }
+}
Camera3Device::HalInterface::HalInterface() {}
@@ -3266,52 +3285,89 @@
status_t res = OK;
common::V1_0::Status status;
- RequestTemplate id;
- switch (templateId) {
- case CAMERA3_TEMPLATE_PREVIEW:
- id = RequestTemplate::PREVIEW;
- break;
- case CAMERA3_TEMPLATE_STILL_CAPTURE:
- id = RequestTemplate::STILL_CAPTURE;
- break;
- case CAMERA3_TEMPLATE_VIDEO_RECORD:
- id = RequestTemplate::VIDEO_RECORD;
- break;
- case CAMERA3_TEMPLATE_VIDEO_SNAPSHOT:
- id = RequestTemplate::VIDEO_SNAPSHOT;
- break;
- case CAMERA3_TEMPLATE_ZERO_SHUTTER_LAG:
- id = RequestTemplate::ZERO_SHUTTER_LAG;
- break;
- case CAMERA3_TEMPLATE_MANUAL:
- id = RequestTemplate::MANUAL;
- break;
- default:
- // Unknown template ID
- return BAD_VALUE;
- }
- auto err = mHidlSession->constructDefaultRequestSettings(id,
- [&status, &requestTemplate]
+
+ auto requestCallback = [&status, &requestTemplate]
(common::V1_0::Status s, const device::V3_2::CameraMetadata& request) {
- status = s;
- if (status == common::V1_0::Status::OK) {
- const camera_metadata *r =
- reinterpret_cast<const camera_metadata_t*>(request.data());
- size_t expectedSize = request.size();
- int ret = validate_camera_metadata_structure(r, &expectedSize);
- if (ret == OK || ret == CAMERA_METADATA_VALIDATION_SHIFTED) {
- *requestTemplate = clone_camera_metadata(r);
- if (*requestTemplate == nullptr) {
- ALOGE("%s: Unable to clone camera metadata received from HAL",
- __FUNCTION__);
- status = common::V1_0::Status::INTERNAL_ERROR;
- }
- } else {
- ALOGE("%s: Malformed camera metadata received from HAL", __FUNCTION__);
+ status = s;
+ if (status == common::V1_0::Status::OK) {
+ const camera_metadata *r =
+ reinterpret_cast<const camera_metadata_t*>(request.data());
+ size_t expectedSize = request.size();
+ int ret = validate_camera_metadata_structure(r, &expectedSize);
+ if (ret == OK || ret == CAMERA_METADATA_VALIDATION_SHIFTED) {
+ *requestTemplate = clone_camera_metadata(r);
+ if (*requestTemplate == nullptr) {
+ ALOGE("%s: Unable to clone camera metadata received from HAL",
+ __FUNCTION__);
status = common::V1_0::Status::INTERNAL_ERROR;
}
+ } else {
+ ALOGE("%s: Malformed camera metadata received from HAL", __FUNCTION__);
+ status = common::V1_0::Status::INTERNAL_ERROR;
}
- });
+ }
+ };
+ hardware::Return<void> err;
+ if (mHidlSession_3_4 != nullptr) {
+ device::V3_4::RequestTemplate id;
+ switch (templateId) {
+ case CAMERA3_TEMPLATE_PREVIEW:
+ id = device::V3_4::RequestTemplate::PREVIEW;
+ break;
+ case CAMERA3_TEMPLATE_STILL_CAPTURE:
+ id = device::V3_4::RequestTemplate::STILL_CAPTURE;
+ break;
+ case CAMERA3_TEMPLATE_VIDEO_RECORD:
+ id = device::V3_4::RequestTemplate::VIDEO_RECORD;
+ break;
+ case CAMERA3_TEMPLATE_VIDEO_SNAPSHOT:
+ id = device::V3_4::RequestTemplate::VIDEO_SNAPSHOT;
+ break;
+ case CAMERA3_TEMPLATE_ZERO_SHUTTER_LAG:
+ id = device::V3_4::RequestTemplate::ZERO_SHUTTER_LAG;
+ break;
+ case CAMERA3_TEMPLATE_MANUAL:
+ id = device::V3_4::RequestTemplate::MANUAL;
+ break;
+ case CAMERA3_TEMPLATE_MOTION_TRACKING_PREVIEW:
+ id = device::V3_4::RequestTemplate::MOTION_TRACKING_PREVIEW;
+ break;
+ case CAMERA3_TEMPLATE_MOTION_TRACKING_BEST:
+ id = device::V3_4::RequestTemplate::MOTION_TRACKING_BEST;
+ break;
+ default:
+ // Unknown template ID
+ return BAD_VALUE;
+ }
+ err = mHidlSession_3_4->constructDefaultRequestSettings_3_4(id, requestCallback);
+ } else {
+ RequestTemplate id;
+ switch (templateId) {
+ case CAMERA3_TEMPLATE_PREVIEW:
+ id = RequestTemplate::PREVIEW;
+ break;
+ case CAMERA3_TEMPLATE_STILL_CAPTURE:
+ id = RequestTemplate::STILL_CAPTURE;
+ break;
+ case CAMERA3_TEMPLATE_VIDEO_RECORD:
+ id = RequestTemplate::VIDEO_RECORD;
+ break;
+ case CAMERA3_TEMPLATE_VIDEO_SNAPSHOT:
+ id = RequestTemplate::VIDEO_SNAPSHOT;
+ break;
+ case CAMERA3_TEMPLATE_ZERO_SHUTTER_LAG:
+ id = RequestTemplate::ZERO_SHUTTER_LAG;
+ break;
+ case CAMERA3_TEMPLATE_MANUAL:
+ id = RequestTemplate::MANUAL;
+ break;
+ default:
+ // Unknown template ID, or this HAL is too old to support it
+ return BAD_VALUE;
+ }
+ err = mHidlSession->constructDefaultRequestSettings(id, requestCallback);
+ }
+
if (!err.isOk()) {
ALOGE("%s: Transaction error: %s", __FUNCTION__, err.description().c_str());
res = DEAD_OBJECT;
@@ -3403,24 +3459,11 @@
common::V1_0::Status status;
// See if we have v3.4 or v3.3 HAL
- sp<device::V3_4::ICameraDeviceSession> hidlSession_3_4;
- sp<device::V3_3::ICameraDeviceSession> hidlSession_3_3;
- auto castResult_3_4 = device::V3_4::ICameraDeviceSession::castFrom(mHidlSession);
- if (castResult_3_4.isOk()) {
- hidlSession_3_4 = castResult_3_4;
- } else {
- auto castResult_3_3 = device::V3_3::ICameraDeviceSession::castFrom(mHidlSession);
- if (castResult_3_3.isOk()) {
- hidlSession_3_3 = castResult_3_3;
- }
- }
-
- if (hidlSession_3_4 != nullptr) {
- // We do; use v3.4 for the call, and construct a v3.4
- // HalStreamConfiguration
+ if (mHidlSession_3_4 != nullptr) {
+ // We do; use v3.4 for the call
ALOGV("%s: v3.4 device found", __FUNCTION__);
device::V3_4::HalStreamConfiguration finalConfiguration3_4;
- auto err = hidlSession_3_4->configureStreams_3_4(requestedConfiguration3_4,
+ auto err = mHidlSession_3_4->configureStreams_3_4(requestedConfiguration3_4,
[&status, &finalConfiguration3_4]
(common::V1_0::Status s, const device::V3_4::HalStreamConfiguration& halConfiguration) {
finalConfiguration3_4 = halConfiguration;
@@ -3434,10 +3477,10 @@
for (size_t i = 0; i < finalConfiguration3_4.streams.size(); i++) {
finalConfiguration.streams[i] = finalConfiguration3_4.streams[i].v3_3;
}
- } else if (hidlSession_3_3 != nullptr) {
+ } else if (mHidlSession_3_3 != nullptr) {
// We do; use v3.3 for the call
ALOGV("%s: v3.3 device found", __FUNCTION__);
- auto err = hidlSession_3_3->configureStreams_3_3(requestedConfiguration3_2,
+ auto err = mHidlSession_3_3->configureStreams_3_3(requestedConfiguration3_2,
[&status, &finalConfiguration]
(common::V1_0::Status s, const device::V3_3::HalStreamConfiguration& halConfiguration) {
finalConfiguration = halConfiguration;
@@ -4433,9 +4476,13 @@
if (res == OK) {
sp<StatusTracker> statusTracker = mStatusTracker.promote();
if (statusTracker != 0) {
+ sp<Camera3Device> parent = mParent.promote();
+ if (parent != nullptr) {
+ parent->pauseStateNotify(true);
+ }
+
statusTracker->markComponentIdle(mStatusId, Fence::NO_FENCE);
- sp<Camera3Device> parent = mParent.promote();
if (parent != nullptr) {
mReconfigured |= parent->reconfigureCamera(mLatestSessionParams);
}
diff --git a/services/camera/libcameraservice/device3/Camera3Device.h b/services/camera/libcameraservice/device3/Camera3Device.h
index 63e6219..bf2a577 100644
--- a/services/camera/libcameraservice/device3/Camera3Device.h
+++ b/services/camera/libcameraservice/device3/Camera3Device.h
@@ -31,6 +31,7 @@
#include <android/hardware/camera/device/3.2/ICameraDevice.h>
#include <android/hardware/camera/device/3.2/ICameraDeviceSession.h>
#include <android/hardware/camera/device/3.3/ICameraDeviceSession.h>
+#include <android/hardware/camera/device/3.4/ICameraDeviceSession.h>
#include <android/hardware/camera/device/3.2/ICameraDeviceCallback.h>
#include <fmq/MessageQueue.h>
#include <hardware/camera3.h>
@@ -297,7 +298,13 @@
void getInflightBufferKeys(std::vector<std::pair<int32_t, int32_t>>* out);
private:
+ // Always valid
sp<hardware::camera::device::V3_2::ICameraDeviceSession> mHidlSession;
+ // Valid if ICameraDeviceSession is @3.3 or newer
+ sp<hardware::camera::device::V3_3::ICameraDeviceSession> mHidlSession_3_3;
+ // Valid if ICameraDeviceSession is @3.4 or newer
+ sp<hardware::camera::device::V3_4::ICameraDeviceSession> mHidlSession_3_4;
+
std::shared_ptr<RequestMetadataQueue> mRequestMetadataQueue;
std::mutex mInflightLock;
@@ -554,8 +561,15 @@
const SurfaceMap &surfaceMap);
/**
+ * Pause state updates to the client application. Needed to mask out idle/active
+ * transitions during internal reconfigure
+ */
+ void pauseStateNotify(bool enable);
+
+ /**
* Internally re-configure camera device using new session parameters.
- * This will get triggered by the request thread.
+ * This will get triggered by the request thread. Be sure to call
+ * pauseStateNotify(true) before going idle in the requesting location.
*/
bool reconfigureCamera(const CameraMetadata& sessionParams);
diff --git a/services/mediaextractor/main_extractorservice.cpp b/services/mediaextractor/main_extractorservice.cpp
index 0dc5e29..8d3359a 100644
--- a/services/mediaextractor/main_extractorservice.cpp
+++ b/services/mediaextractor/main_extractorservice.cpp
@@ -25,6 +25,7 @@
#include <string>
#include <android-base/logging.h>
+#include <android-base/properties.h>
#include <utils/misc.h>
// from LOCAL_C_INCLUDES
@@ -65,14 +66,10 @@
sp<IServiceManager> sm = defaultServiceManager();
MediaExtractorService::instantiate();
- // TODO: Uncomment below once sepolicy change is landed.
- /*
- char value[PROPERTY_VALUE_MAX];
- property_get("ro.build.type", value, "unknown");
- if (strcmp(value, "userdebug") == 0 || strcmp(value, "eng") == 0) {
+ std::string value = base::GetProperty("ro.build.type", "unknown");
+ if (value == "userdebug" || value == "eng") {
media::MediaExtractorUpdateService::instantiate();
}
- */
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();
diff --git a/services/oboeservice/AAudioServiceStreamBase.cpp b/services/oboeservice/AAudioServiceStreamBase.cpp
index 864fc35..e8c9e41 100644
--- a/services/oboeservice/AAudioServiceStreamBase.cpp
+++ b/services/oboeservice/AAudioServiceStreamBase.cpp
@@ -271,11 +271,11 @@
}
aaudio_result_t AAudioServiceStreamBase::flush() {
- if (getState() != AAUDIO_STREAM_STATE_PAUSED) {
- ALOGE("flush() stream not paused, state = %s",
- AAudio_convertStreamStateToText(mState));
- return AAUDIO_ERROR_INVALID_STATE;
+ aaudio_result_t result = AAudio_isFlushAllowed(getState());
+ if (result != AAUDIO_OK) {
+ return result;
}
+
// Data will get flushed when the client receives the FLUSHED event.
sendServiceEvent(AAUDIO_SERVICE_EVENT_FLUSHED);
setState(AAUDIO_STREAM_STATE_FLUSHED);