Merge "NdkMediaExtractor: add disconnect API" into pi-dev
diff --git a/include/media/ExtractorUtils.h b/include/media/ExtractorUtils.h
new file mode 120000
index 0000000..e2dd082
--- /dev/null
+++ b/include/media/ExtractorUtils.h
@@ -0,0 +1 @@
+../../media/libmediaextractor/include/media/ExtractorUtils.h
\ No newline at end of file
diff --git a/media/extractors/mkv/MatroskaExtractor.cpp b/media/extractors/mkv/MatroskaExtractor.cpp
index 3f832bc..fc60fd4 100644
--- a/media/extractors/mkv/MatroskaExtractor.cpp
+++ b/media/extractors/mkv/MatroskaExtractor.cpp
@@ -22,6 +22,7 @@
#include "MatroskaExtractor.h"
#include <media/DataSourceBase.h>
+#include <media/ExtractorUtils.h>
#include <media/MediaTrack.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/AUtils.h>
@@ -1108,7 +1109,7 @@
meta.setData(kKeyFlacMetadata, 0, codecPrivate, codecPrivateSize);
int32_t maxInputSize = 64 << 10;
- sp<FLACDecoder> flacDecoder = FLACDecoder::Create();
+ FLACDecoder *flacDecoder = FLACDecoder::Create();
if (flacDecoder != NULL
&& flacDecoder->parseMetadata((const uint8_t*)codecPrivate, codecPrivateSize) == OK) {
FLAC__StreamMetadata_StreamInfo streamInfo = flacDecoder->getStreamInfo();
@@ -1120,6 +1121,7 @@
&& streamInfo.channels != 0
&& ((streamInfo.bits_per_sample + 7) / 8) >
INT32_MAX / streamInfo.max_blocksize / streamInfo.channels) {
+ delete flacDecoder;
return ERROR_MALFORMED;
}
maxInputSize = ((streamInfo.bits_per_sample + 7) / 8)
@@ -1128,6 +1130,7 @@
}
meta.setInt32(kKeyMaxInputSize, maxInputSize);
+ delete flacDecoder;
return OK;
}
@@ -1143,13 +1146,13 @@
}
const mkvparser::Block::Frame &frame = block->GetFrame(0);
- sp<ABuffer> abuf = new ABuffer(frame.len);
- long n = frame.Read(mReader, abuf->data());
+ auto tmpData = heapbuffer<unsigned char>(frame.len);
+ long n = frame.Read(mReader, tmpData.get());
if (n != 0) {
return ERROR_MALFORMED;
}
- if (!MakeAVCCodecSpecificData(trackInfo->mMeta, abuf)) {
+ if (!MakeAVCCodecSpecificData(trackInfo->mMeta, tmpData.get(), frame.len)) {
return ERROR_MALFORMED;
}
diff --git a/media/extractors/mp4/ItemTable.cpp b/media/extractors/mp4/ItemTable.cpp
index cef5f4a..c082fc1 100644
--- a/media/extractors/mp4/ItemTable.cpp
+++ b/media/extractors/mp4/ItemTable.cpp
@@ -982,26 +982,24 @@
bool InfeBox::parseNullTerminatedString(
off64_t *offset, size_t *size, String8 *out) {
- char tmp[256];
- size_t len = 0;
+ char tmp;
+ Vector<char> buf;
+ buf.setCapacity(256);
off64_t newOffset = *offset;
off64_t stopOffset = *offset + *size;
while (newOffset < stopOffset) {
- if (!source()->readAt(newOffset++, &tmp[len], 1)) {
+ if (!source()->readAt(newOffset++, &tmp, 1)) {
return false;
}
- if (tmp[len] == 0) {
- out->append(tmp, len);
+ buf.push_back(tmp);
+ if (tmp == 0) {
+ out->setTo(buf.array());
*offset = newOffset;
*size = stopOffset - newOffset;
return true;
}
- if (++len >= sizeof(tmp)) {
- out->append(tmp, len);
- len = 0;
- }
}
return false;
}
@@ -1060,9 +1058,12 @@
return ERROR_MALFORMED;
}
- String8 content_encoding;
- if (!parseNullTerminatedString(&offset, &size, &content_encoding)) {
- return ERROR_MALFORMED;
+ // content_encoding is optional; can be omitted if would be empty
+ if (size > 0) {
+ String8 content_encoding;
+ if (!parseNullTerminatedString(&offset, &size, &content_encoding)) {
+ return ERROR_MALFORMED;
+ }
}
} else if (item_type == FOURCC('u', 'r', 'i', ' ')) {
String8 item_uri_type;
diff --git a/media/extractors/ogg/OggExtractor.cpp b/media/extractors/ogg/OggExtractor.cpp
index 4d49013..b2fe69c 100644
--- a/media/extractors/ogg/OggExtractor.cpp
+++ b/media/extractors/ogg/OggExtractor.cpp
@@ -22,6 +22,7 @@
#include <cutils/properties.h>
#include <media/DataSourceBase.h>
+#include <media/ExtractorUtils.h>
#include <media/MediaTrack.h>
#include <media/VorbisComment.h>
#include <media/stagefright/foundation/ABuffer.h>
@@ -990,21 +991,11 @@
return OK;
}
-struct TmpData {
- uint8_t *data;
- TmpData(size_t size) {
- data = (uint8_t*) malloc(size);
- }
- ~TmpData() {
- free(data);
- }
-};
-
status_t MyOpusExtractor::verifyOpusComments(MediaBufferBase *buffer) {
// add artificial framing bit so we can reuse _vorbis_unpack_comment
int32_t commentSize = buffer->range_length() + 1;
- TmpData commentDataHolder(commentSize);
- uint8_t *commentData = commentDataHolder.data;
+ auto tmp = heapbuffer<uint8_t>(commentSize);
+ uint8_t *commentData = tmp.get();
if (commentData == nullptr) {
return ERROR_MALFORMED;
}
diff --git a/media/libaudiohal/4.0/DevicesFactoryHalHybrid.cpp b/media/libaudiohal/4.0/DevicesFactoryHalHybrid.cpp
index c43509c..7ff1ec7d 100644
--- a/media/libaudiohal/4.0/DevicesFactoryHalHybrid.cpp
+++ b/media/libaudiohal/4.0/DevicesFactoryHalHybrid.cpp
@@ -33,7 +33,8 @@
}
status_t DevicesFactoryHalHybrid::openDevice(const char *name, sp<DeviceHalInterface> *device) {
- if (mHidlFactory != 0 && strcmp(AUDIO_HARDWARE_MODULE_ID_A2DP, name) != 0) {
+ if (mHidlFactory != 0 && strcmp(AUDIO_HARDWARE_MODULE_ID_A2DP, name) != 0 &&
+ strcmp(AUDIO_HARDWARE_MODULE_ID_HEARING_AID, name) != 0) {
return mHidlFactory->openDevice(name, device);
}
return mLocalFactory->openDevice(name, device);
diff --git a/media/libheif/HeifDecoderImpl.cpp b/media/libheif/HeifDecoderImpl.cpp
index 2dfbdca..63130c4 100644
--- a/media/libheif/HeifDecoderImpl.cpp
+++ b/media/libheif/HeifDecoderImpl.cpp
@@ -140,6 +140,11 @@
// have been caught above.
CHECK(offset >= mCachedOffset);
+ off64_t resultOffset;
+ if (__builtin_add_overflow(offset, size, &resultOffset)) {
+ return ERROR_IO;
+ }
+
if (size == 0) {
return 0;
}
diff --git a/media/libmediaextractor/include/media/ExtractorUtils.h b/media/libmediaextractor/include/media/ExtractorUtils.h
new file mode 100644
index 0000000..22f9349
--- /dev/null
+++ b/media/libmediaextractor/include/media/ExtractorUtils.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef EXTRACTOR_UTILS_H_
+
+#define EXTRACTOR_UTILS_H_
+
+#include <memory>
+
+namespace android {
+
+template <class T>
+std::unique_ptr<T[]> heapbuffer(size_t size) {
+ return std::unique_ptr<T[]>(new (std::nothrow) T[size]);
+}
+
+} // namespace android
+
+#endif // UTILS_H_
diff --git a/media/libmediaplayerservice/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp
index 9ab9aae..30c0b1c 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.cpp
+++ b/media/libmediaplayerservice/StagefrightRecorder.cpp
@@ -89,6 +89,13 @@
static const char *kRecorderVideoTimescale = "android.media.mediarecorder.video-timescale";
static const char *kRecorderWidth = "android.media.mediarecorder.width";
+// new fields, not yet frozen in the public Java API definitions
+static const char *kRecorderAudioMime = "android.media.mediarecorder.audio.mime";
+static const char *kRecorderVideoMime = "android.media.mediarecorder.video.mime";
+static const char *kRecorderDurationMs = "android.media.mediarecorder.durationMs";
+static const char *kRecorderPaused = "android.media.mediarecorder.pausedMs";
+static const char *kRecorderNumPauses = "android.media.mediarecorder.NPauses";
+
// To collect the encoder usage for the battery app
static void addBatteryData(uint32_t params) {
@@ -126,21 +133,18 @@
}
// log the current record, provided it has some information worth recording
- if (mAnalyticsDirty && mAnalyticsItem != NULL) {
- updateMetrics();
- if (mAnalyticsItem->count() > 0) {
- mAnalyticsItem->selfrecord();
- }
- delete mAnalyticsItem;
- mAnalyticsItem = NULL;
- }
+ // NB: this also reclaims & clears mAnalyticsItem.
+ flushAndResetMetrics(false);
}
void StagefrightRecorder::updateMetrics() {
ALOGV("updateMetrics");
- // we'll populate the values from the raw fields.
- // (NOT going to populate as we go through the various set* ops)
+ // we run as part of the media player service; what we really want to
+ // know is the app which requested the recording.
+ mAnalyticsItem->setUid(mClientUid);
+
+ // populate the values from the raw fields.
// TBD mOutputFormat = OUTPUT_FORMAT_THREE_GPP;
// TBD mAudioEncoder = AUDIO_ENCODER_AMR_NB;
@@ -168,7 +172,6 @@
// TBD mTrackEveryTimeDurationUs = 0;
mAnalyticsItem->setInt32(kRecorderCaptureFpsEnable, mCaptureFpsEnable);
mAnalyticsItem->setDouble(kRecorderCaptureFps, mCaptureFps);
- // TBD mCaptureFps = -1.0;
// TBD mCameraSourceTimeLapse = NULL;
// TBD mMetaDataStoredInVideoBuffers = kMetadataBufferTypeInvalid;
// TBD mEncoderProfiles = MediaProfiles::getInstance();
@@ -177,14 +180,17 @@
// PII mLongitudex10000 = -3600000;
// TBD mTotalBitRate = 0;
- // TBD: some duration information (capture, paused)
- //
-
+ // duration information (recorded, paused, # of pauses)
+ mAnalyticsItem->setInt64(kRecorderDurationMs, (mDurationRecordedUs+500)/1000 );
+ if (mNPauses != 0) {
+ mAnalyticsItem->setInt64(kRecorderPaused, (mDurationPausedUs+500)/1000 );
+ mAnalyticsItem->setInt32(kRecorderNumPauses, mNPauses);
+ }
}
-void StagefrightRecorder::resetMetrics() {
- ALOGV("resetMetrics");
- // flush anything we have, restart the record
+void StagefrightRecorder::flushAndResetMetrics(bool reinitialize) {
+ ALOGV("flushAndResetMetrics");
+ // flush anything we have, maybe setup a new record
if (mAnalyticsDirty && mAnalyticsItem != NULL) {
updateMetrics();
if (mAnalyticsItem->count() > 0) {
@@ -193,8 +199,10 @@
delete mAnalyticsItem;
mAnalyticsItem = NULL;
}
- mAnalyticsItem = new MediaAnalyticsItem(kKeyRecorder);
mAnalyticsDirty = false;
+ if (reinitialize) {
+ mAnalyticsItem = new MediaAnalyticsItem(kKeyRecorder);
+ }
}
status_t StagefrightRecorder::init() {
@@ -1030,6 +1038,8 @@
mAnalyticsDirty = true;
mStarted = true;
+ mStartedRecordingUs = systemTime() / 1000;
+
uint32_t params = IMediaPlayerService::kBatteryDataCodecStarted;
if (mAudioSource != AUDIO_SOURCE_CNT) {
params |= IMediaPlayerService::kBatteryDataTrackAudio;
@@ -1109,6 +1119,14 @@
return NULL;
}
+ // log audio mime type for media metrics
+ if (mAnalyticsItem != NULL) {
+ AString audiomime;
+ if (format->findString("mime", &audiomime)) {
+ mAnalyticsItem->setCString(kRecorderAudioMime, audiomime.c_str());
+ }
+ }
+
int32_t maxInputSize;
CHECK(audioSource->getFormat()->findInt32(
kKeyMaxInputSize, &maxInputSize));
@@ -1655,6 +1673,14 @@
break;
}
+ // log video mime type for media metrics
+ if (mAnalyticsItem != NULL) {
+ AString videomime;
+ if (format->findString("mime", &videomime)) {
+ mAnalyticsItem->setCString(kRecorderVideoMime, videomime.c_str());
+ }
+ }
+
if (cameraSource != NULL) {
sp<MetaData> meta = cameraSource->getFormat();
@@ -1917,6 +1943,13 @@
sp<MetaData> meta = new MetaData;
meta->setInt64(kKeyTime, mPauseStartTimeUs);
+ if (mStartedRecordingUs != 0) {
+ // should always be true
+ int64_t recordingUs = mPauseStartTimeUs - mStartedRecordingUs;
+ mDurationRecordedUs += recordingUs;
+ mStartedRecordingUs = 0;
+ }
+
if (mAudioEncoderSource != NULL) {
mAudioEncoderSource->pause();
}
@@ -1975,6 +2008,16 @@
source->setInputBufferTimeOffset((int64_t)timeOffset);
source->start(meta.get());
}
+
+
+ // sum info on pause duration
+ // (ignore the 30msec of overlap adjustment factored into mTotalPausedDurationUs)
+ int64_t pausedUs = resumeStartTimeUs - mPauseStartTimeUs;
+ mDurationPausedUs += pausedUs;
+ mNPauses++;
+ // and a timestamp marking that we're back to recording....
+ mStartedRecordingUs = resumeStartTimeUs;
+
mPauseStartTimeUs = 0;
return OK;
@@ -2003,10 +2046,28 @@
mWriter.clear();
}
- resetMetrics();
+ // account for the last 'segment' -- whether paused or recording
+ if (mPauseStartTimeUs != 0) {
+ // we were paused
+ int64_t additive = stopTimeUs - mPauseStartTimeUs;
+ mDurationPausedUs += additive;
+ mNPauses++;
+ } else if (mStartedRecordingUs != 0) {
+ // we were recording
+ int64_t additive = stopTimeUs - mStartedRecordingUs;
+ mDurationRecordedUs += additive;
+ } else {
+ ALOGW("stop while neither recording nor paused");
+ }
+ flushAndResetMetrics(true);
+
+ mDurationRecordedUs = 0;
+ mDurationPausedUs = 0;
+ mNPauses = 0;
mTotalPausedDurationUs = 0;
mPauseStartTimeUs = 0;
+ mStartedRecordingUs = 0;
mGraphicBufferProducer.clear();
mPersistentSurface.clear();
@@ -2085,6 +2146,12 @@
mLongitudex10000 = -3600000;
mTotalBitRate = 0;
+ // tracking how long we recorded.
+ mDurationRecordedUs = 0;
+ mStartedRecordingUs = 0;
+ mDurationPausedUs = 0;
+ mNPauses = 0;
+
mOutputFd = -1;
return OK;
diff --git a/media/libmediaplayerservice/StagefrightRecorder.h b/media/libmediaplayerservice/StagefrightRecorder.h
index 18c9256..faa2e59 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.h
+++ b/media/libmediaplayerservice/StagefrightRecorder.h
@@ -95,7 +95,7 @@
MediaAnalyticsItem *mAnalyticsItem;
bool mAnalyticsDirty;
- void resetMetrics();
+ void flushAndResetMetrics(bool reinitialize);
void updateMetrics();
audio_source_t mAudioSource;
@@ -127,6 +127,11 @@
int32_t mStartTimeOffsetMs;
int32_t mTotalBitRate;
+ int64_t mDurationRecordedUs;
+ int64_t mStartedRecordingUs;
+ int64_t mDurationPausedUs;
+ int32_t mNPauses;
+
bool mCaptureFpsEnable;
double mCaptureFps;
int64_t mTimeBetweenCaptureUs;
diff --git a/media/libstagefright/MPEG4Writer.cpp b/media/libstagefright/MPEG4Writer.cpp
index cfbbcb2..768df26 100644
--- a/media/libstagefright/MPEG4Writer.cpp
+++ b/media/libstagefright/MPEG4Writer.cpp
@@ -1376,15 +1376,12 @@
}
void MPEG4Writer::addMultipleLengthPrefixedSamples_l(MediaBuffer *buffer) {
- const size_t kExtensionNALSearchRange = 64; // bytes to look for non-VCL NALUs
-
const uint8_t *dataStart = (const uint8_t *)buffer->data() + buffer->range_offset();
const uint8_t *currentNalStart = dataStart;
const uint8_t *nextNalStart;
const uint8_t *data = dataStart;
size_t nextNalSize;
- size_t searchSize = buffer->range_length() > kExtensionNALSearchRange ?
- kExtensionNALSearchRange : buffer->range_length();
+ size_t searchSize = buffer->range_length();
while (getNextNALUnit(&data, &searchSize, &nextNalStart,
&nextNalSize, true) == OK) {
@@ -1962,6 +1959,17 @@
return;
}
+ // Rotation angle in HEIF is CCW, framework angle is CW.
+ int32_t heifRotation = 0;
+ switch(mRotation) {
+ case 90: heifRotation = 3; break;
+ case 180: heifRotation = 2; break;
+ case 270: heifRotation = 1; break;
+ default: break; // don't set if invalid
+ }
+
+ bool hasGrid = (mNumTiles > 1);
+
if (mProperties.empty()) {
mProperties.push_back(mOwner->addProperty_l({
.type = FOURCC('h', 'v', 'c', 'C'),
@@ -1970,22 +1978,29 @@
mProperties.push_back(mOwner->addProperty_l({
.type = FOURCC('i', 's', 'p', 'e'),
- .width = (mNumTiles > 1) ? mGridWidth : mWidth,
- .height = (mNumTiles > 1) ? mGridHeight : mHeight,
+ .width = hasGrid ? mGridWidth : mWidth,
+ .height = hasGrid ? mGridHeight : mHeight,
}));
+
+ if (!hasGrid && heifRotation > 0) {
+ mProperties.push_back(mOwner->addProperty_l({
+ .type = FOURCC('i', 'r', 'o', 't'),
+ .rotation = heifRotation,
+ }));
+ }
}
uint16_t itemId = mOwner->addItem_l({
.itemType = "hvc1",
- .isPrimary = (mNumTiles > 1) ? false : (mIsPrimary != 0),
- .isHidden = (mNumTiles > 1),
+ .isPrimary = hasGrid ? false : (mIsPrimary != 0),
+ .isHidden = hasGrid,
.offset = (uint32_t)offset,
.size = (uint32_t)size,
.properties = mProperties,
});
mTileIndex++;
- if (mNumTiles > 1) {
+ if (hasGrid) {
mDimgRefs.push_back(itemId);
if (mTileIndex == mNumTiles) {
@@ -1995,6 +2010,12 @@
.width = mWidth,
.height = mHeight,
}));
+ if (heifRotation > 0) {
+ mProperties.push_back(mOwner->addProperty_l({
+ .type = FOURCC('i', 'r', 'o', 't'),
+ .rotation = heifRotation,
+ }));
+ }
mOwner->addItem_l({
.itemType = "grid",
.isPrimary = (mIsPrimary != 0),
@@ -2305,7 +2326,8 @@
mStartTimeRealUs = startTimeUs;
int32_t rotationDegrees;
- if (mIsVideo && params && params->findInt32(kKeyRotation, &rotationDegrees)) {
+ if ((mIsVideo || mIsHeic) && params &&
+ params->findInt32(kKeyRotation, &rotationDegrees)) {
mRotation = rotationDegrees;
}
@@ -3430,16 +3452,36 @@
int32_t MPEG4Writer::Track::getMetaSizeIncrease() const {
CHECK(mIsHeic);
- return 20 // 1. 'ispe' property
- + (8 + mCodecSpecificDataSize) // 2. 'hvcC' property
- + (20 // 3. extra 'ispe'
- + (8 + 2 + 2 + mNumTiles * 2) // 4. 'dimg' ref
- + 12) // 5. ImageGrid in 'idat' (worst case)
- * (mNumTiles > 1) // - (3~5: applicable only if grid)
- + (16 // 6. increase to 'iloc'
- + 21 // 7. increase to 'iinf'
- + (3 + 2 * 2)) // 8. increase to 'ipma' (worst case)
- * (mNumTiles + 1); // - (6~8: are per-item)
+
+ int32_t grid = (mNumTiles > 1);
+
+ // Note that the rotation angle is in the file meta, and we don't have
+ // it until start, so here the calculation has to assume rotation.
+
+ // increase to ipco
+ int32_t increase = 20 * (grid + 1) // 'ispe' property
+ + (8 + mCodecSpecificDataSize) // 'hvcC' property
+ + 9; // 'irot' property (worst case)
+
+ // increase to iref and idat
+ if (grid) {
+ increase += (8 + 2 + 2 + mNumTiles * 2) // 'dimg' in iref
+ + 12; // ImageGrid in 'idat' (worst case)
+ }
+
+ // increase to iloc, iinf and ipma
+ increase += (16 // increase to 'iloc'
+ + 21 // increase to 'iinf'
+ + (3 + 2 * 2)) // increase to 'ipma' (worst case, 2 props x 2 bytes)
+ * (mNumTiles + grid);
+
+ // adjust to ipma:
+ // if rotation is present and only one tile, it could ref 3 properties
+ if (!grid) {
+ increase += 2;
+ }
+
+ return increase;
}
status_t MPEG4Writer::Track::checkCodecSpecificData() const {
@@ -4267,24 +4309,38 @@
numProperties = 32767;
}
for (size_t propIndex = 0; propIndex < numProperties; propIndex++) {
- if (mProperties[propIndex].type == FOURCC('h', 'v', 'c', 'C')) {
- beginBox("hvcC");
- sp<ABuffer> hvcc = mProperties[propIndex].hvcc;
- // Patch avcc's lengthSize field to match the number
- // of bytes we use to indicate the size of a nal unit.
- uint8_t *ptr = (uint8_t *)hvcc->data();
- ptr[21] = (ptr[21] & 0xfc) | (useNalLengthFour() ? 3 : 1);
- write(hvcc->data(), hvcc->size());
- endBox();
- } else if (mProperties[propIndex].type == FOURCC('i', 's', 'p', 'e')) {
- beginBox("ispe");
- writeInt32(0); // Version = 0, Flags = 0
- writeInt32(mProperties[propIndex].width);
- writeInt32(mProperties[propIndex].height);
- endBox();
- } else {
- ALOGW("Skipping unrecognized property: type 0x%08x",
- mProperties[propIndex].type);
+ switch (mProperties[propIndex].type) {
+ case FOURCC('h', 'v', 'c', 'C'):
+ {
+ beginBox("hvcC");
+ sp<ABuffer> hvcc = mProperties[propIndex].hvcc;
+ // Patch avcc's lengthSize field to match the number
+ // of bytes we use to indicate the size of a nal unit.
+ uint8_t *ptr = (uint8_t *)hvcc->data();
+ ptr[21] = (ptr[21] & 0xfc) | (useNalLengthFour() ? 3 : 1);
+ write(hvcc->data(), hvcc->size());
+ endBox();
+ break;
+ }
+ case FOURCC('i', 's', 'p', 'e'):
+ {
+ beginBox("ispe");
+ writeInt32(0); // Version = 0, Flags = 0
+ writeInt32(mProperties[propIndex].width);
+ writeInt32(mProperties[propIndex].height);
+ endBox();
+ break;
+ }
+ case FOURCC('i', 'r', 'o', 't'):
+ {
+ beginBox("irot");
+ writeInt8(mProperties[propIndex].rotation);
+ endBox();
+ break;
+ }
+ default:
+ ALOGW("Skipping unrecognized property: type 0x%08x",
+ mProperties[propIndex].type);
}
}
endBox();
diff --git a/media/libstagefright/MetaDataUtils.cpp b/media/libstagefright/MetaDataUtils.cpp
index af8f539..04f6ade 100644
--- a/media/libstagefright/MetaDataUtils.cpp
+++ b/media/libstagefright/MetaDataUtils.cpp
@@ -24,11 +24,12 @@
namespace android {
-bool MakeAVCCodecSpecificData(MetaDataBase &meta, const sp<ABuffer> &accessUnit) {
+bool MakeAVCCodecSpecificData(MetaDataBase &meta, const uint8_t *data, size_t size) {
int32_t width;
int32_t height;
int32_t sarWidth;
int32_t sarHeight;
+ sp<ABuffer> accessUnit = new ABuffer((void*)data, size);
sp<ABuffer> csd = MakeAVCCodecSpecificData(accessUnit, &width, &height, &sarWidth, &sarHeight);
if (csd == nullptr) {
return false;
diff --git a/media/libstagefright/bqhelper/GraphicBufferSource.cpp b/media/libstagefright/bqhelper/GraphicBufferSource.cpp
index 87d2555..68ae8ec 100644
--- a/media/libstagefright/bqhelper/GraphicBufferSource.cpp
+++ b/media/libstagefright/bqhelper/GraphicBufferSource.cpp
@@ -421,26 +421,31 @@
}
Status GraphicBufferSource::release(){
- Mutex::Autolock autoLock(mMutex);
- if (mLooper != NULL) {
- mLooper->unregisterHandler(mReflector->id());
- mReflector.clear();
+ sp<ALooper> looper;
+ {
+ Mutex::Autolock autoLock(mMutex);
+ looper = mLooper;
+ if (mLooper != NULL) {
+ mLooper->unregisterHandler(mReflector->id());
+ mReflector.clear();
- mLooper->stop();
- mLooper.clear();
+ mLooper.clear();
+ }
+
+ ALOGV("--> release; available=%zu+%d eos=%d eosSent=%d acquired=%d",
+ mAvailableBuffers.size(), mNumAvailableUnacquiredBuffers,
+ mEndOfStream, mEndOfStreamSent, mNumOutstandingAcquires);
+
+ // Codec is no longer executing. Releasing all buffers to bq.
+ mFreeCodecBuffers.clear();
+ mSubmittedCodecBuffers.clear();
+ mLatestBuffer.mBuffer.reset();
+ mComponent.clear();
+ mExecuting = false;
}
-
- ALOGV("--> release; available=%zu+%d eos=%d eosSent=%d acquired=%d",
- mAvailableBuffers.size(), mNumAvailableUnacquiredBuffers,
- mEndOfStream, mEndOfStreamSent, mNumOutstandingAcquires);
-
- // Codec is no longer executing. Releasing all buffers to bq.
- mFreeCodecBuffers.clear();
- mSubmittedCodecBuffers.clear();
- mLatestBuffer.mBuffer.reset();
- mComponent.clear();
- mExecuting = false;
-
+ if (looper != NULL) {
+ looper->stop();
+ }
return Status::ok();
}
diff --git a/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.cpp b/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.cpp
index 0f1fecc..87138af 100644
--- a/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.cpp
+++ b/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.cpp
@@ -48,6 +48,7 @@
}
C2SoftFlacDecoder::~C2SoftFlacDecoder() {
+ delete mFLACDecoder;
}
c2_status_t C2SoftFlacDecoder::onInit() {
@@ -77,6 +78,9 @@
}
status_t C2SoftFlacDecoder::initDecoder() {
+ if (mFLACDecoder) {
+ delete mFLACDecoder;
+ }
mFLACDecoder = FLACDecoder::Create();
if (!mFLACDecoder) {
ALOGE("initDecoder: failed to create FLACDecoder");
diff --git a/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.h b/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.h
index a5c01a9..43d913b 100644
--- a/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.h
+++ b/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.h
@@ -46,7 +46,7 @@
kMaxBlockSize = 4096
};
- sp<FLACDecoder> mFLACDecoder;
+ FLACDecoder *mFLACDecoder;
FLAC__StreamMetadata_StreamInfo mStreamInfo;
bool mSignalledError;
bool mSignalledOutputEos;
diff --git a/media/libstagefright/codecs/flac/dec/SoftFlacDecoder.cpp b/media/libstagefright/codecs/flac/dec/SoftFlacDecoder.cpp
index 4ab1ab2..d0b72b7 100644
--- a/media/libstagefright/codecs/flac/dec/SoftFlacDecoder.cpp
+++ b/media/libstagefright/codecs/flac/dec/SoftFlacDecoder.cpp
@@ -57,6 +57,7 @@
SoftFlacDecoder::~SoftFlacDecoder() {
ALOGV("dtor:");
+ delete mFLACDecoder;
}
void SoftFlacDecoder::initPorts() {
diff --git a/media/libstagefright/codecs/flac/dec/SoftFlacDecoder.h b/media/libstagefright/codecs/flac/dec/SoftFlacDecoder.h
index 4a21c34..0f17ed8 100644
--- a/media/libstagefright/codecs/flac/dec/SoftFlacDecoder.h
+++ b/media/libstagefright/codecs/flac/dec/SoftFlacDecoder.h
@@ -50,7 +50,7 @@
kNumOutputBuffers = 4,
};
- sp<FLACDecoder> mFLACDecoder;
+ FLACDecoder *mFLACDecoder;
FLAC__StreamMetadata_StreamInfo mStreamInfo;
bool mHasStreamInfo;
size_t mInputBufferCount;
diff --git a/media/libstagefright/flac/dec/FLACDecoder.cpp b/media/libstagefright/flac/dec/FLACDecoder.cpp
index 8c7137c..e0e9211 100644
--- a/media/libstagefright/flac/dec/FLACDecoder.cpp
+++ b/media/libstagefright/flac/dec/FLACDecoder.cpp
@@ -220,9 +220,10 @@
}
// static
-sp<FLACDecoder> FLACDecoder::Create() {
- sp<FLACDecoder> decoder = new FLACDecoder();
- if (decoder->init() != OK) {
+FLACDecoder *FLACDecoder::Create() {
+ FLACDecoder *decoder = new (std::nothrow) FLACDecoder();
+ if (decoder == NULL || decoder->init() != OK) {
+ delete decoder;
return NULL;
}
return decoder;
diff --git a/media/libstagefright/flac/dec/FLACDecoder.h b/media/libstagefright/flac/dec/FLACDecoder.h
index 36282a8..1a33cae 100644
--- a/media/libstagefright/flac/dec/FLACDecoder.h
+++ b/media/libstagefright/flac/dec/FLACDecoder.h
@@ -26,14 +26,14 @@
namespace android {
// packet based FLAC decoder, wrapps libFLAC stream decoder.
-class FLACDecoder : public RefBase {
+class FLACDecoder {
public:
enum {
kMaxChannels = 8,
};
- static sp<FLACDecoder> Create();
+ static FLACDecoder *Create();
FLAC__StreamMetadata_StreamInfo getStreamInfo() const {
return mStreamInfo;
@@ -43,10 +43,10 @@
status_t decodeOneFrame(const uint8_t *inBuffer, size_t inBufferLen,
short *outBuffer, size_t *outBufferLen);
void flush();
+ virtual ~FLACDecoder();
protected:
FLACDecoder();
- virtual ~FLACDecoder() override;
private:
// stream properties
diff --git a/media/libstagefright/include/media/stagefright/MPEG4Writer.h b/media/libstagefright/include/media/stagefright/MPEG4Writer.h
index 2a062cc..7b41362 100644
--- a/media/libstagefright/include/media/stagefright/MPEG4Writer.h
+++ b/media/libstagefright/include/media/stagefright/MPEG4Writer.h
@@ -195,6 +195,7 @@
uint32_t type;
int32_t width;
int32_t height;
+ int32_t rotation;
sp<ABuffer> hvcc;
} ItemProperty;
diff --git a/media/libstagefright/include/media/stagefright/MetaDataUtils.h b/media/libstagefright/include/media/stagefright/MetaDataUtils.h
index 3af2218..d5a8080 100644
--- a/media/libstagefright/include/media/stagefright/MetaDataUtils.h
+++ b/media/libstagefright/include/media/stagefright/MetaDataUtils.h
@@ -23,7 +23,7 @@
namespace android {
struct ABuffer;
-bool MakeAVCCodecSpecificData(MetaDataBase &meta, const sp<ABuffer> &accessUnit);
+bool MakeAVCCodecSpecificData(MetaDataBase &meta, const uint8_t *data, size_t size);
bool MakeAACCodecSpecificData(MetaDataBase &meta, unsigned profile, unsigned sampling_freq_index,
unsigned channel_configuration);
diff --git a/media/libstagefright/mpeg2ts/ESQueue.cpp b/media/libstagefright/mpeg2ts/ESQueue.cpp
index 090d4e1..0fa9fcb 100644
--- a/media/libstagefright/mpeg2ts/ESQueue.cpp
+++ b/media/libstagefright/mpeg2ts/ESQueue.cpp
@@ -634,7 +634,7 @@
if (mFormat == NULL) {
mFormat = new MetaData;
- if (!MakeAVCCodecSpecificData(*mFormat, accessUnit)) {
+ if (!MakeAVCCodecSpecificData(*mFormat, accessUnit->data(), accessUnit->size())) {
mFormat.clear();
}
}
@@ -1010,7 +1010,7 @@
}
if (mFormat == NULL) {
mFormat = new MetaData;
- if (!MakeAVCCodecSpecificData(*mFormat, mBuffer)) {
+ if (!MakeAVCCodecSpecificData(*mFormat, mBuffer->data(), mBuffer->size())) {
ALOGW("Creating dummy AVC format for scrambled content");
mFormat = new MetaData;
mFormat->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_AVC);
@@ -1172,7 +1172,9 @@
if (mFormat == NULL) {
mFormat = new MetaData;
- if (!MakeAVCCodecSpecificData(*mFormat, accessUnit)) {
+ if (!MakeAVCCodecSpecificData(*mFormat,
+ accessUnit->data(),
+ accessUnit->size())) {
mFormat.clear();
}
}
diff --git a/packages/MediaComponents/Android.mk b/packages/MediaComponents/Android.mk
index 19e4b24..b0d8e7d 100644
--- a/packages/MediaComponents/Android.mk
+++ b/packages/MediaComponents/Android.mk
@@ -19,7 +19,7 @@
ifneq ($(TARGET_BUILD_PDK),true)
# Build MediaComponents only if this is not a PDK build. MediaComponents won't
# build in PDK builds because frameworks/base/core/java is not available but
-# IMediaSession2.aidl and IMediaSession2Callback.aidl are using classes from
+# IMediaSession2.aidl and IMediaController2.aidl are using classes from
# frameworks/base/core/java.
include $(CLEAR_VARS)
diff --git a/packages/MediaComponents/res/drawable/custom_progress.xml b/packages/MediaComponents/res/drawable/custom_progress.xml
new file mode 100644
index 0000000..9731a6e
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/custom_progress.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
+ <item android:id="@android:id/background">
+ <shape android:shape="rectangle" >
+ <solid android:color="#26000000" />
+ </shape>
+ </item>
+ <item android:id="@android:id/secondaryProgress">
+ <clip>
+ <shape android:shape="rectangle" >
+ <solid android:color="#5Cffffff" />
+ </shape>
+ </clip>
+ </item>
+ <item android:id="@android:id/progress">
+ <clip>
+ <shape android:shape="rectangle" >
+ <solid android:color="#ffffff" />
+ </shape>
+ </clip>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/MediaComponents/res/drawable/custom_progress_thumb.xml b/packages/MediaComponents/res/drawable/custom_progress_thumb.xml
new file mode 100644
index 0000000..2e247f2
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/custom_progress_thumb.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="oval" >
+ <solid android:color="#ffffff" />
+ <size
+ android:height="12dp"
+ android:width="12dp" />
+</shape>
\ No newline at end of file
diff --git a/packages/MediaComponents/res/drawable/ic_high_quality.xml b/packages/MediaComponents/res/drawable/ic_high_quality.xml
index e27d3e2..f76e22f 100644
--- a/packages/MediaComponents/res/drawable/ic_high_quality.xml
+++ b/packages/MediaComponents/res/drawable/ic_high_quality.xml
@@ -1,9 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
+ android:width="34dp"
+ android:height="34dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+
+ <path
+ android:pathData="M0 0h24v24H0z" />
<path
android:fillColor="#FFFFFF"
- android:pathData="M19,4L5,4c-1.11,0 -2,0.9 -2,2v12c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,6c0,-1.1 -0.9,-2 -2,-2zM11,15L9.5,15v-2h-2v2L6,15L6,9h1.5v2.5h2L9.5,9L11,9v6zM18,14c0,0.55 -0.45,1 -1,1h-0.75v1.5h-1.5L14.75,15L14,15c-0.55,0 -1,-0.45 -1,-1v-4c0,-0.55 0.45,-1 1,-1h3c0.55,0 1,0.45 1,1v4zM14.5,13.5h2v-3h-2v3z"/>
+ android:pathData="M19 4H5c-1.11 0-2 0.9-2 2v12c0 1.1 0.89 2 2 2h14c1.1 0 2-0.9 2-2V6c0-1.1-0.9-2-2-2zm-8 11H9.5v-2h-2v2H6V9h1.5v2.5h2V9H11v6zm7-1c0 0.55-0.45 1-1 1h-0.75v1.5h-1.5V15H14c-0.55 0-1-0.45-1-1v-4c0-0.55 0.45 -1 1-1h3c0.55 0 1 0.45 1 1v4zm-3.5-0.5h2v-3h-2v3z" />
</vector>
\ No newline at end of file
diff --git a/packages/MediaComponents/res/drawable/ic_replay_circle_filled.xml b/packages/MediaComponents/res/drawable/ic_replay_circle_filled.xml
index 389396b..a56d5d9 100644
--- a/packages/MediaComponents/res/drawable/ic_replay_circle_filled.xml
+++ b/packages/MediaComponents/res/drawable/ic_replay_circle_filled.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
+ android:width="40dp"
+ android:height="40dp"
android:viewportWidth="24"
android:viewportHeight="24">
diff --git a/packages/MediaComponents/res/drawable/ic_closed_caption_off.xml b/packages/MediaComponents/res/drawable/ic_subtitle_off.xml
similarity index 75%
rename from packages/MediaComponents/res/drawable/ic_closed_caption_off.xml
rename to packages/MediaComponents/res/drawable/ic_subtitle_off.xml
index a79cd11..c0a727a 100644
--- a/packages/MediaComponents/res/drawable/ic_closed_caption_off.xml
+++ b/packages/MediaComponents/res/drawable/ic_subtitle_off.xml
@@ -9,8 +9,7 @@
android:pathData="M0,0h24v24H0V0z" />
<path
android:fillColor="#FFFFFF"
- android:pathData="M19,5.5c0.27,0,0.5,0.23,0.5,0.5v12c0,0.27-0.23,0.5-0.5,0.5H5c-0.28,0-0.5-0.22-0.5-0.5V6c0-0.28,0.22-0.5,0.5-0.5H19
-M19,4H5C3.89,4,3,4.9,3,6v12c0,1.1,0.89,2,2,2h14c1.1,0,2-0.9,2-2V6C21,4.9,20.1,4,19,4L19,4z" />
+ android:pathData="M19.5,5.5v13h-15v-13H19.5z M19,4H5C3.89,4,3,4.9,3,6v12c0,1.1,0.89,2,2,2h14c1.1,0,2-0.9,2-2V6C21,4.9,20.1,4,19,4L19,4z" />
<path
android:fillColor="#FFFFFF"
android:pathData="M11,11H9.5v-0.5h-2v3h2V13H11v1c0,0.55-0.45,1-1,1H7c-0.55,0-1-0.45-1-1v-4c0-0.55,0.45-1,1-1h3c0.55,0,1,0.45,1,1V11z" />
diff --git a/packages/MediaComponents/res/drawable/ic_subtitle_on.xml b/packages/MediaComponents/res/drawable/ic_subtitle_on.xml
new file mode 100644
index 0000000..7c91c06
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_subtitle_on.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+
+ <path
+ android:pathData="M0 0h24v24H0z" />
+ <path
+ android:fillColor="#FFFFFF"
+ android:pathData="M19 4H5c-1.11 0-2 0.9-2 2v12c0 1.1 0.89 2 2 2h14c1.1 0 2-0.9 2-2V6c0-1.1-0.9-2-2-2zm-8 7H9.5v-0.5h-2v3h2V13H11v1c0 0.55-0.45 1-1 1H7c-0.55 0-1-0.45-1-1v-4c0-0.55 0.45 -1 1-1h3c0.55 0 1 0.45 1 1v1zm7 0h-1.5v-0.5h-2v3h2V13H18v1c0 0.55-0.45 1-1 1h-3c-0.55 0-1-0.45-1-1v-4c0-0.55 0.45 -1 1-1h3c0.55 0 1 0.45 1 1v1z" />
+</vector>
\ No newline at end of file
diff --git a/packages/MediaComponents/res/layout/media_controller.xml b/packages/MediaComponents/res/layout/media_controller.xml
index 8488c84..eab2309 100644
--- a/packages/MediaComponents/res/layout/media_controller.xml
+++ b/packages/MediaComponents/res/layout/media_controller.xml
@@ -129,10 +129,10 @@
<SeekBar
android:id="@+id/mediacontroller_progress"
android:layout_width="match_parent"
- android:layout_height="32dp"
- android:padding="0dp"
- android:progressTint="#FFFFFFFF"
- android:thumbTint="#FFFFFFFF"/>
+ android:layout_height="12dp"
+ android:maxHeight="2dp"
+ android:minHeight="2dp"
+ android:padding="0dp"/>
<RelativeLayout
android:layout_width="match_parent"
@@ -199,6 +199,7 @@
<ImageButton
android:id="@+id/subtitle"
android:scaleType="fitCenter"
+ android:visibility="gone"
style="@style/BottomBarButton.CC" />
<ImageButton
android:id="@+id/fullscreen"
@@ -224,9 +225,6 @@
android:layout_height="wrap_content" />
<ImageButton
- android:id="@+id/aspect_ratio"
- style="@style/BottomBarButton.AspectRatio" />
- <ImageButton
android:id="@+id/video_quality"
style="@style/BottomBarButton.VideoQuality" />
<ImageButton
diff --git a/packages/MediaComponents/res/layout/settings_list_item.xml b/packages/MediaComponents/res/layout/settings_list_item.xml
index e7522b7..81b3275 100644
--- a/packages/MediaComponents/res/layout/settings_list_item.xml
+++ b/packages/MediaComponents/res/layout/settings_list_item.xml
@@ -28,14 +28,6 @@
android:orientation="horizontal">
<ImageView
- android:id="@+id/check"
- android:layout_width="@dimen/MediaControlView2_settings_icon_size"
- android:layout_height="@dimen/MediaControlView2_settings_icon_size"
- android:gravity="center"
- android:paddingLeft="2dp"
- android:src="@drawable/ic_check"/>
-
- <ImageView
android:id="@+id/icon"
android:layout_width="@dimen/MediaControlView2_settings_icon_size"
android:layout_height="@dimen/MediaControlView2_settings_icon_size"
diff --git a/packages/MediaComponents/res/layout/sub_settings_list_item.xml b/packages/MediaComponents/res/layout/sub_settings_list_item.xml
new file mode 100644
index 0000000..9de7f2b
--- /dev/null
+++ b/packages/MediaComponents/res/layout/sub_settings_list_item.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="@dimen/MediaControlView2_settings_width"
+ android:layout_height="@dimen/MediaControlView2_settings_height"
+ android:orientation="horizontal"
+ android:background="@color/black_transparent_70">
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/MediaControlView2_settings_height"
+ android:paddingRight="2dp"
+ android:gravity="center"
+ android:orientation="horizontal">
+
+ <ImageView
+ android:id="@+id/check"
+ android:layout_width="@dimen/MediaControlView2_settings_icon_size"
+ android:layout_height="@dimen/MediaControlView2_settings_icon_size"
+ android:gravity="center"
+ android:paddingLeft="2dp"
+ android:src="@drawable/ic_check"/>
+ </LinearLayout>
+
+ <RelativeLayout
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/MediaControlView2_settings_height"
+ android:gravity="center"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/text"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/MediaControlView2_text_width"
+ android:paddingLeft="2dp"
+ android:textColor="@color/white"
+ android:textSize="@dimen/MediaControlView2_settings_main_text_size"/>
+ </RelativeLayout>
+
+</LinearLayout>
diff --git a/packages/MediaComponents/res/values/arrays.xml b/packages/MediaComponents/res/values/arrays.xml
new file mode 100644
index 0000000..1187320
--- /dev/null
+++ b/packages/MediaComponents/res/values/arrays.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <integer-array name="speed_multiplied_by_100">
+ <item>25</item>
+ <item>50</item>
+ <item>75</item>
+ <item>100</item>
+ <item>125</item>
+ <item>150</item>
+ <item>200</item>
+ </integer-array>
+</resources>
\ No newline at end of file
diff --git a/packages/MediaComponents/res/values/strings.xml b/packages/MediaComponents/res/values/strings.xml
index 305cef4..c80aaf3 100644
--- a/packages/MediaComponents/res/values/strings.xml
+++ b/packages/MediaComponents/res/values/strings.xml
@@ -111,15 +111,18 @@
<string name="MediaControlView2_audio_track_none_text">None</string>
<string name="MediaControlView2_video_quality_text">Video quality</string>
<string name="MediaControlView2_video_quality_auto_text">Auto</string>
- <string name="MediaControlView2_playback_speed_text">Playback speed</string>
- <string name="MediaControlView2_playback_speed_0_25x_text">0.25x</string>
- <string name="MediaControlView2_playback_speed_0_5x_text">0.5x</string>
- <string name="MediaControlView2_playback_speed_0_75x_text">0.75x</string>
- <string name="MediaControlView2_playback_speed_1x_text">Normal</string>
- <string name="MediaControlView2_playback_speed_1_25x_text">1.25x</string>
- <string name="MediaControlView2_playback_speed_1_5x_text">1.5x</string>
- <string name="MediaControlView2_playback_speed_2x_text">2x</string>
<string name="MediaControlView2_help_text">Help & feedback</string>
+ <string name="MediaControlView2_playback_speed_text">Playback speed</string>
+ <string-array name="MediaControlView2_playback_speeds">
+ <item>0.25x</item>
+ <item>0.5x</item>
+ <item>0.75x</item>
+ <item>Normal</item>
+ <item>1.25x</item>
+ <item>1.5x</item>
+ <item>2x</item>
+ </string-array>
+
<!-- Text for displaying subtitle track number. -->
<string name="MediaControlView2_subtitle_track_number_text">
Track <xliff:g id="track_number" example="1">%1$s</xliff:g>
diff --git a/packages/MediaComponents/res/values/style.xml b/packages/MediaComponents/res/values/style.xml
index 23c7bc9..e6ed039 100644
--- a/packages/MediaComponents/res/values/style.xml
+++ b/packages/MediaComponents/res/values/style.xml
@@ -4,6 +4,7 @@
<item name="android:background">@null</item>
<item name="android:layout_width">70dp</item>
<item name="android:layout_height">40dp</item>
+ <item name="android:visibility">gone</item>
</style>
<style name="TransportControlsButton.Previous">
@@ -55,7 +56,7 @@
</style>
<style name="BottomBarButton.CC">
- <item name="android:src">@drawable/ic_media_subtitle_disabled</item>
+ <item name="android:src">@drawable/ic_subtitle_off</item>
</style>
<style name="BottomBarButton.FullScreen">
@@ -74,12 +75,8 @@
<item name="android:src">@drawable/ic_settings</item>
</style>
- <style name="BottomBarButton.AspectRatio">
- <item name="android:src">@drawable/ic_aspect_ratio</item>
- </style>
-
<style name="BottomBarButton.Mute">
- <item name="android:src">@drawable/ic_mute</item>
+ <item name="android:src">@drawable/ic_unmute</item>
</style>
<style name="BottomBarButton.VideoQuality">
diff --git a/packages/MediaComponents/runcts.sh b/packages/MediaComponents/runcts.sh
index 0cf0e44..61b1a1e 100644
--- a/packages/MediaComponents/runcts.sh
+++ b/packages/MediaComponents/runcts.sh
@@ -23,6 +23,7 @@
echo ' -h|--help: This help'
echo ' --skip: Skip build and flash. Just rerun-tests'
echo ' --min: Only rebuild tests and updatable library.'
+ echo ' --test: Only rebuild tests'
echo ' -s [device_id]: Specify a device name to run test against.'
echo ' You can define ${ADBHOST} instead.'
echo ' -r [count]: Repeat tests for given count. It will stop when fails.'
@@ -58,6 +59,7 @@
while true; do
local OPTION_SKIP="false"
local OPTION_MIN="false"
+ local OPTION_TEST="false"
local OPTION_REPEAT_COUNT="1"
local OPTION_IGNORE="false"
local OPTION_TEST_TARGET="${DEFAULT_TEST_TARGET}"
@@ -74,6 +76,9 @@
--min)
OPTION_MIN="true"
;;
+ --test)
+ OPTION_TEST="true"
+ ;;
-s)
shift
adbhost_local=${1}
@@ -133,36 +138,43 @@
fi
# Build test apk and required apk.
- local build_targets="${BUILD_TARGETS[@]}"
- if [[ "${OPTION_MIN}" != "true" ]]; then
- build_targets="${build_targets} droid"
+ local build_targets
+ if [[ "${OPTION_TEST}" == "true" ]]; then
+ build_targets="${INSTALL_TARGETS[@]}"
+ elif [[ "${OPTION_MIN}" == "true" ]]; then
+ build_targets="${BUILD_TARGETS[@]}"
+ else
+ build_targets="${BUILD_TARGETS[@]} droid"
fi
m ${build_targets} -j || break
- local device_build_type="$(${adb} shell getprop ro.build.type)"
- if [[ "${device_build_type}" == "user" ]]; then
- # User build. Cannot adb sync
- ${adb} reboot bootloader
- fastboot flashall
- else
- ${adb} root
- local device_verity_mode="$(${adb} shell getprop ro.boot.veritymode)"
- if [[ "${device_verity_mode}" != "disabled" ]]; then
- ${adb} disable-verity
- ${adb} reboot
- ${adb} wait-for-device || break
+ if [[ "${OPTION_TEST}" != "true" ]]; then
+ # Flash only when needed
+ local device_build_type="$(${adb} shell getprop ro.build.type)"
+ if [[ "${device_build_type}" == "user" ]]; then
+ # User build. Cannot adb sync
+ ${adb} reboot bootloader
+ fastboot flashall
+ else
${adb} root
+ local device_verity_mode="$(${adb} shell getprop ro.boot.veritymode)"
+ if [[ "${device_verity_mode}" != "disabled" ]]; then
+ ${adb} disable-verity
+ ${adb} reboot
+ ${adb} wait-for-device || break
+ ${adb} root
+ fi
+ ${adb} remount
+ ${adb} shell stop
+ ${adb} shell setprop log.tag.MediaSessionService DEBUG
+ ${adb} sync
+ ${adb} shell start
fi
- ${adb} remount
- ${adb} shell stop
- ${adb} shell setprop log.tag.MediaSessionService DEBUG
- ${adb} sync
- ${adb} shell start
+ ${adb} wait-for-device || break
+ # Ensure package manager is loaded.
+ # TODO(jaewan): Find better way to wait
+ sleep 15
fi
- ${adb} wait-for-device || break
- # Ensure package manager is loaded.
- # TODO(jaewan): Find better way to wait
- sleep 15
# Install apks
local install_failed="false"
diff --git a/packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl b/packages/MediaComponents/src/com/android/media/IMediaController2.aidl
similarity index 71%
rename from packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl
rename to packages/MediaComponents/src/com/android/media/IMediaController2.aidl
index 3d812f8..d6c8e21 100644
--- a/packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl
+++ b/packages/MediaComponents/src/com/android/media/IMediaController2.aidl
@@ -23,21 +23,27 @@
import com.android.media.IMediaSession2;
/**
- * Interface from MediaSession2 to MediaSession2Record.
+ * Interface from MediaSession2 to MediaController2.
* <p>
* Keep this interface oneway. Otherwise a malicious app may implement fake version of this,
* and holds calls from session to make session owner(s) frozen.
*/
-oneway interface IMediaSession2Callback {
- void onPlaybackStateChanged(in Bundle state);
- void onPlaylistChanged(in List<Bundle> playlist);
+// TODO(jaewan): (Post P) Handle when the playlist becomes too huge.
+// Note that ParcelledSliceList isn't a good idea for the purpose. (see: b/37493677)
+oneway interface IMediaController2 {
+ void onPlayerStateChanged(int state);
+ void onPositionChanged(long eventTimeMs, long positionMs);
+ void onPlaybackSpeedChanged(float speed);
+ void onBufferedPositionChanged(long bufferedPositionMs);
+ void onPlaylistChanged(in List<Bundle> playlist, in Bundle metadata);
+ void onPlaylistMetadataChanged(in Bundle metadata);
void onPlaylistParamsChanged(in Bundle params);
void onPlaybackInfoChanged(in Bundle playbackInfo);
- // TODO(jaewan): Handle when the playlist becomes too huge.
- void onConnected(IMediaSession2 sessionBinder, in Bundle commandGroup, in Bundle playbackState,
- in Bundle playbackInfo, in Bundle params, in List<Bundle> playlist,
- in PendingIntent sessionActivity);
+ void onConnected(IMediaSession2 sessionBinder, in Bundle commandGroup,
+ int playerState, long positionEventTimeMs, long positionMs, float playbackSpeed,
+ long bufferedPositionMs, in Bundle playbackInfo, in Bundle params,
+ in List<Bundle> playlist, in PendingIntent sessionActivity);
void onDisconnected();
void onCustomLayoutChanged(in List<Bundle> commandButtonlist);
diff --git a/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl b/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl
index ef7120d..a241abc 100644
--- a/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl
+++ b/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl
@@ -20,55 +20,61 @@
import android.os.ResultReceiver;
import android.net.Uri;
-import com.android.media.IMediaSession2Callback;
+import com.android.media.IMediaController2;
/**
- * Interface to MediaSession2.
+ * Interface from MediaController2 to MediaSession2.
* <p>
* Keep this interface oneway. Otherwise a malicious app may implement fake version of this,
* and holds calls from session to make session owner(s) frozen.
*/
+ // TODO(jaewan): (Post P) Handle when the playlist becomes too huge.
+ // Note that ParcelledSliceList isn't a good idea for the purpose. (see: b/37493677)
oneway interface IMediaSession2 {
// TODO(jaewan): add onCommand() to send private command
- // TODO(jaewan): Due to the nature of oneway calls, APIs can be called in out of order
- // Add id for individual calls to address this.
- // TODO(jaewan): We may consider to add another binder just for the connection
+ // TODO(jaewan): (Post P) We may consider to add another binder just for the connection
// not to expose other methods to the controller whose connection wasn't accepted.
// But this would be enough for now because it's the same as existing
// MediaBrowser and MediaBrowserService.
- void connect(IMediaSession2Callback caller, String callingPackage);
- void release(IMediaSession2Callback caller);
+ void connect(IMediaController2 caller, String callingPackage);
+ void release(IMediaController2 caller);
- void setVolumeTo(IMediaSession2Callback caller, int value, int flags);
- void adjustVolume(IMediaSession2Callback caller, int direction, int flags);
+ void setVolumeTo(IMediaController2 caller, int value, int flags);
+ void adjustVolume(IMediaController2 caller, int direction, int flags);
//////////////////////////////////////////////////////////////////////////////////////////////
// send command
//////////////////////////////////////////////////////////////////////////////////////////////
- void sendTransportControlCommand(IMediaSession2Callback caller,
+ void sendTransportControlCommand(IMediaController2 caller,
int commandCode, in Bundle args);
- void sendCustomCommand(IMediaSession2Callback caller, in Bundle command, in Bundle args,
+ void sendCustomCommand(IMediaController2 caller, in Bundle command, in Bundle args,
in ResultReceiver receiver);
- void prepareFromUri(IMediaSession2Callback caller, in Uri uri, in Bundle extras);
- void prepareFromSearch(IMediaSession2Callback caller, String query, in Bundle extras);
- void prepareFromMediaId(IMediaSession2Callback caller, String mediaId, in Bundle extras);
- void playFromUri(IMediaSession2Callback caller, in Uri uri, in Bundle extras);
- void playFromSearch(IMediaSession2Callback caller, String query, in Bundle extras);
- void playFromMediaId(IMediaSession2Callback caller, String mediaId, in Bundle extras);
- void setRating(IMediaSession2Callback caller, String mediaId, in Bundle rating);
+ void prepareFromUri(IMediaController2 caller, in Uri uri, in Bundle extras);
+ void prepareFromSearch(IMediaController2 caller, String query, in Bundle extras);
+ void prepareFromMediaId(IMediaController2 caller, String mediaId, in Bundle extras);
+ void playFromUri(IMediaController2 caller, in Uri uri, in Bundle extras);
+ void playFromSearch(IMediaController2 caller, String query, in Bundle extras);
+ void playFromMediaId(IMediaController2 caller, String mediaId, in Bundle extras);
+ void setRating(IMediaController2 caller, String mediaId, in Bundle rating);
+
+ void setPlaylist(IMediaController2 caller, in List<Bundle> playlist, in Bundle metadata);
+ void updatePlaylistMetadata(IMediaController2 caller, in Bundle metadata);
+ void addPlaylistItem(IMediaController2 caller, int index, in Bundle mediaItem);
+ void removePlaylistItem(IMediaController2 caller, in Bundle mediaItem);
+ void replacePlaylistItem(IMediaController2 caller, int index, in Bundle mediaItem);
//////////////////////////////////////////////////////////////////////////////////////////////
// library service specific
//////////////////////////////////////////////////////////////////////////////////////////////
- void getLibraryRoot(IMediaSession2Callback caller, in Bundle rootHints);
- void getItem(IMediaSession2Callback caller, String mediaId);
- void getChildren(IMediaSession2Callback caller, String parentId, int page, int pageSize,
+ void getLibraryRoot(IMediaController2 caller, in Bundle rootHints);
+ void getItem(IMediaController2 caller, String mediaId);
+ void getChildren(IMediaController2 caller, String parentId, int page, int pageSize,
in Bundle extras);
- void search(IMediaSession2Callback caller, String query, in Bundle extras);
- void getSearchResult(IMediaSession2Callback caller, String query, int page, int pageSize,
+ void search(IMediaController2 caller, String query, in Bundle extras);
+ void getSearchResult(IMediaController2 caller, String query, int page, int pageSize,
in Bundle extras);
- void subscribe(IMediaSession2Callback caller, String parentId, in Bundle extras);
- void unsubscribe(IMediaSession2Callback caller, String parentId);
+ void subscribe(IMediaController2 caller, String parentId, in Bundle extras);
+ void unsubscribe(IMediaController2 caller, String parentId);
}
diff --git a/packages/MediaComponents/src/com/android/media/MediaController2Impl.java b/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
index 96fdda0..8c6cfda 100644
--- a/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
@@ -16,7 +16,18 @@
package com.android.media;
-import static android.media.MediaSession2.*;
+import static android.media.MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME;
+import static android.media.MediaSession2.COMMAND_CODE_PLAYLIST_ADD_ITEM;
+import static android.media.MediaSession2.COMMAND_CODE_PLAYLIST_REMOVE_ITEM;
+import static android.media.MediaSession2.COMMAND_CODE_PLAYLIST_REPLACE_ITEM;
+import static android.media.MediaSession2.COMMAND_CODE_PLAYLIST_SET_LIST;
+import static android.media.MediaSession2.COMMAND_CODE_PLAYLIST_SET_LIST_METADATA;
+import static android.media.MediaSession2.COMMAND_CODE_PLAY_FROM_MEDIA_ID;
+import static android.media.MediaSession2.COMMAND_CODE_PLAY_FROM_SEARCH;
+import static android.media.MediaSession2.COMMAND_CODE_PLAY_FROM_URI;
+import static android.media.MediaSession2.COMMAND_CODE_PREPARE_FROM_MEDIA_ID;
+import static android.media.MediaSession2.COMMAND_CODE_PREPARE_FROM_SEARCH;
+import static android.media.MediaSession2.COMMAND_CODE_PREPARE_FROM_URI;
import android.app.PendingIntent;
import android.content.ComponentName;
@@ -28,13 +39,13 @@
import android.media.MediaController2.ControllerCallback;
import android.media.MediaController2.PlaybackInfo;
import android.media.MediaItem2;
+import android.media.MediaMetadata2;
import android.media.MediaSession2;
import android.media.MediaSession2.Command;
import android.media.MediaSession2.CommandButton;
import android.media.MediaSession2.CommandGroup;
import android.media.MediaSession2.PlaylistParams;
import android.media.MediaSessionService2;
-import android.media.PlaybackState2;
import android.media.Rating2;
import android.media.SessionToken2;
import android.media.update.MediaController2Provider;
@@ -61,7 +72,7 @@
private final Context mContext;
private final Object mLock = new Object();
- private final MediaSession2CallbackStub mSessionCallbackStub;
+ private final MediaController2Stub mControllerStub;
private final SessionToken2 mToken;
private final ControllerCallback mCallback;
private final Executor mCallbackExecutor;
@@ -72,12 +83,22 @@
@GuardedBy("mLock")
private boolean mIsReleased;
@GuardedBy("mLock")
- private PlaybackState2 mPlaybackState;
- @GuardedBy("mLock")
private List<MediaItem2> mPlaylist;
@GuardedBy("mLock")
+ private MediaMetadata2 mPlaylistMetadata;
+ @GuardedBy("mLock")
private PlaylistParams mPlaylistParams;
@GuardedBy("mLock")
+ private int mPlayerState;
+ @GuardedBy("mLock")
+ private long mPositionEventTimeMs;
+ @GuardedBy("mLock")
+ private long mPositionMs;
+ @GuardedBy("mLock")
+ private float mPlaybackSpeed;
+ @GuardedBy("mLock")
+ private long mBufferedPositionMs;
+ @GuardedBy("mLock")
private PlaybackInfo mPlaybackInfo;
@GuardedBy("mLock")
private PendingIntent mSessionActivity;
@@ -110,7 +131,7 @@
throw new IllegalArgumentException("executor shouldn't be null");
}
mContext = context;
- mSessionCallbackStub = new MediaSession2CallbackStub(this);
+ mControllerStub = new MediaController2Stub(this);
mToken = token;
mCallback = callback;
mCallbackExecutor = executor;
@@ -191,7 +212,7 @@
private void connectToSession(IMediaSession2 sessionBinder) {
try {
- sessionBinder.connect(mSessionCallbackStub, mContext.getPackageName());
+ sessionBinder.connect(mControllerStub, mContext.getPackageName());
} catch (RemoteException e) {
Log.w(TAG, "Failed to call connection request. Framework will retry"
+ " automatically");
@@ -216,12 +237,12 @@
}
binder = mSessionBinder;
mSessionBinder = null;
- mSessionCallbackStub.destroy();
+ mControllerStub.destroy();
}
if (binder != null) {
try {
binder.asBinder().unlinkToDeath(mDeathRecipient, 0);
- binder.release(mSessionCallbackStub);
+ binder.release(mControllerStub);
} catch (RemoteException e) {
// No-op.
}
@@ -235,8 +256,8 @@
return mSessionBinder;
}
- MediaSession2CallbackStub getControllerStub() {
- return mSessionCallbackStub;
+ MediaController2Stub getControllerStub() {
+ return mControllerStub;
}
Executor getCallbackExecutor() {
@@ -331,7 +352,7 @@
final IMediaSession2 binder = mSessionBinder;
if (binder != null) {
try {
- binder.sendTransportControlCommand(mSessionCallbackStub, commandCode, args);
+ binder.sendTransportControlCommand(mControllerStub, commandCode, args);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to the service or the session is gone", e);
}
@@ -354,7 +375,7 @@
final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_PLAYBACK_SET_VOLUME);
if (binder != null) {
try {
- binder.setVolumeTo(mSessionCallbackStub, value, flags);
+ binder.setVolumeTo(mControllerStub, value, flags);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to the service or the session is gone", e);
}
@@ -369,7 +390,7 @@
final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_PLAYBACK_SET_VOLUME);
if (binder != null) {
try {
- binder.adjustVolume(mSessionCallbackStub, direction, flags);
+ binder.adjustVolume(mControllerStub, direction, flags);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to the service or the session is gone", e);
}
@@ -386,7 +407,7 @@
}
if (binder != null) {
try {
- binder.prepareFromUri(mSessionCallbackStub, uri, extras);
+ binder.prepareFromUri(mControllerStub, uri, extras);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to the service or the session is gone", e);
}
@@ -403,7 +424,7 @@
}
if (binder != null) {
try {
- binder.prepareFromSearch(mSessionCallbackStub, query, extras);
+ binder.prepareFromSearch(mControllerStub, query, extras);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to the service or the session is gone", e);
}
@@ -420,7 +441,7 @@
}
if (binder != null) {
try {
- binder.prepareFromMediaId(mSessionCallbackStub, mediaId, extras);
+ binder.prepareFromMediaId(mControllerStub, mediaId, extras);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to the service or the session is gone", e);
}
@@ -437,7 +458,7 @@
}
if (binder != null) {
try {
- binder.playFromUri(mSessionCallbackStub, uri, extras);
+ binder.playFromUri(mControllerStub, uri, extras);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to the service or the session is gone", e);
}
@@ -454,7 +475,7 @@
}
if (binder != null) {
try {
- binder.playFromSearch(mSessionCallbackStub, query, extras);
+ binder.playFromSearch(mControllerStub, query, extras);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to the service or the session is gone", e);
}
@@ -471,7 +492,7 @@
}
if (binder != null) {
try {
- binder.playFromMediaId(mSessionCallbackStub, mediaId, extras);
+ binder.playFromMediaId(mControllerStub, mediaId, extras);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to the service or the session is gone", e);
}
@@ -492,7 +513,7 @@
final IMediaSession2 binder = mSessionBinder;
if (binder != null) {
try {
- binder.setRating(mSessionCallbackStub, mediaId, rating.toBundle());
+ binder.setRating(mControllerStub, mediaId, rating.toBundle());
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to the service or the session is gone", e);
}
@@ -509,7 +530,7 @@
final IMediaSession2 binder = getSessionBinderIfAble(command);
if (binder != null) {
try {
- binder.sendCustomCommand(mSessionCallbackStub, command.toBundle(), args, cb);
+ binder.sendCustomCommand(mControllerStub, command.toBundle(), args, cb);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to the service or the session is gone", e);
}
@@ -526,6 +547,51 @@
}
@Override
+ public void setPlaylist_impl(List<MediaItem2> list, MediaMetadata2 metadata) {
+ if (list == null) {
+ throw new IllegalArgumentException("list shouldn't be null");
+ }
+ final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_PLAYLIST_SET_LIST);
+ if (binder != null) {
+ List<Bundle> bundleList = new ArrayList<>();
+ for (int i = 0; i < list.size(); i++) {
+ bundleList.add(list.get(i).toBundle());
+ }
+ Bundle metadataBundle = (metadata == null) ? null : metadata.toBundle();
+ try {
+ binder.setPlaylist(mControllerStub, bundleList, metadataBundle);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Cannot connect to the service or the session is gone", e);
+ }
+ } else {
+ Log.w(TAG, "Session isn't active", new IllegalStateException());
+ }
+ }
+
+ @Override
+ public MediaMetadata2 getPlaylistMetadata_impl() {
+ synchronized (mLock) {
+ return mPlaylistMetadata;
+ }
+ }
+
+ @Override
+ public void updatePlaylistMetadata_impl(MediaMetadata2 metadata) {
+ final IMediaSession2 binder = getSessionBinderIfAble(
+ COMMAND_CODE_PLAYLIST_SET_LIST_METADATA);
+ if (binder != null) {
+ Bundle metadataBundle = (metadata == null) ? null : metadata.toBundle();
+ try {
+ binder.updatePlaylistMetadata(mControllerStub, metadataBundle);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Cannot connect to the service or the session is gone", e);
+ }
+ } else {
+ Log.w(TAG, "Session isn't active", new IllegalStateException());
+ }
+ }
+
+ @Override
public void prepare_impl() {
sendTransportControlCommand(MediaSession2.COMMAND_CODE_PLAYBACK_PREPARE);
}
@@ -566,40 +632,60 @@
}
@Override
- public PlaybackState2 getPlaybackState_impl() {
- synchronized (mLock) {
- return mPlaybackState;
- }
- }
-
- @Override
public void addPlaylistItem_impl(int index, MediaItem2 item) {
- // TODO(jaewan): Implement (b/73149584)
if (index < 0) {
throw new IllegalArgumentException("index shouldn't be negative");
}
if (item == null) {
throw new IllegalArgumentException("item shouldn't be null");
}
+ final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_PLAYLIST_ADD_ITEM);
+ if (binder != null) {
+ try {
+ binder.addPlaylistItem(mControllerStub, index, item.toBundle());
+ } catch (RemoteException e) {
+ Log.w(TAG, "Cannot connect to the service or the session is gone", e);
+ }
+ } else {
+ Log.w(TAG, "Session isn't active", new IllegalStateException());
+ }
}
@Override
public void removePlaylistItem_impl(MediaItem2 item) {
- // TODO(jaewan): Implement (b/73149584)
if (item == null) {
throw new IllegalArgumentException("item shouldn't be null");
}
+ final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_PLAYLIST_REMOVE_ITEM);
+ if (binder != null) {
+ try {
+ binder.removePlaylistItem(mControllerStub, item.toBundle());
+ } catch (RemoteException e) {
+ Log.w(TAG, "Cannot connect to the service or the session is gone", e);
+ }
+ } else {
+ Log.w(TAG, "Session isn't active", new IllegalStateException());
+ }
}
@Override
public void replacePlaylistItem_impl(int index, MediaItem2 item) {
- // TODO: Implement this (b/73149407)
if (index < 0) {
throw new IllegalArgumentException("index shouldn't be negative");
}
if (item == null) {
throw new IllegalArgumentException("item shouldn't be null");
}
+ final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_PLAYLIST_REPLACE_ITEM);
+ if (binder != null) {
+ try {
+ binder.replacePlaylistItem(mControllerStub, index, item.toBundle());
+ } catch (RemoteException e) {
+ Log.w(TAG, "Cannot connect to the service or the session is gone", e);
+ }
+ } else {
+ Log.w(TAG, "Session isn't active", new IllegalStateException());
+ }
}
@Override
@@ -628,26 +714,32 @@
@Override
public int getPlayerState_impl() {
- // TODO(jaewan): Implement
- return 0;
+ synchronized (mLock) {
+ return mPlayerState;
+ }
}
@Override
public long getPosition_impl() {
- // TODO(jaewan): Implement
- return 0;
+ synchronized (mLock) {
+ long timeDiff = System.currentTimeMillis() - mPositionEventTimeMs;
+ long expectedPosition = mPositionMs + (long) (mPlaybackSpeed * timeDiff);
+ return Math.max(0, expectedPosition);
+ }
}
@Override
public float getPlaybackSpeed_impl() {
- // TODO(jaewan): Implement
- return 0;
+ synchronized (mLock) {
+ return mPlaybackSpeed;
+ }
}
@Override
public long getBufferedPosition_impl() {
- // TODO(jaewan): Implement
- return 0;
+ synchronized (mLock) {
+ return mBufferedPositionMs;
+ }
}
@Override
@@ -656,15 +748,52 @@
return null;
}
- void pushPlaybackStateChanges(final PlaybackState2 state) {
+ void pushPlayerStateChanges(final int state) {
synchronized (mLock) {
- mPlaybackState = state;
+ mPlayerState = state;
}
mCallbackExecutor.execute(() -> {
if (!mInstance.isConnected()) {
return;
}
- mCallback.onPlaybackStateChanged(mInstance, state);
+ mCallback.onPlayerStateChanged(mInstance, state);
+ });
+ }
+
+ void pushPositionChanges(final long eventTimeMs, final long positionMs) {
+ synchronized (mLock) {
+ mPositionEventTimeMs = eventTimeMs;
+ mPositionMs = positionMs;
+ }
+ mCallbackExecutor.execute(() -> {
+ if (!mInstance.isConnected()) {
+ return;
+ }
+ mCallback.onPositionChanged(mInstance, eventTimeMs, positionMs);
+ });
+ }
+
+ void pushPlaybackSpeedChanges(final float speed) {
+ synchronized (mLock) {
+ mPlaybackSpeed = speed;
+ }
+ mCallbackExecutor.execute(() -> {
+ if (!mInstance.isConnected()) {
+ return;
+ }
+ mCallback.onPlaybackSpeedChanged(mInstance, speed);
+ });
+ }
+
+ void pushBufferedPositionChanges(final long bufferedPositionMs) {
+ synchronized (mLock) {
+ mBufferedPositionMs = bufferedPositionMs;
+ }
+ mCallbackExecutor.execute(() -> {
+ if (!mInstance.isConnected()) {
+ return;
+ }
+ mCallback.onBufferedPositionChanged(mInstance, bufferedPositionMs);
});
}
@@ -692,21 +821,42 @@
});
}
- void pushPlaylistChanges(final List<MediaItem2> playlist) {
+ void pushPlaylistChanges(final List<MediaItem2> playlist, final MediaMetadata2 metadata) {
synchronized (mLock) {
mPlaylist = playlist;
- mCallbackExecutor.execute(() -> {
- if (!mInstance.isConnected()) {
- return;
- }
- mCallback.onPlaylistChanged(mInstance, playlist);
- });
+ mPlaylistMetadata = metadata;
}
+ mCallbackExecutor.execute(() -> {
+ if (!mInstance.isConnected()) {
+ return;
+ }
+ // TODO(jaewan): Fix public API not to take playlistAgent.
+ mCallback.onPlaylistChanged(mInstance, null, playlist, metadata);
+ });
+ }
+
+ public void pushPlaylistMetadataChanges(MediaMetadata2 metadata) {
+ synchronized (mLock) {
+ mPlaylistMetadata = metadata;
+ }
+ mCallbackExecutor.execute(() -> {
+ if (!mInstance.isConnected()) {
+ return;
+ }
+ // TODO(jaewan): Fix public API not to take playlistAgent.
+ mCallback.onPlaylistMetadataChanged(mInstance, null, metadata);
+ });
}
// Should be used without a lock to prevent potential deadlock.
void onConnectedNotLocked(IMediaSession2 sessionBinder,
- final CommandGroup allowedCommands, final PlaybackState2 state, final PlaybackInfo info,
+ final CommandGroup allowedCommands,
+ final int playerState,
+ final long positionEventTimeMs,
+ final long positionMs,
+ final float playbackSpeed,
+ final long bufferedPositionMs,
+ final PlaybackInfo info,
final PlaylistParams params, final List<MediaItem2> playlist,
final PendingIntent sessionActivity) {
if (DEBUG) {
@@ -731,7 +881,11 @@
return;
}
mAllowedCommands = allowedCommands;
- mPlaybackState = state;
+ mPlayerState = playerState;
+ mPositionEventTimeMs = positionEventTimeMs;
+ mPositionMs = positionMs;
+ mPlaybackSpeed = playbackSpeed;
+ mBufferedPositionMs = bufferedPositionMs;
mPlaybackInfo = info;
mPlaylistParams = params;
mPlaylist = playlist;
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2CallbackStub.java b/packages/MediaComponents/src/com/android/media/MediaController2Stub.java
similarity index 80%
rename from packages/MediaComponents/src/com/android/media/MediaSession2CallbackStub.java
rename to packages/MediaComponents/src/com/android/media/MediaController2Stub.java
index 451368f..99bdfce 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSession2CallbackStub.java
+++ b/packages/MediaComponents/src/com/android/media/MediaController2Stub.java
@@ -20,11 +20,11 @@
import android.content.Context;
import android.media.MediaController2;
import android.media.MediaItem2;
+import android.media.MediaMetadata2;
import android.media.MediaSession2.Command;
import android.media.MediaSession2.CommandButton;
import android.media.MediaSession2.CommandGroup;
import android.media.MediaSession2.PlaylistParams;
-import android.media.PlaybackState2;
import android.os.Bundle;
import android.os.ResultReceiver;
import android.text.TextUtils;
@@ -37,13 +37,13 @@
import java.util.ArrayList;
import java.util.List;
-public class MediaSession2CallbackStub extends IMediaSession2Callback.Stub {
- private static final String TAG = "MS2CallbackStub";
+public class MediaController2Stub extends IMediaController2.Stub {
+ private static final String TAG = "MediaController2Stub";
private static final boolean DEBUG = true; // TODO(jaewan): Change
private final WeakReference<MediaController2Impl> mController;
- MediaSession2CallbackStub(MediaController2Impl controller) {
+ MediaController2Stub(MediaController2Impl controller) {
mController = new WeakReference<>(controller);
}
@@ -68,7 +68,7 @@
}
@Override
- public void onPlaybackStateChanged(Bundle state) throws RuntimeException {
+ public void onPlayerStateChanged(int state) {
final MediaController2Impl controller;
try {
controller = getController();
@@ -76,12 +76,59 @@
Log.w(TAG, "Don't fail silently here. Highly likely a bug");
return;
}
- controller.pushPlaybackStateChanges(
- PlaybackState2.fromBundle(controller.getContext(), state));
+ controller.pushPlayerStateChanges(state);
}
@Override
- public void onPlaylistChanged(List<Bundle> playlistBundle) throws RuntimeException {
+ public void onPositionChanged(long eventTimeMs, long positionMs) {
+ final MediaController2Impl controller;
+ try {
+ controller = getController();
+ } catch (IllegalStateException e) {
+ Log.w(TAG, "Don't fail silently here. Highly likely a bug");
+ return;
+ }
+ if (eventTimeMs < 0) {
+ Log.w(TAG, "onPositionChanged(): Ignoring negative eventTimeMs");
+ return;
+ }
+ if (positionMs < 0) {
+ Log.w(TAG, "onPositionChanged(): Ignoring negative positionMs");
+ return;
+ }
+ controller.pushPositionChanges(eventTimeMs, positionMs);
+ }
+
+ @Override
+ public void onPlaybackSpeedChanged(float speed) {
+ final MediaController2Impl controller;
+ try {
+ controller = getController();
+ } catch (IllegalStateException e) {
+ Log.w(TAG, "Don't fail silently here. Highly likely a bug");
+ return;
+ }
+ controller.pushPlaybackSpeedChanges(speed);
+ }
+
+ @Override
+ public void onBufferedPositionChanged(long bufferedPositionMs) {
+ final MediaController2Impl controller;
+ try {
+ controller = getController();
+ } catch (IllegalStateException e) {
+ Log.w(TAG, "Don't fail silently here. Highly likely a bug");
+ return;
+ }
+ if (bufferedPositionMs < 0) {
+ Log.w(TAG, "onBufferedPositionChanged(): Ignoring negative bufferedPositionMs");
+ return;
+ }
+ controller.pushBufferedPositionChanges(bufferedPositionMs);
+ }
+
+ @Override
+ public void onPlaylistChanged(List<Bundle> playlistBundle, Bundle metadataBundle) {
final MediaController2Impl controller;
try {
controller = getController();
@@ -90,7 +137,7 @@
return;
}
if (playlistBundle == null) {
- Log.w(TAG, "onPlaylistChanged(): Ignoring null playlist");
+ Log.w(TAG, "onPlaylistChanged(): Ignoring null playlist from " + controller);
return;
}
List<MediaItem2> playlist = new ArrayList<>();
@@ -102,7 +149,23 @@
playlist.add(item);
}
}
- controller.pushPlaylistChanges(playlist);
+ MediaMetadata2 metadata =
+ MediaMetadata2.fromBundle(controller.getContext(), metadataBundle);
+ controller.pushPlaylistChanges(playlist, metadata);
+ }
+
+ @Override
+ public void onPlaylistMetadataChanged(Bundle metadataBundle) throws RuntimeException {
+ final MediaController2Impl controller;
+ try {
+ controller = getController();
+ } catch (IllegalStateException e) {
+ Log.w(TAG, "Don't fail silently here. Highly likely a bug");
+ return;
+ }
+ MediaMetadata2 metadata =
+ MediaMetadata2.fromBundle(controller.getContext(), metadataBundle);
+ controller.pushPlaylistMetadataChanges(metadata);
}
@Override
@@ -146,8 +209,9 @@
@Override
public void onConnected(IMediaSession2 sessionBinder, Bundle commandGroup,
- Bundle playbackState, Bundle playbackInfo, Bundle playlistParams, List<Bundle>
- itemBundleList, PendingIntent sessionActivity) {
+ int playerState, long positionEventTimeMs, long positionMs, float playbackSpeed,
+ long bufferedPositionMs, Bundle playbackInfo, Bundle playlistParams,
+ List<Bundle> itemBundleList, PendingIntent sessionActivity) {
final MediaController2Impl controller = mController.get();
if (controller == null) {
if (DEBUG) {
@@ -168,7 +232,7 @@
}
controller.onConnectedNotLocked(sessionBinder,
CommandGroup.fromBundle(context, commandGroup),
- PlaybackState2.fromBundle(context, playbackState),
+ playerState, positionEventTimeMs, positionMs, playbackSpeed, bufferedPositionMs,
PlaybackInfoImpl.fromBundle(context, playbackInfo),
PlaylistParams.fromBundle(context, playlistParams),
itemList, sessionActivity);
diff --git a/packages/MediaComponents/src/com/android/media/MediaItem2Impl.java b/packages/MediaComponents/src/com/android/media/MediaItem2Impl.java
index 039ff8f..c95b43f 100644
--- a/packages/MediaComponents/src/com/android/media/MediaItem2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaItem2Impl.java
@@ -31,21 +31,31 @@
import android.os.Bundle;
import android.text.TextUtils;
+import java.util.UUID;
+
public class MediaItem2Impl implements MediaItem2Provider {
private static final String KEY_ID = "android.media.mediaitem2.id";
private static final String KEY_FLAGS = "android.media.mediaitem2.flags";
private static final String KEY_METADATA = "android.media.mediaitem2.metadata";
+ private static final String KEY_UUID = "android.media.mediaitem2.uuid";
private final Context mContext;
private final MediaItem2 mInstance;
private final String mId;
private final int mFlags;
+ private final UUID mUUID;
private MediaMetadata2 mMetadata;
private DataSourceDesc mDataSourceDesc;
// From the public API
public MediaItem2Impl(@NonNull Context context, @NonNull String mediaId,
@Nullable DataSourceDesc dsd, @Nullable MediaMetadata2 metadata, @Flags int flags) {
+ this(context, mediaId, dsd, metadata, flags, null);
+ }
+
+ private MediaItem2Impl(@NonNull Context context, @NonNull String mediaId,
+ @Nullable DataSourceDesc dsd, @Nullable MediaMetadata2 metadata, @Flags int flags,
+ @Nullable UUID uuid) {
if (mediaId == null) {
throw new IllegalArgumentException("mediaId shouldn't be null");
}
@@ -58,24 +68,18 @@
mDataSourceDesc = dsd;
mMetadata = metadata;
mFlags = flags;
+ mUUID = (uuid == null) ? UUID.randomUUID() : uuid;
mInstance = new MediaItem2(this);
}
- // Create anonymized version
- public MediaItem2Impl(Context context, String mediaId, MediaMetadata2 metadata,
- @Flags int flags) {
- if (mediaId == null) {
- throw new IllegalArgumentException("mediaId shouldn't be null");
+ @Override
+ public boolean equals_impl(Object obj) {
+ if (!(obj instanceof MediaItem2)) {
+ return false;
}
- if (metadata != null && !TextUtils.equals(mediaId, metadata.getMediaId())) {
- throw new IllegalArgumentException("metadata's id should be matched with the mediaid");
- }
- mContext = context;
- mId = mediaId;
- mMetadata = metadata;
- mFlags = flags;
- mInstance = new MediaItem2(this);
+ MediaItem2 other = (MediaItem2) obj;
+ return mUUID.equals(((MediaItem2Impl) other.getProvider()).mUUID);
}
/**
@@ -90,10 +94,37 @@
if (mMetadata != null) {
bundle.putBundle(KEY_METADATA, mMetadata.toBundle());
}
+ bundle.putString(KEY_UUID, mUUID.toString());
return bundle;
}
- public static MediaItem2 fromBundle(Context context, Bundle bundle) {
+ /**
+ * Create a MediaItem2 from the {@link Bundle}.
+ *
+ * @param context A context.
+ * @param bundle The bundle which was published by {@link MediaItem2#toBundle()}.
+ * @return The newly created MediaItem2
+ */
+ public static MediaItem2 fromBundle(@NonNull Context context, @NonNull Bundle bundle) {
+ if (bundle == null) {
+ return null;
+ }
+ final String uuidString = bundle.getString(KEY_UUID);
+ return fromBundle(context, bundle, UUID.fromString(uuidString));
+ }
+
+ /**
+ * Create a MediaItem2 from the {@link Bundle} with the specified {@link UUID}.
+ * If {@link UUID}
+ * can be null for creating new.
+ *
+ * @param context A context.
+ * @param bundle The bundle which was published by {@link MediaItem2#toBundle()}.
+ * @param uuid A {@link UUID} to override. Can be {@link null} for override.
+ * @return The newly created MediaItem2
+ */
+ static MediaItem2 fromBundle(@NonNull Context context, @NonNull Bundle bundle,
+ @Nullable UUID uuid) {
if (bundle == null) {
return null;
}
@@ -102,7 +133,7 @@
final MediaMetadata2 metadata = metadataBundle != null
? MediaMetadata2.fromBundle(context, metadataBundle) : null;
final int flags = bundle.getInt(KEY_FLAGS);
- return new MediaItem2Impl(context, id, metadata, flags).getInstance();
+ return new MediaItem2Impl(context, id, null, metadata, flags, uuid).getInstance();
}
private MediaItem2 getInstance() {
diff --git a/packages/MediaComponents/src/com/android/media/MediaMetadata2Impl.java b/packages/MediaComponents/src/com/android/media/MediaMetadata2Impl.java
index 333455d..286f5ee 100644
--- a/packages/MediaComponents/src/com/android/media/MediaMetadata2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaMetadata2Impl.java
@@ -228,7 +228,7 @@
}
public static MediaMetadata2 fromBundle(Context context, Bundle bundle) {
- return new MediaMetadata2Impl(context, bundle).getInstance();
+ return (bundle == null) ? null : new MediaMetadata2Impl(context, bundle).getInstance();
}
public static final class BuilderImpl implements MediaMetadata2Provider.BuilderProvider {
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java b/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
index 5c18515..6a53b05 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
@@ -52,7 +52,6 @@
import android.media.MediaSession2.PlaylistParams.ShuffleMode;
import android.media.MediaSession2.SessionCallback;
import android.media.MediaSessionService2;
-import android.media.PlaybackState2;
import android.media.SessionToken2;
import android.media.VolumeProvider2;
import android.media.session.MediaSessionManager;
@@ -248,11 +247,11 @@
oldAgent.unregisterPlaylistEventCallback(mPlaylistEventCallback);
}
}
- // TODO(jaewan): Notify controllers about the change in the media player base (b/74370608)
- // Note that notification will be done indirectly by telling player state,
- // position, buffered position, etc.
- mSessionStub.notifyPlaybackInfoChanged(info);
- notifyPlaybackStateChangedNotLocked(mInstance.getPlaybackState());
+
+ if (oldPlayer != null) {
+ mSessionStub.notifyPlaybackInfoChanged(info);
+ notifyPlayerUpdatedNotLocked(oldPlayer);
+ }
}
private PlaybackInfo createPlaybackInfo(VolumeProvider2 volumeProvider, AudioAttributes attrs) {
@@ -496,22 +495,27 @@
}
@Override
- public void setPlaylist_impl(List<MediaItem2> playlist) {
- if (playlist == null) {
- throw new IllegalArgumentException("playlist shouldn't be null");
+ public void setPlaylist_impl(List<MediaItem2> list, MediaMetadata2 metadata) {
+ if (list == null) {
+ throw new IllegalArgumentException("list shouldn't be null");
}
ensureCallingThread();
- // TODO: Uncomment or remove
- /*
- final MediaPlayerBase player = mPlayer;
- if (player != null) {
- // TODO implement and use SessionPlaylistAgent
- //player.setPlaylist(playlist);
- mSessionStub.notifyPlaylistChanged(playlist);
+ final MediaPlaylistAgent agent = mPlaylistAgent;
+ if (agent != null) {
+ agent.setPlaylist(list, metadata);
} else if (DEBUG) {
Log.d(TAG, "API calls after the close()", new IllegalStateException());
}
- */
+ }
+
+ @Override
+ public void updatePlaylistMetadata_impl(MediaMetadata2 metadata) {
+ final MediaPlaylistAgent agent = mPlaylistAgent;
+ if (agent != null) {
+ agent.updatePlaylistMetadata(metadata);
+ } else if (DEBUG) {
+ Log.d(TAG, "API calls after the close()", new IllegalStateException());
+ }
}
@Override
@@ -522,7 +526,12 @@
if (item == null) {
throw new IllegalArgumentException("item shouldn't be null");
}
- // TODO(jaewan): Implement
+ final MediaPlaylistAgent agent = mPlaylistAgent;
+ if (agent != null) {
+ agent.addPlaylistItem(index, item);
+ } else if (DEBUG) {
+ Log.d(TAG, "API calls after the close()", new IllegalStateException());
+ }
}
@Override
@@ -530,15 +539,12 @@
if (item == null) {
throw new IllegalArgumentException("item shouldn't be null");
}
- // TODO(jaewan): Implement
- }
-
- @Override
- public void editPlaylistItem_impl(MediaItem2 item) {
- if (item == null) {
- throw new IllegalArgumentException("item shouldn't be null");
+ final MediaPlaylistAgent agent = mPlaylistAgent;
+ if (agent != null) {
+ agent.removePlaylistItem(item);
+ } else if (DEBUG) {
+ Log.d(TAG, "API calls after the close()", new IllegalStateException());
}
- // TODO(jaewan): Implement
}
@Override
@@ -549,24 +555,33 @@
if (item == null) {
throw new IllegalArgumentException("item shouldn't be null");
}
- // TODO(jaewan): Implement
+ final MediaPlaylistAgent agent = mPlaylistAgent;
+ if (agent != null) {
+ agent.replacePlaylistItem(index, item);
+ } else if (DEBUG) {
+ Log.d(TAG, "API calls after the close()", new IllegalStateException());
+ }
}
@Override
public List<MediaItem2> getPlaylist_impl() {
- // TODO: Uncomment or remove
- /*
- final MediaPlayerBase player = mPlayer;
- if (player != null) {
- // TODO(jaewan): Is it safe to be called on any thread?
- // Otherwise MediaSession2 should cache parameter of setPlaylist.
- // TODO implement
- //return player.getPlaylist();
- return null;
+ final MediaPlaylistAgent agent = mPlaylistAgent;
+ if (agent != null) {
+ return agent.getPlaylist();
} else if (DEBUG) {
Log.d(TAG, "API calls after the close()", new IllegalStateException());
}
- */
+ return null;
+ }
+
+ @Override
+ public MediaMetadata2 getPlaylistMetadata_impl() {
+ final MediaPlaylistAgent agent = mPlaylistAgent;
+ if (agent != null) {
+ return agent.getPlaylistMetadata();
+ } else if (DEBUG) {
+ Log.d(TAG, "API calls after the close()", new IllegalStateException());
+ }
return null;
}
@@ -660,12 +675,6 @@
return;
}
mCallbacks.put(callback, executor);
- // TODO: Uncomment or remove
- /*
- // TODO(jaewan): Double check if we need this.
- final PlaybackState2 state = getInstance().getPlaybackState();
- executor.execute(() -> callback.onPlaybackStateChanged(state));
- */
}
@Override
@@ -678,25 +687,6 @@
}
@Override
- public PlaybackState2 getPlaybackState_impl() {
- ensureCallingThread();
- // TODO: Uncomment or remove
- /*
- final MediaPlayerBase player = mPlayer;
- if (player != null) {
- // TODO(jaewan): Is it safe to be called on any thread?
- // Otherwise MediaSession2 should cache the result from listener.
- // TODO implement
- //return player.getPlaybackState();
- return null;
- } else if (DEBUG) {
- Log.d(TAG, "API calls after the close()", new IllegalStateException());
- }
- */
- return null;
- }
-
- @Override
public void notifyError_impl(int errorCode, Bundle extras) {
// TODO(jaewan): Implement
}
@@ -725,9 +715,31 @@
}*/
}
- private void notifyPlaybackStateChangedNotLocked(final PlaybackState2 state) {
+ private void notifyPlaylistChangedOnExecutor(MediaPlaylistAgent playlistAgent,
+ List<MediaItem2> list, MediaMetadata2 metadata) {
+ if (playlistAgent != mPlaylistAgent) {
+ // Ignore calls from the old agent. Ignore.
+ return;
+ }
+ mCallback.onPlaylistChanged(mInstance, playlistAgent, list, metadata);
+ mSessionStub.notifyPlaylistChangedNotLocked(list, metadata);
+ }
+
+ private void notifyPlaylistMetadataChangedOnExecutor(MediaPlaylistAgent playlistAgent,
+ MediaMetadata2 metadata) {
+ if (playlistAgent != mPlaylistAgent) {
+ // Ignore calls from the old agent. Ignore.
+ return;
+ }
+ mCallback.onPlaylistMetadataChanged(mInstance, playlistAgent, metadata);
+ mSessionStub.notifyPlaylistMetadataChangedNotLocked(metadata);
+ }
+
+ private void notifyPlayerUpdatedNotLocked(MediaPlayerBase oldPlayer) {
ArrayMap<PlayerEventCallback, Executor> callbacks = new ArrayMap<>();
+ MediaPlayerBase player;
synchronized (mLock) {
+ player = mPlayer;
callbacks.putAll(mCallbacks);
}
// Notify to callbacks added directly to this session
@@ -738,7 +750,26 @@
//executor.execute(() -> callback.onPlaybackStateChanged(state));
}
// Notify to controllers as well.
- mSessionStub.notifyPlaybackStateChangedNotLocked(state);
+ final int state = player.getPlayerState();
+ if (state != oldPlayer.getPlayerState()) {
+ mSessionStub.notifyPlayerStateChangedNotLocked(state);
+ }
+
+ final long currentTimeMs = System.currentTimeMillis();
+ final long position = player.getCurrentPosition();
+ if (position != oldPlayer.getCurrentPosition()) {
+ mSessionStub.notifyPositionChangedNotLocked(currentTimeMs, position);
+ }
+
+ final float speed = player.getPlaybackSpeed();
+ if (speed != oldPlayer.getPlaybackSpeed()) {
+ mSessionStub.notifyPlaybackSpeedChangedNotLocked(speed);
+ }
+
+ final long bufferedPosition = player.getBufferedPosition();
+ if (bufferedPosition != oldPlayer.getBufferedPosition()) {
+ mSessionStub.notifyBufferedPositionChangedNotLocked(bufferedPosition);
+ }
}
private void notifyErrorNotLocked(String mediaId, int what, int extra) {
@@ -768,6 +799,10 @@
return mPlayer;
}
+ MediaPlaylistAgent getPlaylistAgent() {
+ return mPlaylistAgent;
+ }
+
Executor getCallbackExecutor() {
return mCallbackExecutor;
}
@@ -803,26 +838,64 @@
@Override
public void onCurrentDataSourceChanged(MediaPlayerBase mpb, DataSourceDesc dsd) {
- super.onCurrentDataSourceChanged(mpb, dsd);
- // TODO(jaewan): Handle this b/74370608
+ MediaSession2Impl session = getSession();
+ if (session == null) {
+ return;
+ }
+ session.getCallbackExecutor().execute(() -> {
+ // TODO (jaewan): Convert dsd to MediaItem (b/74506462)
+ // TODO (jaewan): Notify controllers through appropriate callback. (b/74505936)
+ session.getCallback().onCurrentMediaItemChanged(
+ session.getInstance(), mpb, null /* MediaItem */);
+ });
}
@Override
public void onMediaPrepared(MediaPlayerBase mpb, DataSourceDesc dsd) {
- super.onMediaPrepared(mpb, dsd);
- // TODO(jaewan): Handle this b/74370608
+ MediaSession2Impl session = getSession();
+ if (session == null) {
+ return;
+ }
+ session.getCallbackExecutor().execute(() -> {
+ // TODO (jaewan): Convert dsd to MediaItem (b/74506462)
+ // TODO (jaewan): Notify controllers through appropriate callback. (b/74505936)
+ session.getCallback().onMediaPrepared(
+ session.getInstance(), mpb, null /* MediaItem */);
+ });
}
@Override
public void onPlayerStateChanged(MediaPlayerBase mpb, int state) {
- super.onPlayerStateChanged(mpb, state);
- // TODO(jaewan): Handle this b/74370608
+ MediaSession2Impl session = getSession();
+ if (session == null) {
+ return;
+ }
+ session.getCallbackExecutor().execute(() -> {
+ session.getCallback().onPlayerStateChanged(session.getInstance(), mpb, state);
+ session.getSessionStub().notifyPlayerStateChangedNotLocked(state);
+ });
}
@Override
public void onBufferingStateChanged(MediaPlayerBase mpb, DataSourceDesc dsd, int state) {
- super.onBufferingStateChanged(mpb, dsd, state);
- // TODO(jaewan): Handle this b/74370608
+ MediaSession2Impl session = getSession();
+ if (session == null) {
+ return;
+ }
+ session.getCallbackExecutor().execute(() -> {
+ // TODO (jaewan): Convert dsd to MediaItem (b/74506462)
+ // TODO (jaewan): Notify controllers through appropriate callback. (b/74505936)
+ session.getCallback().onBufferingStateChanged(
+ session.getInstance(), mpb, null /* MediaItem */, state);
+ });
+ }
+
+ private MediaSession2Impl getSession() {
+ final MediaSession2Impl session = mSession.get();
+ if (session == null && DEBUG) {
+ Log.d(TAG, "Session is closed", new IllegalStateException());
+ }
+ return session;
}
}
@@ -836,15 +909,21 @@
@Override
public void onPlaylistChanged(MediaPlaylistAgent playlistAgent, List<MediaItem2> list,
MediaMetadata2 metadata) {
- super.onPlaylistChanged(playlistAgent, list, metadata);
- // TODO(jaewan): Handle this (b/74326040)
+ final MediaSession2Impl session = mSession.get();
+ if (session == null) {
+ return;
+ }
+ session.notifyPlaylistChangedOnExecutor(playlistAgent, list, metadata);
}
@Override
public void onPlaylistMetadataChanged(MediaPlaylistAgent playlistAgent,
MediaMetadata2 metadata) {
- super.onPlaylistMetadataChanged(playlistAgent, metadata);
- // TODO(jaewan): Handle this (b/74174649)
+ final MediaSession2Impl session = mSession.get();
+ if (session == null) {
+ return;
+ }
+ session.notifyPlaylistMetadataChangedOnExecutor(playlistAgent, metadata);
}
@Override
@@ -1068,10 +1147,10 @@
private final int mUid;
private final String mPackageName;
private final boolean mIsTrusted;
- private final IMediaSession2Callback mControllerBinder;
+ private final IMediaController2 mControllerBinder;
public ControllerInfoImpl(Context context, ControllerInfo instance, int uid,
- int pid, String packageName, IMediaSession2Callback callback) {
+ int pid, String packageName, IMediaController2 callback) {
if (TextUtils.isEmpty(packageName)) {
throw new IllegalArgumentException("packageName shouldn't be empty");
}
@@ -1147,7 +1226,7 @@
return mControllerBinder.asBinder();
}
- public IMediaSession2Callback getControllerBinder() {
+ public IMediaController2 getControllerBinder() {
return mControllerBinder;
}
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java b/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
index a9c5224..379e49a 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
@@ -21,13 +21,14 @@
import android.media.MediaController2;
import android.media.MediaItem2;
import android.media.MediaLibraryService2.LibraryRoot;
+import android.media.MediaMetadata2;
+import android.media.MediaPlayerBase;
import android.media.MediaSession2;
import android.media.MediaSession2.Command;
import android.media.MediaSession2.CommandButton;
import android.media.MediaSession2.CommandGroup;
import android.media.MediaSession2.ControllerInfo;
import android.media.MediaSession2.PlaylistParams;
-import android.media.PlaybackState2;
import android.media.Rating2;
import android.media.VolumeProvider2;
import android.net.Uri;
@@ -85,7 +86,7 @@
mControllers.clear();
}
for (int i = 0; i < list.size(); i++) {
- IMediaSession2Callback controllerBinder =
+ IMediaController2 controllerBinder =
((ControllerInfoImpl) list.get(i).getProvider()).getControllerBinder();
try {
// Should be used without a lock hold to prevent potential deadlock.
@@ -113,7 +114,7 @@
}
// Get controller if the command from caller to session is able to be handled.
- private ControllerInfo getControllerIfAble(IMediaSession2Callback caller) {
+ private ControllerInfo getControllerIfAble(IMediaController2 caller) {
synchronized (mLock) {
final ControllerInfo controllerInfo = mControllers.get(caller.asBinder());
if (controllerInfo == null && DEBUG) {
@@ -124,7 +125,7 @@
}
// Get controller if the command from caller to session is able to be handled.
- private ControllerInfo getControllerIfAble(IMediaSession2Callback caller, int commandCode) {
+ private ControllerInfo getControllerIfAble(IMediaController2 caller, int commandCode) {
synchronized (mLock) {
final ControllerInfo controllerInfo = getControllerIfAble(caller);
if (controllerInfo == null) {
@@ -147,7 +148,7 @@
}
// Get controller if the command from caller to session is able to be handled.
- private ControllerInfo getControllerIfAble(IMediaSession2Callback caller, Command command) {
+ private ControllerInfo getControllerIfAble(IMediaController2 caller, Command command) {
synchronized (mLock) {
final ControllerInfo controllerInfo = getControllerIfAble(caller);
if (controllerInfo == null) {
@@ -170,7 +171,7 @@
}
// Return binder if the session is able to send a command to the controller.
- private IMediaSession2Callback getControllerBinderIfAble(ControllerInfo controller) {
+ private IMediaController2 getControllerBinderIfAble(ControllerInfo controller) {
if (getSession() == null) {
// getSession() already logged if session is closed.
return null;
@@ -190,7 +191,7 @@
}
// Return binder if the session is able to send a command to the controller.
- private IMediaSession2Callback getControllerBinderIfAble(ControllerInfo controller,
+ private IMediaController2 getControllerBinderIfAble(ControllerInfo controller,
int commandCode) {
synchronized (mLock) {
CommandGroup allowedCommands = mAllowedCommandGroupMap.get(controller);
@@ -212,7 +213,7 @@
// AIDL methods for session overrides
//////////////////////////////////////////////////////////////////////////////////////////////
@Override
- public void connect(final IMediaSession2Callback caller, final String callingPackage)
+ public void connect(final IMediaController2 caller, final String callingPackage)
throws RuntimeException {
final MediaSession2Impl session = getSession();
if (session == null) {
@@ -256,11 +257,13 @@
// It's needed because we cannot call synchronous calls between session/controller.
// Note: We're doing this after the onConnectionChanged(), but there's no guarantee
// that events here are notified after the onConnected() because
- // IMediaSession2Callback is oneway (i.e. async call) and CallbackStub will
+ // IMediaController2 is oneway (i.e. async call) and Stub will
// use thread poll for incoming calls.
- // TODO(jaewan): Should we protect getting playback state?
- final PlaybackState2 state = session.getInstance().getPlaybackState();
- final Bundle playbackStateBundle = (state != null) ? state.toBundle() : null;
+ final int playerState = session.getInstance().getPlayerState();
+ final long positionEventTimeMs = System.currentTimeMillis();
+ final long positionMs = session.getInstance().getCurrentPosition();
+ final float playbackSpeed = session.getInstance().getPlaybackSpeed();
+ final long bufferedPositionMs = session.getInstance().getBufferedPosition();
final Bundle playbackInfoBundle = ((MediaController2Impl.PlaybackInfoImpl)
session.getPlaybackInfo().getProvider()).toBundle();
final PlaylistParams params = session.getInstance().getPlaylistParams();
@@ -292,9 +295,10 @@
return;
}
try {
- caller.onConnected(MediaSession2Stub.this,
- allowedCommands.toBundle(), playbackStateBundle, playbackInfoBundle,
- paramsBundle, playlistBundle, sessionActivity);
+ caller.onConnected(MediaSession2Stub.this, allowedCommands.toBundle(),
+ playerState, positionEventTimeMs, positionMs, playbackSpeed,
+ bufferedPositionMs, playbackInfoBundle, paramsBundle, playlistBundle,
+ sessionActivity);
} catch (RemoteException e) {
// Controller may be died prematurely.
// TODO(jaewan): Handle here.
@@ -317,18 +321,26 @@
}
@Override
- public void release(final IMediaSession2Callback caller) throws RemoteException {
+ public void release(final IMediaController2 caller) throws RemoteException {
+ ControllerInfo controller;
synchronized (mLock) {
- ControllerInfo controllerInfo = mControllers.remove(caller.asBinder());
+ controller = mControllers.remove(caller.asBinder());
if (DEBUG) {
- Log.d(TAG, "releasing " + controllerInfo);
+ Log.d(TAG, "releasing " + controller);
}
- mSubscriptions.remove(controllerInfo);
+ mSubscriptions.remove(controller);
}
+ final MediaSession2Impl session = getSession();
+ if (session == null || controller == null) {
+ return;
+ }
+ session.getCallbackExecutor().execute(() -> {
+ session.getCallback().onDisconnected(session.getInstance(), controller);
+ });
}
@Override
- public void setVolumeTo(final IMediaSession2Callback caller, final int value, final int flags)
+ public void setVolumeTo(final IMediaController2 caller, final int value, final int flags)
throws RuntimeException {
final MediaSession2Impl session = getSession();
final ControllerInfo controller = getControllerIfAble(
@@ -364,7 +376,7 @@
}
@Override
- public void adjustVolume(IMediaSession2Callback caller, int direction, int flags)
+ public void adjustVolume(IMediaController2 caller, int direction, int flags)
throws RuntimeException {
final MediaSession2Impl session = getSession();
final ControllerInfo controller = getControllerIfAble(
@@ -400,7 +412,7 @@
}
@Override
- public void sendTransportControlCommand(IMediaSession2Callback caller,
+ public void sendTransportControlCommand(IMediaController2 caller,
int commandCode, Bundle args) throws RuntimeException {
final MediaSession2Impl session = getSession();
final ControllerInfo controller = getControllerIfAble(caller, commandCode);
@@ -474,7 +486,7 @@
}
@Override
- public void sendCustomCommand(final IMediaSession2Callback caller, final Bundle commandBundle,
+ public void sendCustomCommand(final IMediaController2 caller, final Bundle commandBundle,
final Bundle args, final ResultReceiver receiver) {
final MediaSession2Impl session = getSession();
if (session == null) {
@@ -500,7 +512,7 @@
}
@Override
- public void prepareFromUri(final IMediaSession2Callback caller, final Uri uri,
+ public void prepareFromUri(final IMediaController2 caller, final Uri uri,
final Bundle extras) {
final MediaSession2Impl session = getSession();
final ControllerInfo controller = getControllerIfAble(
@@ -523,7 +535,7 @@
}
@Override
- public void prepareFromSearch(final IMediaSession2Callback caller, final String query,
+ public void prepareFromSearch(final IMediaController2 caller, final String query,
final Bundle extras) {
final MediaSession2Impl session = getSession();
final ControllerInfo controller = getControllerIfAble(
@@ -546,7 +558,7 @@
}
@Override
- public void prepareFromMediaId(final IMediaSession2Callback caller, final String mediaId,
+ public void prepareFromMediaId(final IMediaController2 caller, final String mediaId,
final Bundle extras) {
final MediaSession2Impl session = getSession();
final ControllerInfo controller = getControllerIfAble(
@@ -569,7 +581,7 @@
}
@Override
- public void playFromUri(final IMediaSession2Callback caller, final Uri uri,
+ public void playFromUri(final IMediaController2 caller, final Uri uri,
final Bundle extras) {
final MediaSession2Impl session = getSession();
final ControllerInfo controller = getControllerIfAble(
@@ -591,7 +603,7 @@
}
@Override
- public void playFromSearch(final IMediaSession2Callback caller, final String query,
+ public void playFromSearch(final IMediaController2 caller, final String query,
final Bundle extras) {
final MediaSession2Impl session = getSession();
final ControllerInfo controller = getControllerIfAble(
@@ -614,7 +626,7 @@
}
@Override
- public void playFromMediaId(final IMediaSession2Callback caller, final String mediaId,
+ public void playFromMediaId(final IMediaController2 caller, final String mediaId,
final Bundle extras) {
final MediaSession2Impl session = getSession();
final ControllerInfo controller = getControllerIfAble(
@@ -636,36 +648,206 @@
}
@Override
- public void setRating(final IMediaSession2Callback caller, final String mediaId,
+ public void setRating(final IMediaController2 caller, final String mediaId,
final Bundle ratingBundle) {
- final MediaSession2Impl sessionImpl = getSession();
+ final MediaSession2Impl session = getSession();
final ControllerInfo controller = getControllerIfAble(caller);
- if (controller == null) {
- if (DEBUG) {
- Log.d(TAG, "Command from a controller that hasn't connected. Ignore");
+ if (session == null || controller == null) {
+ return;
+ }
+ if (mediaId == null) {
+ Log.w(TAG, "setRating(): Ignoring null mediaId from " + controller);
+ return;
+ }
+ if (ratingBundle == null) {
+ Log.w(TAG, "setRating(): Ignoring null ratingBundle from " + controller);
+ return;
+ }
+ session.getCallbackExecutor().execute(() -> {
+ if (getControllerIfAble(caller) == null) {
+ return;
}
- return;
- }
- Rating2 rating = Rating2Impl.fromBundle(sessionImpl.getContext(), ratingBundle);
- if (rating == null) {
- Log.w(TAG, "setRating(): Ignore null rating");
- return;
- }
- sessionImpl.getCallbackExecutor().execute(() -> {
- final MediaSession2Impl session = mSession.get();
- if (session == null) {
+ Rating2 rating = Rating2Impl.fromBundle(session.getContext(), ratingBundle);
+ if (rating == null) {
+ if (ratingBundle == null) {
+ Log.w(TAG, "setRating(): Ignoring null rating from " + controller);
+ return;
+ }
return;
}
session.getCallback().onSetRating(session.getInstance(), controller, mediaId, rating);
});
}
+ @Override
+ public void setPlaylist(final IMediaController2 caller, final List<Bundle> playlist,
+ final Bundle metadata) {
+ final MediaSession2Impl session = getSession();
+ final ControllerInfo controller = getControllerIfAble(
+ caller, MediaSession2.COMMAND_CODE_PLAYLIST_SET_LIST);
+ if (session == null || controller == null) {
+ return;
+ }
+ if (playlist == null) {
+ Log.w(TAG, "setPlaylist(): Ignoring null playlist from " + controller);
+ return;
+ }
+ session.getCallbackExecutor().execute(() -> {
+ if (getControllerIfAble(
+ caller, MediaSession2.COMMAND_CODE_PLAYLIST_SET_LIST) == null) {
+ return;
+ }
+ Command command = new Command(session.getContext(),
+ MediaSession2.COMMAND_CODE_PLAYLIST_SET_LIST);
+ boolean accepted = session.getCallback().onCommandRequest(session.getInstance(),
+ controller, command);
+ if (!accepted) {
+ // Don't run rejected command.
+ if (DEBUG) {
+ Log.d(TAG, "setPlaylist() from " + controller + " was rejected");
+ }
+ return;
+ }
+ List<MediaItem2> list = new ArrayList<>();
+ for (int i = 0; i < playlist.size(); i++) {
+ MediaItem2 item = MediaItem2.fromBundle(session.getContext(), playlist.get(i));
+ if (item != null) {
+ list.add(item);
+ }
+ }
+ session.getInstance().setPlaylist(list,
+ MediaMetadata2.fromBundle(session.getContext(), metadata));
+ });
+ }
+
+ @Override
+ public void updatePlaylistMetadata(final IMediaController2 caller, final Bundle metadata) {
+ final MediaSession2Impl session = getSession();
+ final ControllerInfo controller = getControllerIfAble(
+ caller, MediaSession2.COMMAND_CODE_PLAYLIST_SET_LIST_METADATA);
+ if (session == null || controller == null) {
+ return;
+ }
+ session.getCallbackExecutor().execute(() -> {
+ if (getControllerIfAble(
+ caller, MediaSession2.COMMAND_CODE_PLAYLIST_SET_LIST_METADATA) == null) {
+ return;
+ }
+ Command command = new Command(session.getContext(),
+ MediaSession2.COMMAND_CODE_PLAYLIST_SET_LIST_METADATA);
+ boolean accepted = session.getCallback().onCommandRequest(session.getInstance(),
+ controller, command);
+ if (!accepted) {
+ // Don't run rejected command.
+ if (DEBUG) {
+ Log.d(TAG, "setPlaylist() from " + controller + " was rejected");
+ }
+ return;
+ }
+ session.getInstance().updatePlaylistMetadata(
+ MediaMetadata2.fromBundle(session.getContext(), metadata));
+ });
+ }
+
+ @Override
+ public void addPlaylistItem(IMediaController2 caller, int index, Bundle mediaItem) {
+ final MediaSession2Impl session = getSession();
+ final ControllerInfo controller = getControllerIfAble(
+ caller, MediaSession2.COMMAND_CODE_PLAYLIST_ADD_ITEM);
+ if (session == null || controller == null) {
+ return;
+ }
+ session.getCallbackExecutor().execute(() -> {
+ if (getControllerIfAble(
+ caller, MediaSession2.COMMAND_CODE_PLAYLIST_ADD_ITEM) == null) {
+ return;
+ }
+ Command command = new Command(session.getContext(),
+ MediaSession2.COMMAND_CODE_PLAYLIST_ADD_ITEM);
+ boolean accepted = session.getCallback().onCommandRequest(session.getInstance(),
+ controller, command);
+ if (!accepted) {
+ // Don't run rejected command.
+ if (DEBUG) {
+ Log.d(TAG, "addPlaylistItem() from " + controller + " was rejected");
+ }
+ return;
+ }
+ // Resets the UUID from the incoming media id, so controller may reuse a media item
+ // multiple times for addPlaylistItem.
+ session.getInstance().addPlaylistItem(index,
+ MediaItem2Impl.fromBundle(session.getContext(), mediaItem, null));
+ });
+ }
+
+ @Override
+ public void removePlaylistItem(IMediaController2 caller, Bundle mediaItem) {
+ final MediaSession2Impl session = getSession();
+ final ControllerInfo controller = getControllerIfAble(
+ caller, MediaSession2.COMMAND_CODE_PLAYLIST_REMOVE_ITEM);
+ if (session == null || controller == null) {
+ return;
+ }
+ session.getCallbackExecutor().execute(() -> {
+ if (getControllerIfAble(
+ caller, MediaSession2.COMMAND_CODE_PLAYLIST_REMOVE_ITEM) == null) {
+ return;
+ }
+ Command command = new Command(session.getContext(),
+ MediaSession2.COMMAND_CODE_PLAYLIST_REMOVE_ITEM);
+ boolean accepted = session.getCallback().onCommandRequest(session.getInstance(),
+ controller, command);
+ if (!accepted) {
+ // Don't run rejected command.
+ if (DEBUG) {
+ Log.d(TAG, "removePlaylistItem() from " + controller + " was rejected");
+ }
+ return;
+ }
+ MediaItem2 item = MediaItem2.fromBundle(session.getContext(), mediaItem);
+ List<MediaItem2> list = session.getInstance().getPlaylist();
+ // Trick to use the same reference for calls from the controller.
+ session.getInstance().removePlaylistItem(list.get(list.indexOf(item)));
+ });
+ }
+
+ @Override
+ public void replacePlaylistItem(IMediaController2 caller, int index, Bundle mediaItem) {
+ final MediaSession2Impl session = getSession();
+ final ControllerInfo controller = getControllerIfAble(
+ caller, MediaSession2.COMMAND_CODE_PLAYLIST_REPLACE_ITEM);
+ if (session == null || controller == null) {
+ return;
+ }
+ session.getCallbackExecutor().execute(() -> {
+ if (getControllerIfAble(
+ caller, MediaSession2.COMMAND_CODE_PLAYLIST_REPLACE_ITEM) == null) {
+ return;
+ }
+ Command command = new Command(session.getContext(),
+ MediaSession2.COMMAND_CODE_PLAYLIST_REPLACE_ITEM);
+ boolean accepted = session.getCallback().onCommandRequest(session.getInstance(),
+ controller, command);
+ if (!accepted) {
+ // Don't run rejected command.
+ if (DEBUG) {
+ Log.d(TAG, "replacePlaylistItem() from " + controller + " was rejected");
+ }
+ return;
+ }
+ // Resets the UUID from the incoming media id, so controller may reuse a media item
+ // multiple times for replacePlaylistItem.
+ session.getInstance().replacePlaylistItem(index,
+ MediaItem2.fromBundle(session.getContext(), mediaItem));
+ });
+ }
+
//////////////////////////////////////////////////////////////////////////////////////////////
// AIDL methods for LibrarySession overrides
//////////////////////////////////////////////////////////////////////////////////////////////
@Override
- public void getLibraryRoot(final IMediaSession2Callback caller, final Bundle rootHints)
+ public void getLibraryRoot(final IMediaController2 caller, final Bundle rootHints)
throws RuntimeException {
final MediaLibrarySessionImpl session = getLibrarySession();
final ControllerInfo controller = getControllerIfAble(
@@ -691,7 +873,7 @@
}
@Override
- public void getItem(final IMediaSession2Callback caller, final String mediaId)
+ public void getItem(final IMediaController2 caller, final String mediaId)
throws RuntimeException {
if (mediaId == null) {
if (DEBUG) {
@@ -721,7 +903,7 @@
}
@Override
- public void getChildren(final IMediaSession2Callback caller, final String parentId,
+ public void getChildren(final IMediaController2 caller, final String parentId,
final int page, final int pageSize, final Bundle extras) throws RuntimeException {
if (parentId == null) {
if (DEBUG) {
@@ -769,7 +951,7 @@
}
@Override
- public void search(IMediaSession2Callback caller, String query, Bundle extras) {
+ public void search(IMediaController2 caller, String query, Bundle extras) {
final MediaLibrarySessionImpl session = getLibrarySession();
final ControllerInfo controller = getControllerIfAble(
caller, MediaSession2.COMMAND_CODE_BROWSER);
@@ -789,7 +971,7 @@
}
@Override
- public void getSearchResult(final IMediaSession2Callback caller, final String query,
+ public void getSearchResult(final IMediaController2 caller, final String query,
final int page, final int pageSize, final Bundle extras) {
final MediaLibrarySessionImpl session = getLibrarySession();
final ControllerInfo controller = getControllerIfAble(
@@ -835,7 +1017,7 @@
}
@Override
- public void subscribe(final IMediaSession2Callback caller, final String parentId,
+ public void subscribe(final IMediaController2 caller, final String parentId,
final Bundle option) {
final MediaLibrarySessionImpl session = getLibrarySession();
final ControllerInfo controller = getControllerIfAble(
@@ -865,7 +1047,7 @@
}
@Override
- public void unsubscribe(final IMediaSession2Callback caller, final String parentId) {
+ public void unsubscribe(final IMediaController2 caller, final String parentId) {
final MediaLibrarySessionImpl session = getLibrarySession();
final ControllerInfo controller = getControllerIfAble(
caller, MediaSession2.COMMAND_CODE_BROWSER);
@@ -903,16 +1085,63 @@
}
// Should be used without a lock to prevent potential deadlock.
- public void notifyPlaybackStateChangedNotLocked(PlaybackState2 state) {
+ public void notifyPlayerStateChangedNotLocked(int state) {
final List<ControllerInfo> list = getControllers();
for (int i = 0; i < list.size(); i++) {
- final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(list.get(i));
+ final IMediaController2 controllerBinder = getControllerBinderIfAble(list.get(i));
if (controllerBinder == null) {
return;
}
try {
- final Bundle bundle = state != null ? state.toBundle() : null;
- controllerBinder.onPlaybackStateChanged(bundle);
+ controllerBinder.onPlayerStateChanged(state);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Controller is gone", e);
+ // TODO(jaewan): What to do when the controller is gone?
+ }
+ }
+ }
+
+ public void notifyPositionChangedNotLocked(long eventTimeMs, long positionMs) {
+ final List<ControllerInfo> list = getControllers();
+ for (int i = 0; i < list.size(); i++) {
+ final IMediaController2 controllerBinder = getControllerBinderIfAble(list.get(i));
+ if (controllerBinder == null) {
+ return;
+ }
+ try {
+ controllerBinder.onPositionChanged(eventTimeMs, positionMs);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Controller is gone", e);
+ // TODO(jaewan): What to do when the controller is gone?
+ }
+ }
+ }
+
+ public void notifyPlaybackSpeedChangedNotLocked(float speed) {
+ final List<ControllerInfo> list = getControllers();
+ for (int i = 0; i < list.size(); i++) {
+ final IMediaController2 controllerBinder = getControllerBinderIfAble(list.get(i));
+ if (controllerBinder == null) {
+ return;
+ }
+ try {
+ controllerBinder.onPlaybackSpeedChanged(speed);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Controller is gone", e);
+ // TODO(jaewan): What to do when the controller is gone?
+ }
+ }
+ }
+
+ public void notifyBufferedPositionChangedNotLocked(long bufferedPositionMs) {
+ final List<ControllerInfo> list = getControllers();
+ for (int i = 0; i < list.size(); i++) {
+ final IMediaController2 controllerBinder = getControllerBinderIfAble(list.get(i));
+ if (controllerBinder == null) {
+ return;
+ }
+ try {
+ controllerBinder.onBufferedPositionChanged(bufferedPositionMs);
} catch (RemoteException e) {
Log.w(TAG, "Controller is gone", e);
// TODO(jaewan): What to do when the controller is gone?
@@ -921,7 +1150,7 @@
}
public void notifyCustomLayoutNotLocked(ControllerInfo controller, List<CommandButton> layout) {
- final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(controller);
+ final IMediaController2 controllerBinder = getControllerBinderIfAble(controller);
if (controllerBinder == null) {
return;
}
@@ -940,23 +1169,57 @@
}
}
- public void notifyPlaylistChanged(List<MediaItem2> playlist) {
- final List<Bundle> bundleList = new ArrayList<>();
- for (int i = 0; i < playlist.size(); i++) {
- if (playlist.get(i) != null) {
- Bundle bundle = playlist.get(i).toBundle();
- if (bundle != null) {
- bundleList.add(bundle);
+ public void notifyPlaylistChangedNotLocked(List<MediaItem2> playlist, MediaMetadata2 metadata) {
+ final List<Bundle> bundleList;
+ if (playlist != null) {
+ bundleList = new ArrayList<>();
+ for (int i = 0; i < playlist.size(); i++) {
+ if (playlist.get(i) != null) {
+ Bundle bundle = playlist.get(i).toBundle();
+ if (bundle != null) {
+ bundleList.add(bundle);
+ }
}
}
+ } else {
+ bundleList = null;
}
+ final Bundle metadataBundle = (metadata == null) ? null : metadata.toBundle();
final List<ControllerInfo> list = getControllers();
for (int i = 0; i < list.size(); i++) {
- final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(
+ final IMediaController2 controllerBinder = getControllerBinderIfAble(
list.get(i), MediaSession2.COMMAND_CODE_PLAYLIST_GET_LIST);
if (controllerBinder != null) {
try {
- controllerBinder.onPlaylistChanged(bundleList);
+ controllerBinder.onPlaylistChanged(bundleList, metadataBundle);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Controller is gone", e);
+ // TODO(jaewan): What to do when the controller is gone?
+ }
+ } else {
+ final IMediaController2 binder = getControllerBinderIfAble(
+ list.get(i), MediaSession2.COMMAND_CODE_PLAYLIST_GET_LIST_METADATA);
+ if (binder != null) {
+ try {
+ binder.onPlaylistMetadataChanged(metadataBundle);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Controller is gone", e);
+ // TODO(jaewan): What to do when the controller is gone?
+ }
+ }
+ }
+ }
+ }
+
+ public void notifyPlaylistMetadataChangedNotLocked(MediaMetadata2 metadata) {
+ final Bundle metadataBundle = (metadata == null) ? null : metadata.toBundle();
+ final List<ControllerInfo> list = getControllers();
+ for (int i = 0; i < list.size(); i++) {
+ final IMediaController2 controllerBinder = getControllerBinderIfAble(
+ list.get(i), MediaSession2.COMMAND_CODE_PLAYLIST_GET_LIST_METADATA);
+ if (controllerBinder != null) {
+ try {
+ controllerBinder.onPlaylistMetadataChanged(metadataBundle);
} catch (RemoteException e) {
Log.w(TAG, "Controller is gone", e);
// TODO(jaewan): What to do when the controller is gone?
@@ -968,7 +1231,7 @@
public void notifyPlaylistParamsChanged(MediaSession2.PlaylistParams params) {
final List<ControllerInfo> list = getControllers();
for (int i = 0; i < list.size(); i++) {
- final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(list.get(i));
+ final IMediaController2 controllerBinder = getControllerBinderIfAble(list.get(i));
if (controllerBinder == null) {
return;
}
@@ -984,7 +1247,7 @@
public void notifyPlaybackInfoChanged(MediaController2.PlaybackInfo playbackInfo) {
final List<ControllerInfo> list = getControllers();
for (int i = 0; i < list.size(); i++) {
- final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(list.get(i));
+ final IMediaController2 controllerBinder = getControllerBinderIfAble(list.get(i));
if (controllerBinder == null) {
return;
}
@@ -1002,7 +1265,7 @@
synchronized (mLock) {
mAllowedCommandGroupMap.put(controller, commands);
}
- final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(controller);
+ final IMediaController2 controllerBinder = getControllerBinderIfAble(controller);
if (controllerBinder == null) {
return;
}
@@ -1038,7 +1301,7 @@
private void sendCustomCommandInternal(ControllerInfo controller, Command command, Bundle args,
ResultReceiver receiver) {
- final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(controller);
+ final IMediaController2 controllerBinder = getControllerBinderIfAble(controller);
if (controllerBinder == null) {
return;
}
@@ -1057,7 +1320,7 @@
public void notifySearchResultChanged(ControllerInfo controller, String query, int itemCount,
Bundle extras) {
- final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(controller);
+ final IMediaController2 controllerBinder = getControllerBinderIfAble(controller);
if (controllerBinder == null) {
return;
}
@@ -1091,7 +1354,7 @@
return;
}
}
- final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(controller);
+ final IMediaController2 controllerBinder = getControllerBinderIfAble(controller);
if (controller == null) {
return;
}
diff --git a/packages/MediaComponents/src/com/android/media/MediaSessionService2Impl.java b/packages/MediaComponents/src/com/android/media/MediaSessionService2Impl.java
index 6bd2b2a..a0123b5 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSessionService2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSessionService2Impl.java
@@ -27,10 +27,8 @@
import android.media.MediaSession2;
import android.media.MediaSessionService2;
import android.media.MediaSessionService2.MediaNotification;
-import android.media.PlaybackState2;
import android.media.SessionToken2;
import android.media.SessionToken2.TokenType;
-import android.media.session.PlaybackState;
import android.media.update.MediaSessionService2Provider;
import android.os.IBinder;
import android.support.annotation.GuardedBy;
@@ -109,13 +107,13 @@
return null;
}
- private void updateNotification(PlaybackState2 state) {
+ private void updateNotification(int playerState) {
MediaNotification mediaNotification = mInstance.onUpdateNotification();
if (mediaNotification == null) {
return;
}
- switch((int) state.getState()) {
- case PlaybackState.STATE_PLAYING:
+ switch(playerState) {
+ case MediaPlayerBase.PLAYER_STATE_PLAYING:
if (!mIsRunningForeground) {
mIsRunningForeground = true;
mInstance.startForegroundService(mStartSelfIntent);
@@ -124,7 +122,8 @@
return;
}
break;
- case PlaybackState.STATE_STOPPED:
+ case MediaPlayerBase.PLAYER_STATE_IDLE:
+ case MediaPlayerBase.PLAYER_STATE_ERROR:
if (mIsRunningForeground) {
mIsRunningForeground = false;
mInstance.stopForeground(true);
@@ -142,15 +141,6 @@
// TODO: Implement this
return;
}
- // TODO: Uncomment or remove
- //public void onPlaybackStateChanged(PlaybackState2 state) {
- // if (state == null) {
- // Log.w(TAG, "Ignoring null playback state");
- // return;
- // }
- // MediaSession2Impl impl = (MediaSession2Impl) mSession.getProvider();
- // updateNotification(impl.getInstance().getPlaybackState());
- //}
}
public static class MediaNotificationImpl implements MediaNotificationProvider {
diff --git a/packages/MediaComponents/src/com/android/media/PlaybackState2Impl.java b/packages/MediaComponents/src/com/android/media/PlaybackState2Impl.java
deleted file mode 100644
index ee8d6d7..0000000
--- a/packages/MediaComponents/src/com/android/media/PlaybackState2Impl.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.media;
-
-import android.content.Context;
-import android.media.PlaybackState2;
-import android.media.update.PlaybackState2Provider;
-import android.os.Bundle;
-
-public final class PlaybackState2Impl implements PlaybackState2Provider {
- /**
- * Keys used for converting a PlaybackState2 to a bundle object and vice versa.
- */
- private static final String KEY_STATE = "android.media.playbackstate2.state";
- private static final String KEY_POSITION = "android.media.playbackstate2.position";
- private static final String KEY_BUFFERED_POSITION =
- "android.media.playbackstate2.buffered_position";
- private static final String KEY_SPEED = "android.media.playbackstate2.speed";
- private static final String KEY_UPDATE_TIME = "android.media.playbackstate2.update_time";
- private static final String KEY_ACTIVE_ITEM_ID = "android.media.playbackstate2.active_item_id";
-
- private final Context mContext;
- private final PlaybackState2 mInstance;
- private final int mState;
- private final long mPosition;
- private final long mUpdateTime;
- private final float mSpeed;
- private final long mBufferedPosition;
- private final long mActiveItemId;
-
- public PlaybackState2Impl(Context context, PlaybackState2 instance, int state, long position,
- long updateTime, float speed, long bufferedPosition, long activeItemId) {
- mContext = context;
- mInstance = instance;
- mState = state;
- mPosition = position;
- mSpeed = speed;
- mUpdateTime = updateTime;
- mBufferedPosition = bufferedPosition;
- mActiveItemId = activeItemId;
- }
-
- @Override
- public String toString_impl() {
- StringBuilder bob = new StringBuilder("PlaybackState {");
- bob.append("state=").append(mState);
- bob.append(", position=").append(mPosition);
- bob.append(", buffered position=").append(mBufferedPosition);
- bob.append(", speed=").append(mSpeed);
- bob.append(", updated=").append(mUpdateTime);
- bob.append(", active item id=").append(mActiveItemId);
- bob.append("}");
- return bob.toString();
- }
-
- @Override
- public int getState_impl() {
- return mState;
- }
-
- @Override
- public long getPosition_impl() {
- return mPosition;
- }
-
- @Override
- public long getBufferedPosition_impl() {
- return mBufferedPosition;
- }
-
- @Override
- public float getPlaybackSpeed_impl() {
- return mSpeed;
- }
-
- @Override
- public long getLastPositionUpdateTime_impl() {
- return mUpdateTime;
- }
-
- @Override
- public long getCurrentPlaylistItemIndex_impl() {
- return mActiveItemId;
- }
-
- @Override
- public Bundle toBundle_impl() {
- Bundle bundle = new Bundle();
- bundle.putInt(KEY_STATE, mState);
- bundle.putLong(KEY_POSITION, mPosition);
- bundle.putLong(KEY_UPDATE_TIME, mUpdateTime);
- bundle.putFloat(KEY_SPEED, mSpeed);
- bundle.putLong(KEY_BUFFERED_POSITION, mBufferedPosition);
- bundle.putLong(KEY_ACTIVE_ITEM_ID, mActiveItemId);
- return bundle;
- }
-
- public static PlaybackState2 fromBundle(Context context, Bundle bundle) {
- if (bundle == null) {
- return null;
- }
- if (!bundle.containsKey(KEY_STATE)
- || !bundle.containsKey(KEY_POSITION)
- || !bundle.containsKey(KEY_UPDATE_TIME)
- || !bundle.containsKey(KEY_SPEED)
- || !bundle.containsKey(KEY_BUFFERED_POSITION)
- || !bundle.containsKey(KEY_ACTIVE_ITEM_ID)) {
- return null;
- }
- return new PlaybackState2(context,
- bundle.getInt(KEY_STATE),
- bundle.getLong(KEY_POSITION),
- bundle.getLong(KEY_UPDATE_TIME),
- bundle.getFloat(KEY_SPEED),
- bundle.getLong(KEY_BUFFERED_POSITION),
- bundle.getLong(KEY_ACTIVE_ITEM_ID));
- }
-}
\ No newline at end of file
diff --git a/packages/MediaComponents/src/com/android/media/update/ApiFactory.java b/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
index 07b8788..7f225de 100644
--- a/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
+++ b/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
@@ -20,7 +20,6 @@
import android.content.Context;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
-import android.media.DataSourceDesc;
import android.media.MediaBrowser2;
import android.media.MediaBrowser2.BrowserCallback;
import android.media.MediaController2;
@@ -31,7 +30,6 @@
import android.media.MediaLibraryService2.MediaLibrarySession;
import android.media.MediaLibraryService2.MediaLibrarySession.MediaLibrarySessionCallback;
import android.media.MediaMetadata2;
-import android.media.MediaPlayerBase;
import android.media.MediaPlaylistAgent;
import android.media.MediaSession2;
import android.media.MediaSession2.Command;
@@ -41,7 +39,6 @@
import android.media.MediaSession2.SessionCallback;
import android.media.MediaSessionService2;
import android.media.MediaSessionService2.MediaNotification;
-import android.media.PlaybackState2;
import android.media.Rating2;
import android.media.SessionToken2;
import android.media.VolumeProvider2;
@@ -58,7 +55,6 @@
import android.media.update.MediaSession2Provider.PlaylistParamsProvider;
import android.media.update.MediaSessionService2Provider;
import android.media.update.MediaSessionService2Provider.MediaNotificationProvider;
-import android.media.update.PlaybackState2Provider;
import android.media.update.SessionToken2Provider;
import android.media.update.StaticProvider;
import android.media.update.VideoView2Provider;
@@ -71,7 +67,7 @@
import android.widget.MediaControlView2;
import android.widget.VideoView2;
-import com.android.media.IMediaSession2Callback;
+import com.android.media.IMediaController2;
import com.android.media.MediaBrowser2Impl;
import com.android.media.MediaController2Impl;
import com.android.media.MediaItem2Impl;
@@ -82,7 +78,6 @@
import com.android.media.MediaSession2Impl;
import com.android.media.MediaSession2Impl.PlaylistParamsImpl;
import com.android.media.MediaSessionService2Impl;
-import com.android.media.PlaybackState2Impl;
import com.android.media.Rating2Impl;
import com.android.media.SessionToken2Impl;
import com.android.media.VolumeProvider2Impl;
@@ -142,7 +137,7 @@
Context context, ControllerInfo instance, int uid, int pid, String packageName,
IInterface callback) {
return new MediaSession2Impl.ControllerInfoImpl(context,
- instance, uid, pid, packageName, (IMediaSession2Callback) callback);
+ instance, uid, pid, packageName, (IMediaController2) callback);
}
@Override
@@ -293,19 +288,6 @@
}
@Override
- public PlaybackState2Provider createPlaybackState2(Context context, PlaybackState2 instance,
- int state, long position, long updateTime, float speed, long bufferedPosition,
- long activeItemId) {
- return new PlaybackState2Impl(context, instance, state, position, updateTime, speed,
- bufferedPosition, activeItemId);
- }
-
- @Override
- public PlaybackState2 fromBundle_PlaybackState2(Context context, Bundle bundle) {
- return PlaybackState2Impl.fromBundle(context, bundle);
- }
-
- @Override
public MediaPlaylistAgentProvider createMediaPlaylistAgent(Context context,
MediaPlaylistAgent instance) {
return new MediaPlaylistAgentImpl(context, instance);
diff --git a/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java b/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
index 69febc2..16707c6 100644
--- a/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
+++ b/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
@@ -52,6 +52,7 @@
import com.android.support.mediarouter.media.MediaRouter;
import com.android.support.mediarouter.media.MediaRouteSelector;
+import java.util.Arrays;
import java.util.ArrayList;
import java.util.Formatter;
import java.util.List;
@@ -69,19 +70,37 @@
static final String KEY_VIDEO_TRACK_COUNT = "VideoTrackCount";
static final String KEY_AUDIO_TRACK_COUNT = "AudioTrackCount";
static final String KEY_SUBTITLE_TRACK_COUNT = "SubtitleTrackCount";
+ static final String KEY_PLAYBACK_SPEED = "PlaybackSpeed";
+ static final String KEY_SELECTED_AUDIO_INDEX = "SelectedAudioIndex";
+ static final String KEY_SELECTED_SUBTITLE_INDEX = "SelectedSubtitleIndex";
static final String EVENT_UPDATE_TRACK_STATUS = "UpdateTrackStatus";
// TODO: Remove this once integrating with MediaSession2 & MediaMetadata2
static final String KEY_STATE_IS_ADVERTISEMENT = "MediaTypeAdvertisement";
static final String EVENT_UPDATE_MEDIA_TYPE_STATUS = "UpdateMediaTypeStatus";
- // String for receiving command to show subtitle from MediaSession.
+ // String for sending command to show subtitle to MediaSession.
static final String COMMAND_SHOW_SUBTITLE = "showSubtitle";
- // String for receiving command to hide subtitle from MediaSession.
+ // String for sending command to hide subtitle to MediaSession.
static final String COMMAND_HIDE_SUBTITLE = "hideSubtitle";
// TODO: remove once the implementation is revised
public static final String COMMAND_SET_FULLSCREEN = "setFullscreen";
+ // String for sending command to select audio track to MediaSession.
+ static final String COMMAND_SELECT_AUDIO_TRACK = "SelectTrack";
+ // String for sending command to set playback speed to MediaSession.
+ static final String COMMAND_SET_PLAYBACK_SPEED = "SetPlaybackSpeed";
+ // String for sending command to mute audio to MediaSession.
+ static final String COMMAND_MUTE= "Mute";
+ // String for sending command to unmute audio to MediaSession.
+ static final String COMMAND_UNMUTE = "Unmute";
+ private static final int SETTINGS_MODE_AUDIO_TRACK = 0;
+ private static final int SETTINGS_MODE_PLAYBACK_SPEED = 1;
+ private static final int SETTINGS_MODE_HELP = 2;
+ private static final int SETTINGS_MODE_SUBTITLE_TRACK = 3;
+ private static final int SETTINGS_MODE_VIDEO_QUALITY = 4;
+ private static final int SETTINGS_MODE_MAIN = 5;
+ private static final int PLAYBACK_SPEED_1x_INDEX = 3;
private static final int MAX_PROGRESS = 1000;
private static final int DEFAULT_PROGRESS_UPDATE_TIME_MS = 1000;
private static final int REWIND_TIME_MS = 10000;
@@ -100,6 +119,7 @@
private TextView mTitleView;
private TextView mAdSkipView, mAdRemainingView;
private View mAdExternalLink;
+ private View mTitleBar;
private View mRoot;
private int mDuration;
private int mPrevState;
@@ -107,6 +127,13 @@
private int mVideoTrackCount;
private int mAudioTrackCount;
private int mSubtitleTrackCount;
+ private int mSettingsMode;
+ private int mSelectedSubtitleTrackIndex;
+ private int mSelectedAudioTrackIndex;
+ private int mSelectedVideoQualityIndex;
+ private int mSelectedSpeedIndex;
+ private int mSettingsItemHeight;
+ private int mSettingsWindowMargin;
private long mPlaybackActions;
private boolean mDragging;
private boolean mIsFullScreen;
@@ -115,11 +142,13 @@
private boolean mSubtitleIsEnabled;
private boolean mSeekAvailable;
private boolean mIsAdvertisement;
+ private boolean mIsMute;
private ImageButton mPlayPauseButton;
private ImageButton mFfwdButton;
private ImageButton mRewButton;
private ImageButton mNextButton;
private ImageButton mPrevButton;
+ private ImageButton mBackButton;
private ViewGroup mBasicControls;
private ImageButton mSubtitleButton;
@@ -130,12 +159,13 @@
private ViewGroup mCustomButtons;
private ImageButton mOverflowButtonLeft;
private ImageButton mMuteButton;
- private ImageButton mAspectRationButton;
+ private ImageButton mVideoQualityButton;
private ImageButton mSettingsButton;
private ListView mSettingsListView;
private PopupWindow mSettingsWindow;
private SettingsAdapter mSettingsAdapter;
+ private SubSettingsAdapter mSubSettingsAdapter;
private List<String> mSettingsMainTextsList;
private List<String> mSettingsSubTextsList;
@@ -143,7 +173,8 @@
private List<String> mSubtitleDescriptionsList;
private List<String> mAudioTrackList;
private List<String> mVideoQualityList;
- private List<String> mPlaybackSpeedTextIdsList;
+ private List<String> mPlaybackSpeedTextList;
+ private List<Float> mPlaybackSpeedList;
private CharSequence mPlayDescription;
private CharSequence mPauseDescription;
@@ -166,7 +197,6 @@
mResources = ApiHelper.getLibResources();
// Inflate MediaControlView2 from XML
mRoot = makeControllerView();
- mRoot.addOnLayoutChangeListener(mTitleBarLayoutChangeListener);
mInstance.addView(mRoot);
}
@@ -217,15 +247,11 @@
}
break;
case MediaControlView2.BUTTON_NEXT:
- // TODO: this button is not visible unless its listener is manually set. Should this
- // function still be provided?
if (mNextButton != null) {
mNextButton.setVisibility(visibility);
}
break;
case MediaControlView2.BUTTON_PREV:
- // TODO: this button is not visible unless its listener is manually set. Should this
- // function still be provided?
if (mPrevButton != null) {
mPrevButton.setVisibility(visibility);
}
@@ -250,11 +276,6 @@
mMuteButton.setVisibility(visibility);
}
break;
- case MediaControlView2.BUTTON_ASPECT_RATIO:
- if (mAspectRationButton != null) {
- mAspectRationButton.setVisibility(visibility);
- }
- break;
case MediaControlView2.BUTTON_SETTINGS:
if (mSettingsButton != null) {
mSettingsButton.setVisibility(visibility);
@@ -363,7 +384,8 @@
}
mPlaybackState = mController.getPlaybackState();
if (mPlaybackState != null) {
- return (int) (mPlaybackState.getBufferedPosition() * 100) / mDuration;
+ long bufferedPos = mPlaybackState.getBufferedPosition();
+ return (bufferedPos == -1) ? -1 : (int) (bufferedPos * 100 / mDuration);
}
return 0;
}
@@ -415,20 +437,14 @@
if (mPlayPauseButton != null) {
mPlayPauseButton.requestFocus();
mPlayPauseButton.setOnClickListener(mPlayPauseListener);
- mPlayPauseButton.setColorFilter(R.color.gray);
- mPlayPauseButton.setEnabled(false);
}
mFfwdButton = v.findViewById(R.id.ffwd);
if (mFfwdButton != null) {
mFfwdButton.setOnClickListener(mFfwdListener);
- mFfwdButton.setColorFilter(R.color.gray);
- mFfwdButton.setEnabled(false);
}
mRewButton = v.findViewById(R.id.rew);
if (mRewButton != null) {
mRewButton.setOnClickListener(mRewListener);
- mRewButton.setColorFilter(R.color.gray);
- mRewButton.setEnabled(false);
}
mNextButton = v.findViewById(R.id.next);
if (mNextButton != null) {
@@ -438,13 +454,15 @@
if (mPrevButton != null) {
mPrevButton.setOnClickListener(mPrevListener);
}
+ mBackButton = v.findViewById(R.id.back);
+ if (mBackButton != null) {
+ mBackButton.setOnClickListener(mBackListener);
+ }
mBasicControls = v.findViewById(R.id.basic_controls);
mSubtitleButton = v.findViewById(R.id.subtitle);
if (mSubtitleButton != null) {
mSubtitleButton.setOnClickListener(mSubtitleListener);
- mSubtitleButton.setColorFilter(R.color.gray);
- mSubtitleButton.setEnabled(false);
}
mFullScreenButton = v.findViewById(R.id.fullscreen);
if (mFullScreenButton != null) {
@@ -464,21 +482,33 @@
mOverflowButtonLeft.setOnClickListener(mOverflowLeftListener);
}
mMuteButton = v.findViewById(R.id.mute);
- mAspectRationButton = v.findViewById(R.id.aspect_ratio);
+ if (mMuteButton != null) {
+ mMuteButton.setOnClickListener(mMuteButtonListener);
+ }
mSettingsButton = v.findViewById(R.id.settings);
if (mSettingsButton != null) {
mSettingsButton.setOnClickListener(mSettingsButtonListener);
}
+ mVideoQualityButton = v.findViewById(R.id.video_quality);
+ if (mVideoQualityButton != null) {
+ mVideoQualityButton.setOnClickListener(mVideoQualityListener);
+ }
mProgress = v.findViewById(R.id.mediacontroller_progress);
if (mProgress != null) {
if (mProgress instanceof SeekBar) {
SeekBar seeker = (SeekBar) mProgress;
seeker.setOnSeekBarChangeListener(mSeekListener);
+ seeker.setProgressDrawable(mResources.getDrawable(R.drawable.custom_progress));
+ seeker.setThumb(mResources.getDrawable(R.drawable.custom_progress_thumb));
}
mProgress.setMax(MAX_PROGRESS);
}
+ mTitleBar = v.findViewById(R.id.title_bar);
+ if (mTitleBar != null) {
+ mTitleBar.addOnLayoutChangeListener(mTitleBarLayoutChangeListener);
+ }
mTitleView = v.findViewById(R.id.title_text);
mEndTime = v.findViewById(R.id.time);
@@ -494,10 +524,16 @@
mSettingsListView = (ListView) ApiHelper.inflateLibLayout(mInstance.getContext(),
R.layout.settings_list);
mSettingsAdapter = new SettingsAdapter(mSettingsMainTextsList, mSettingsSubTextsList,
- mSettingsIconIdsList, false);
+ mSettingsIconIdsList);
+ mSubSettingsAdapter = new SubSettingsAdapter(null, 0);
mSettingsListView.setAdapter(mSettingsAdapter);
mSettingsListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
mSettingsListView.setOnItemClickListener(mSettingsItemClickListener);
+
+ mSettingsItemHeight = mResources.getDimensionPixelSize(
+ R.dimen.MediaControlView2_settings_height);
+ mSettingsWindowMargin = (-1) * mResources.getDimensionPixelSize(
+ R.dimen.MediaControlView2_settings_offset);
int width = mResources.getDimensionPixelSize(R.dimen.MediaControlView2_settings_width);
mSettingsWindow = new PopupWindow(mSettingsListView, width,
ViewGroup.LayoutParams.WRAP_CONTENT, true);
@@ -574,7 +610,13 @@
}
if (mProgress != null && currentPosition != mDuration) {
mProgress.setProgress(positionOnProgressBar);
- mProgress.setSecondaryProgress(getBufferPercentage() * 10);
+ // If the media is a local file, there is no need to set a buffer, so set secondary
+ // progress to maximum.
+ if (getBufferPercentage() < 0) {
+ mProgress.setSecondaryProgress(MAX_PROGRESS);
+ } else {
+ mProgress.setSecondaryProgress(getBufferPercentage() * 10);
+ }
}
if (mEndTime != null) {
@@ -744,20 +786,30 @@
}
};
+ private final View.OnClickListener mBackListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // TODO: implement
+ }
+ };
+
private final View.OnClickListener mSubtitleListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
- if (!mSubtitleIsEnabled) {
- mSubtitleButton.setImageDrawable(
- mResources.getDrawable(R.drawable.ic_media_subtitle_enabled, null));
- mController.sendCommand(MediaControlView2Impl.COMMAND_SHOW_SUBTITLE, null, null);
- mSubtitleIsEnabled = true;
- } else {
- mSubtitleButton.setImageDrawable(
- mResources.getDrawable(R.drawable.ic_media_subtitle_disabled, null));
- mController.sendCommand(MediaControlView2Impl.COMMAND_HIDE_SUBTITLE, null, null);
- mSubtitleIsEnabled = false;
- }
+ mSettingsMode = SETTINGS_MODE_SUBTITLE_TRACK;
+ mSubSettingsAdapter.setTexts(mSubtitleDescriptionsList);
+ mSubSettingsAdapter.setCheckPosition(mSelectedSubtitleTrackIndex);
+ displaySettingsWindow(mSubSettingsAdapter);
+ }
+ };
+
+ private final View.OnClickListener mVideoQualityListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mSettingsMode = SETTINGS_MODE_VIDEO_QUALITY;
+ mSubSettingsAdapter.setTexts(mVideoQualityList);
+ mSubSettingsAdapter.setCheckPosition(mSelectedVideoQualityIndex);
+ displaySettingsWindow(mSubSettingsAdapter);
}
};
@@ -797,20 +849,29 @@
}
};
+ private final View.OnClickListener mMuteButtonListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (!mIsMute) {
+ mMuteButton.setImageDrawable(
+ mResources.getDrawable(R.drawable.ic_mute, null));
+ mIsMute = true;
+ mController.sendCommand(COMMAND_MUTE, null, null);
+ } else {
+ mMuteButton.setImageDrawable(
+ mResources.getDrawable(R.drawable.ic_unmute, null));
+ mIsMute = false;
+ mController.sendCommand(COMMAND_UNMUTE, null, null);
+ }
+ }
+ };
+
private final View.OnClickListener mSettingsButtonListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
- mSettingsAdapter = new SettingsAdapter(mSettingsMainTextsList,
- mSettingsSubTextsList, mSettingsIconIdsList, false);
- mSettingsListView.setAdapter(mSettingsAdapter);
- int itemHeight = mResources.getDimensionPixelSize(
- R.dimen.MediaControlView2_settings_height);
- int totalHeight = mSettingsAdapter.getCount() * itemHeight;
- int margin = (-1) * mResources.getDimensionPixelSize(
- R.dimen.MediaControlView2_settings_offset);
- mSettingsWindow.dismiss();
- mSettingsWindow.showAsDropDown(mInstance, margin, margin - totalHeight,
- Gravity.BOTTOM | Gravity.RIGHT);
+ mSettingsMode = SETTINGS_MODE_MAIN;
+ mSettingsAdapter.setSubTexts(mSettingsSubTextsList);
+ displaySettingsWindow(mSettingsAdapter);
}
};
@@ -818,56 +879,78 @@
= new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- switch (position) {
- // change to identifiers
- case 0:
- // TODO: add additional subtitle track details
- mSubtitleDescriptionsList = new ArrayList<String>();
- mSubtitleDescriptionsList.add(mResources.getString(
- R.string.MediaControlView2_subtitle_off_text));
- for (int i = 0; i < mSubtitleTrackCount; i++) {
- String track = mResources.getString(
- R.string.MediaControlView2_subtitle_track_number_text, i + 1);
- mSubtitleDescriptionsList.add(track);
+ switch (mSettingsMode) {
+ case SETTINGS_MODE_MAIN:
+ if (position == SETTINGS_MODE_AUDIO_TRACK) {
+ mSubSettingsAdapter.setTexts(mAudioTrackList);
+ mSubSettingsAdapter.setCheckPosition(mSelectedAudioTrackIndex);
+ mSettingsMode = SETTINGS_MODE_AUDIO_TRACK;
+ } else if (position == SETTINGS_MODE_PLAYBACK_SPEED) {
+ mSubSettingsAdapter.setTexts(mPlaybackSpeedTextList);
+ mSubSettingsAdapter.setCheckPosition(mSelectedSpeedIndex);
+ mSettingsMode = SETTINGS_MODE_PLAYBACK_SPEED;
+ } else if (position == SETTINGS_MODE_HELP) {
+ // TODO: implement this.
+ mSettingsWindow.dismiss();
+ return;
}
- mSettingsAdapter = new SettingsAdapter(mSubtitleDescriptionsList, null,
- null, true);
+ displaySettingsWindow(mSubSettingsAdapter);
break;
- case 1:
- // TODO: add additional audio track details
- mAudioTrackList = new ArrayList<String>();
- mAudioTrackList.add(mResources.getString(
- R.string.MediaControlView2_audio_track_none_text));
- for (int i = 0; i < mAudioTrackCount; i++) {
- String track = mResources.getString(
- R.string.MediaControlView2_audio_track_number_text, i + 1);
- mAudioTrackList.add(track);
+ case SETTINGS_MODE_AUDIO_TRACK:
+ if (position != mSelectedAudioTrackIndex) {
+ mSelectedAudioTrackIndex = position;
+ if (mAudioTrackCount > 0) {
+ Bundle extra = new Bundle();
+ extra.putInt(KEY_SELECTED_AUDIO_INDEX, position);
+ mController.sendCommand(COMMAND_SELECT_AUDIO_TRACK, extra, null);
+ }
+ mSettingsSubTextsList.set(SETTINGS_MODE_AUDIO_TRACK,
+ mSubSettingsAdapter.getMainText(position));
}
- mSettingsAdapter = new SettingsAdapter(mAudioTrackList, null,
- null, true);
+ mSettingsWindow.dismiss();
break;
- case 2:
- // TODO: add support for multiple quality video tracks
- mSettingsAdapter = new SettingsAdapter(mVideoQualityList, null,
- null, true);
+ case SETTINGS_MODE_PLAYBACK_SPEED:
+ if (position != mSelectedSpeedIndex) {
+ mSelectedSpeedIndex = position;
+ Bundle extra = new Bundle();
+ extra.putFloat(KEY_PLAYBACK_SPEED, mPlaybackSpeedList.get(position));
+ mController.sendCommand(COMMAND_SET_PLAYBACK_SPEED, extra, null);
+ mSettingsSubTextsList.set(SETTINGS_MODE_PLAYBACK_SPEED,
+ mSubSettingsAdapter.getMainText(position));
+ }
+ mSettingsWindow.dismiss();
break;
- case 3:
- // TODO: implement code to reflect change in speed.
- mSettingsAdapter = new SettingsAdapter(mPlaybackSpeedTextIdsList, null,
- null, true);
+ case SETTINGS_MODE_HELP:
+ // TODO: implement this.
break;
- default:
- return;
+ case SETTINGS_MODE_SUBTITLE_TRACK:
+ if (position != mSelectedSubtitleTrackIndex) {
+ mSelectedSubtitleTrackIndex = position;
+ if (position > 0) {
+ Bundle extra = new Bundle();
+ extra.putInt(KEY_SELECTED_SUBTITLE_INDEX, position - 1);
+ mController.sendCommand(
+ MediaControlView2Impl.COMMAND_SHOW_SUBTITLE, extra, null);
+ mSubtitleButton.setImageDrawable(
+ mResources.getDrawable(R.drawable.ic_subtitle_on, null));
+ mSubtitleIsEnabled = true;
+ } else {
+ mController.sendCommand(
+ MediaControlView2Impl.COMMAND_HIDE_SUBTITLE, null, null);
+ mSubtitleButton.setImageDrawable(
+ mResources.getDrawable(R.drawable.ic_subtitle_off, null));
+
+ mSubtitleIsEnabled = false;
+ }
+ }
+ mSettingsWindow.dismiss();
+ break;
+ case SETTINGS_MODE_VIDEO_QUALITY:
+ // TODO: add support for video quality
+ mSelectedVideoQualityIndex = position;
+ mSettingsWindow.dismiss();
+ break;
}
- mSettingsListView.setAdapter(mSettingsAdapter);
- int itemHeight = mResources.getDimensionPixelSize(
- R.dimen.MediaControlView2_settings_height);
- int totalHeight = mSettingsAdapter.getCount() * itemHeight;
- int margin = (-1) * mResources.getDimensionPixelSize(
- R.dimen.MediaControlView2_settings_offset);
- mSettingsWindow.dismiss();
- mSettingsWindow.showAsDropDown(mInstance, margin, margin - totalHeight,
- Gravity.BOTTOM | Gravity.RIGHT);
}
};
@@ -953,80 +1036,56 @@
}
private void initializeSettingsLists() {
- if (mSettingsMainTextsList == null) {
- mSettingsMainTextsList = new ArrayList<String>();
- mSettingsMainTextsList.add(
- mResources.getString(R.string.MediaControlView2_subtitle_text));
- mSettingsMainTextsList.add(
- mResources.getString(R.string.MediaControlView2_audio_track_text));
- mSettingsMainTextsList.add(
- mResources.getString(R.string.MediaControlView2_video_quality_text));
- mSettingsMainTextsList.add(
- mResources.getString(R.string.MediaControlView2_playback_speed_text));
- mSettingsMainTextsList.add(
- mResources.getString(R.string.MediaControlView2_help_text));
- }
+ mSettingsMainTextsList = new ArrayList<String>();
+ mSettingsMainTextsList.add(
+ mResources.getString(R.string.MediaControlView2_audio_track_text));
+ mSettingsMainTextsList.add(
+ mResources.getString(R.string.MediaControlView2_playback_speed_text));
+ mSettingsMainTextsList.add(
+ mResources.getString(R.string.MediaControlView2_help_text));
- // TODO: Update the following code to be dynamic.
- if (mSettingsSubTextsList == null) {
- mSettingsSubTextsList = new ArrayList<String>();
- mSettingsSubTextsList.add(
- mResources.getString(R.string.MediaControlView2_subtitle_off_text));
- mSettingsSubTextsList.add(
- mResources.getString(R.string.MediaControlView2_audio_track_none_text));
- mSettingsSubTextsList.add(
- mResources.getString(R.string.MediaControlView2_video_quality_auto_text));
- mSettingsSubTextsList.add(
- mResources.getString(R.string.MediaControlView2_playback_speed_1x_text));
- mSettingsSubTextsList.add(RESOURCE_EMPTY);
- }
+ mSettingsSubTextsList = new ArrayList<String>();
+ mSettingsSubTextsList.add(
+ mResources.getString(R.string.MediaControlView2_audio_track_none_text));
+ mSettingsSubTextsList.add(
+ mResources.getStringArray(
+ R.array.MediaControlView2_playback_speeds)[PLAYBACK_SPEED_1x_INDEX]);
+ mSettingsSubTextsList.add(RESOURCE_EMPTY);
- if (mSettingsIconIdsList == null) {
- mSettingsIconIdsList = new ArrayList<Integer>();
- mSettingsIconIdsList.add(R.drawable.ic_closed_caption_off);
- mSettingsIconIdsList.add(R.drawable.ic_audiotrack);
- mSettingsIconIdsList.add(R.drawable.ic_high_quality);
- mSettingsIconIdsList.add(R.drawable.ic_play_circle_filled);
- mSettingsIconIdsList.add(R.drawable.ic_help);
- }
+ mSettingsIconIdsList = new ArrayList<Integer>();
+ mSettingsIconIdsList.add(R.drawable.ic_audiotrack);
+ mSettingsIconIdsList.add(R.drawable.ic_play_circle_filled);
+ mSettingsIconIdsList.add(R.drawable.ic_help);
- if (mSubtitleDescriptionsList == null) {
- mSubtitleDescriptionsList = new ArrayList<String>();
- mSubtitleDescriptionsList.add(
- mResources.getString(R.string.MediaControlView2_subtitle_off_text));
- }
+ mAudioTrackList = new ArrayList<String>();
+ mAudioTrackList.add(
+ mResources.getString(R.string.MediaControlView2_audio_track_none_text));
- if (mAudioTrackList == null) {
- mAudioTrackList = new ArrayList<String>();
- mAudioTrackList.add(
- mResources.getString(R.string.MediaControlView2_audio_track_none_text));
- }
+ mVideoQualityList = new ArrayList<String>();
+ mVideoQualityList.add(
+ mResources.getString(R.string.MediaControlView2_video_quality_auto_text));
- if (mVideoQualityList == null) {
- mVideoQualityList = new ArrayList<String>();
- mVideoQualityList.add(
- mResources.getString(R.string.MediaControlView2_video_quality_auto_text));
- }
+ mPlaybackSpeedTextList = new ArrayList<String>(Arrays.asList(
+ mResources.getStringArray(R.array.MediaControlView2_playback_speeds)));
+ // Select the "1x" speed as the default value.
+ mSelectedSpeedIndex = PLAYBACK_SPEED_1x_INDEX;
- if (mPlaybackSpeedTextIdsList == null) {
- mPlaybackSpeedTextIdsList = new ArrayList<String>();
- mPlaybackSpeedTextIdsList.add(
- mResources.getString(R.string.MediaControlView2_playback_speed_0_25x_text));
- mPlaybackSpeedTextIdsList.add(
- mResources.getString(R.string.MediaControlView2_playback_speed_0_5x_text));
- mPlaybackSpeedTextIdsList.add(
- mResources.getString(R.string.MediaControlView2_playback_speed_0_75x_text));
- mPlaybackSpeedTextIdsList.add(
- mResources.getString(R.string.MediaControlView2_playback_speed_1x_text));
- mPlaybackSpeedTextIdsList.add(
- mResources.getString(R.string.MediaControlView2_playback_speed_1_25x_text));
- mPlaybackSpeedTextIdsList.add(
- mResources.getString(R.string.MediaControlView2_playback_speed_1_5x_text));
- mPlaybackSpeedTextIdsList.add(
- mResources.getString(R.string.MediaControlView2_playback_speed_2x_text));
+ mPlaybackSpeedList = new ArrayList<Float>();
+ int[] speeds = mResources.getIntArray(R.array.speed_multiplied_by_100);
+ for (int i = 0; i < speeds.length; i++) {
+ float speed = (float) speeds[i] / 100.0f;
+ mPlaybackSpeedList.add(speed);
}
}
+ private void displaySettingsWindow(BaseAdapter adapter) {
+ mSettingsListView.setAdapter(adapter);
+ int totalHeight = adapter.getCount() * mSettingsItemHeight;
+ mSettingsWindow.dismiss();
+ mSettingsWindow.showAsDropDown(mInstance, mSettingsWindowMargin,
+ mSettingsWindowMargin - totalHeight, Gravity.BOTTOM | Gravity.RIGHT);
+ }
+
private class MediaControllerCallback extends MediaController.Callback {
@Override
public void onPlaybackStateChanged(PlaybackState state) {
@@ -1065,16 +1124,13 @@
if (mPlaybackActions != mPlaybackState.getActions()) {
long newActions = mPlaybackState.getActions();
if ((newActions & PlaybackState.ACTION_PAUSE) != 0) {
- mPlayPauseButton.clearColorFilter();
- mPlayPauseButton.setEnabled(true);
+ mPlayPauseButton.setVisibility(View.VISIBLE);
}
if ((newActions & PlaybackState.ACTION_REWIND) != 0) {
- mRewButton.clearColorFilter();
- mRewButton.setEnabled(true);
+ mRewButton.setVisibility(View.VISIBLE);
}
if ((newActions & PlaybackState.ACTION_FAST_FORWARD) != 0) {
- mFfwdButton.clearColorFilter();
- mFfwdButton.setEnabled(true);
+ mFfwdButton.setVisibility(View.VISIBLE);
}
if ((newActions & PlaybackState.ACTION_SEEK_TO) != 0) {
mSeekAvailable = true;
@@ -1122,16 +1178,42 @@
switch (event) {
case EVENT_UPDATE_TRACK_STATUS:
mVideoTrackCount = extras.getInt(KEY_VIDEO_TRACK_COUNT);
+ // If there is one or more audio tracks, and this information has not been
+ // reflected into the Settings window yet, automatically check the first track.
+ // Otherwise, the Audio Track selection will be defaulted to "None".
mAudioTrackCount = extras.getInt(KEY_AUDIO_TRACK_COUNT);
- int newSubtitleTrackCount = extras.getInt(KEY_SUBTITLE_TRACK_COUNT);
- if (newSubtitleTrackCount > 0) {
- mSubtitleButton.clearColorFilter();
- mSubtitleButton.setEnabled(true);
+ mAudioTrackList = new ArrayList<String>();
+ if (mAudioTrackCount > 0) {
+ // TODO: add more text about track info.
+ for (int i = 0; i < mAudioTrackCount; i++) {
+ String track = mResources.getString(
+ R.string.MediaControlView2_audio_track_number_text, i + 1);
+ mAudioTrackList.add(track);
+ }
+ // Change sub text inside the Settings window.
+ mSettingsSubTextsList.set(SETTINGS_MODE_AUDIO_TRACK,
+ mAudioTrackList.get(0));
} else {
- mSubtitleButton.setColorFilter(R.color.gray);
+ mAudioTrackList.add(mResources.getString(
+ R.string.MediaControlView2_audio_track_none_text));
+ }
+
+ mSubtitleTrackCount = extras.getInt(KEY_SUBTITLE_TRACK_COUNT);
+ mSubtitleDescriptionsList = new ArrayList<String>();
+ if (mSubtitleTrackCount > 0) {
+ mSubtitleButton.setVisibility(View.VISIBLE);
+ mSubtitleButton.setEnabled(true);
+ mSubtitleDescriptionsList.add(mResources.getString(
+ R.string.MediaControlView2_subtitle_off_text));
+ for (int i = 0; i < mSubtitleTrackCount; i++) {
+ String track = mResources.getString(
+ R.string.MediaControlView2_subtitle_track_number_text, i + 1);
+ mSubtitleDescriptionsList.add(track);
+ }
+ } else {
+ mSubtitleButton.setVisibility(View.GONE);
mSubtitleButton.setEnabled(false);
}
- mSubtitleTrackCount = newSubtitleTrackCount;
break;
case EVENT_UPDATE_MEDIA_TYPE_STATUS:
boolean newStatus = extras.getBoolean(KEY_STATE_IS_ADVERTISEMENT);
@@ -1145,17 +1227,29 @@
}
private class SettingsAdapter extends BaseAdapter {
- List<Integer> mIconIds;
- List<String> mMainTexts;
- List<String> mSubTexts;
- boolean mIsCheckable;
+ private List<Integer> mIconIds;
+ private List<String> mMainTexts;
+ private List<String> mSubTexts;
public SettingsAdapter(List<String> mainTexts, @Nullable List<String> subTexts,
- @Nullable List<Integer> iconIds, boolean isCheckable) {
+ @Nullable List<Integer> iconIds) {
mMainTexts = mainTexts;
mSubTexts = subTexts;
mIconIds = iconIds;
- mIsCheckable = isCheckable;
+ }
+
+ public void updateSubTexts(List<String> subTexts) {
+ mSubTexts = subTexts;
+ notifyDataSetChanged();
+ }
+
+ public String getMainText(int position) {
+ if (mMainTexts != null) {
+ if (position < mMainTexts.size()) {
+ return mMainTexts.get(position);
+ }
+ }
+ return RESOURCE_EMPTY;
}
@Override
@@ -1184,7 +1278,6 @@
TextView mainTextView = (TextView) row.findViewById(R.id.main_text);
TextView subTextView = (TextView) row.findViewById(R.id.sub_text);
ImageView iconView = (ImageView) row.findViewById(R.id.icon);
- ImageView checkView = (ImageView) row.findViewById(R.id.check);
// Set main text
mainTextView.setText(mMainTexts.get(position));
@@ -1206,13 +1299,72 @@
// Otherwise, set main icon.
iconView.setImageDrawable(mResources.getDrawable(mIconIds.get(position), null));
}
+ return row;
+ }
- // Set check icon
- // TODO: make the following code dynamic
- if (!mIsCheckable) {
- checkView.setVisibility(View.GONE);
+ public void setSubTexts(List<String> subTexts) {
+ mSubTexts = subTexts;
+ }
+ }
+
+ // TODO: extend this class from SettingsAdapter
+ private class SubSettingsAdapter extends BaseAdapter {
+ private List<String> mTexts;
+ private int mCheckPosition;
+
+ public SubSettingsAdapter(List<String> texts, int checkPosition) {
+ mTexts = texts;
+ mCheckPosition = checkPosition;
+ }
+
+ public String getMainText(int position) {
+ if (mTexts != null) {
+ if (position < mTexts.size()) {
+ return mTexts.get(position);
+ }
+ }
+ return RESOURCE_EMPTY;
+ }
+
+ @Override
+ public int getCount() {
+ return (mTexts == null) ? 0 : mTexts.size();
+ }
+
+ @Override
+ public long getItemId(int position) {
+ // Auto-generated method stub--does not have any purpose here
+ // TODO: implement this.
+ return 0;
+ }
+
+ @Override
+ public Object getItem(int position) {
+ // Auto-generated method stub--does not have any purpose here
+ // TODO: implement this.
+ return null;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup container) {
+ View row = ApiHelper.inflateLibLayout(mInstance.getContext(),
+ R.layout.sub_settings_list_item);
+ TextView textView = (TextView) row.findViewById(R.id.text);
+ ImageView checkView = (ImageView) row.findViewById(R.id.check);
+
+ textView.setText(mTexts.get(position));
+ if (position != mCheckPosition) {
+ checkView.setVisibility(View.INVISIBLE);
}
return row;
}
+
+ public void setTexts(List<String> texts) {
+ mTexts = texts;
+ }
+
+ public void setCheckPosition(int checkPosition) {
+ mCheckPosition = checkPosition;
+ }
}
}
diff --git a/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java b/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
index 0bb659d..264f632 100644
--- a/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
+++ b/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
@@ -86,6 +86,7 @@
private static final int STATE_PLAYBACK_COMPLETED = 5;
private static final int INVALID_TRACK_INDEX = -1;
+ private static final float INVALID_SPEED = 0f;
private AccessibilityManager mAccessibilityManager;
private AudioManager mAudioManager;
@@ -137,6 +138,9 @@
// TODO: Remove mFallbackSpeed when integration with MediaPlayer2's new setPlaybackParams().
// Refer: https://docs.google.com/document/d/1nzAfns6i2hJ3RkaUre3QMT6wsDedJ5ONLiA_OOBFFX8/edit
private float mFallbackSpeed; // keep the original speed before 'pause' is called.
+ private float mVolumeLevelFloat;
+ private int mVolumeLevel;
+
private long mShowControllerIntervalMs;
private MediaRouter mMediaRouter;
@@ -651,7 +655,7 @@
};
mMediaPlayer.setMediaPlayer2EventCallback(executor, mMediaPlayer2Callback);
- mCurrentBufferPercentage = 0;
+ mCurrentBufferPercentage = -1;
mMediaPlayer.setDataSource(dsd);
mMediaPlayer.setAudioAttributes(mAudioAttributes);
// we don't set the target state here either, but preserve the
@@ -753,8 +757,12 @@
&& mCurrentState != STATE_PREPARING) {
// TODO: this should be replaced with MediaPlayer2.getBufferedPosition() once it is
// implemented.
- mStateBuilder.setBufferedPosition(
- (long) (mCurrentBufferPercentage / 100.0) * mMediaPlayer.getDuration());
+ if (mCurrentBufferPercentage == -1) {
+ mStateBuilder.setBufferedPosition(-1);
+ } else {
+ mStateBuilder.setBufferedPosition(
+ (long) (mCurrentBufferPercentage / 100.0 * mMediaPlayer.getDuration()));
+ }
}
// Set PlaybackState for MediaSession
@@ -854,8 +862,6 @@
}
if (select) {
if (mSubtitleTrackIndices.size() > 0) {
- // TODO: make this selection dynamic
- mSelectedSubtitleTrackIndex = mSubtitleTrackIndices.get(0);
mMediaPlayer.selectTrack(mSelectedSubtitleTrackIndex);
mSubtitleView.setVisibility(View.VISIBLE);
}
@@ -881,9 +887,17 @@
mAudioTrackIndices.add(i);
} else if (trackType == MediaPlayer2.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE
|| trackType == MediaPlayer2.TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT) {
- mSubtitleTrackIndices.add(i);
+ mSubtitleTrackIndices.add(i);
}
}
+ // Select first tracks as default
+ if (mVideoTrackIndices.size() > 0) {
+ mSelectedVideoTrackIndex = 0;
+ }
+ if (mAudioTrackIndices.size() > 0) {
+ mSelectedAudioTrackIndex = 0;
+ }
+
Bundle data = new Bundle();
data.putInt(MediaControlView2Impl.KEY_VIDEO_TRACK_COUNT, mVideoTrackIndices.size());
data.putInt(MediaControlView2Impl.KEY_AUDIO_TRACK_COUNT, mAudioTrackIndices.size());
@@ -1048,10 +1062,19 @@
} else {
switch (command) {
case MediaControlView2Impl.COMMAND_SHOW_SUBTITLE:
- mInstance.setSubtitleEnabled(true);
+ int subtitleIndex = args.getInt(
+ MediaControlView2Impl.KEY_SELECTED_SUBTITLE_INDEX,
+ INVALID_TRACK_INDEX);
+ if (subtitleIndex != INVALID_TRACK_INDEX) {
+ int subtitleTrackIndex = mSubtitleTrackIndices.get(subtitleIndex);
+ if (subtitleTrackIndex != mSelectedSubtitleTrackIndex) {
+ mSelectedSubtitleTrackIndex = subtitleTrackIndex;
+ selectOrDeselectSubtitle(true);
+ }
+ }
break;
case MediaControlView2Impl.COMMAND_HIDE_SUBTITLE:
- mInstance.setSubtitleEnabled(false);
+ selectOrDeselectSubtitle(false);
break;
case MediaControlView2Impl.COMMAND_SET_FULLSCREEN:
if (mFullScreenRequestListener != null) {
@@ -1060,6 +1083,32 @@
args.getBoolean(MediaControlView2Impl.ARGUMENT_KEY_FULLSCREEN));
}
break;
+ case MediaControlView2Impl.COMMAND_SELECT_AUDIO_TRACK:
+ int audioIndex = args.getInt(MediaControlView2Impl.KEY_SELECTED_AUDIO_INDEX,
+ INVALID_TRACK_INDEX);
+ if (audioIndex != INVALID_TRACK_INDEX) {
+ int audioTrackIndex = mAudioTrackIndices.get(audioIndex);
+ if (audioTrackIndex != mSelectedAudioTrackIndex) {
+ mSelectedAudioTrackIndex = audioTrackIndex;
+ mMediaPlayer.selectTrack(mSelectedAudioTrackIndex);
+ }
+ }
+ break;
+ case MediaControlView2Impl.COMMAND_SET_PLAYBACK_SPEED:
+ float speed = args.getFloat(
+ MediaControlView2Impl.KEY_PLAYBACK_SPEED, INVALID_SPEED);
+ if (speed != INVALID_SPEED && speed != mSpeed) {
+ mInstance.setSpeed(speed);
+ mSpeed = speed;
+ }
+ break;
+ case MediaControlView2Impl.COMMAND_MUTE:
+ mVolumeLevel = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+ mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 0, 0);
+ break;
+ case MediaControlView2Impl.COMMAND_UNMUTE:
+ mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, mVolumeLevel, 0);
+ break;
}
}
showController();
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index 229e08e..8033382 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -3051,6 +3051,7 @@
// check recording permission for visualizer
if ((memcmp(&desc.type, SL_IID_VISUALIZATION, sizeof(effect_uuid_t)) == 0) &&
+ // TODO: Do we need to start/stop op - i.e. is there recording being performed?
!recordingAllowed(opPackageName, pid, IPCThreadState::self()->getCallingUid())) {
lStatus = PERMISSION_DENIED;
goto Exit;
diff --git a/services/audioflinger/ServiceUtilities.cpp b/services/audioflinger/ServiceUtilities.cpp
index f08698e..aa267ea 100644
--- a/services/audioflinger/ServiceUtilities.cpp
+++ b/services/audioflinger/ServiceUtilities.cpp
@@ -30,6 +30,8 @@
namespace android {
+static const String16 sAndroidPermissionRecordAudio("android.permission.RECORD_AUDIO");
+
// Not valid until initialized by AudioFlinger constructor. It would have to be
// re-initialized if the process containing AudioFlinger service forks (which it doesn't).
// This is often used to validate binder interface calls within audioserver
@@ -48,26 +50,11 @@
}
}
-bool recordingAllowed(const String16& opPackageName, pid_t pid, uid_t uid) {
- // we're always OK.
- if (getpid_cached == IPCThreadState::self()->getCallingPid()) return true;
-
- static const String16 sRecordAudio("android.permission.RECORD_AUDIO");
-
- // We specify a pid and uid here as mediaserver (aka MediaRecorder or StageFrightRecorder)
- // may open a record track on behalf of a client. Note that pid may be a tid.
- // IMPORTANT: Don't use PermissionCache - a runtime permission and may change.
- const bool ok = checkPermission(sRecordAudio, pid, uid);
- if (!ok) {
- ALOGE("Request requires android.permission.RECORD_AUDIO");
- return false;
+static String16 resolveCallingPackage(PermissionController& permissionController,
+ const String16& opPackageName, uid_t uid) {
+ if (opPackageName.size() > 0) {
+ return opPackageName;
}
-
- // To permit command-line native tests
- if (uid == AID_ROOT) return true;
-
- String16 checkedOpPackageName = opPackageName;
-
// In some cases the calling code has no access to the package it runs under.
// For example, code using the wilhelm framework's OpenSL-ES APIs. In this
// case we will get the packages for the calling UID and pick the first one
@@ -75,40 +62,89 @@
// as for legacy apps we will toggle the app op for all packages in the UID.
// The caveat is that the operation may be attributed to the wrong package and
// stats based on app ops may be slightly off.
- if (checkedOpPackageName.size() <= 0) {
- sp<IServiceManager> sm = defaultServiceManager();
- sp<IBinder> binder = sm->getService(String16("permission"));
- if (binder == 0) {
- ALOGE("Cannot get permission service");
- return false;
- }
+ Vector<String16> packages;
+ permissionController.getPackagesForUid(uid, packages);
+ if (packages.isEmpty()) {
+ ALOGE("No packages for uid %d", uid);
+ return opPackageName; // empty string
+ }
+ return packages[0];
+}
- sp<IPermissionController> permCtrl = interface_cast<IPermissionController>(binder);
- Vector<String16> packages;
+static inline bool isAudioServerOrRoot(uid_t uid) {
+ // AID_ROOT is OK for command-line tests. Native unforked audioserver always OK.
+ return uid == AID_ROOT || uid == AID_AUDIOSERVER ;
+}
- permCtrl->getPackagesForUid(uid, packages);
+static bool checkRecordingInternal(const String16& opPackageName, pid_t pid,
+ uid_t uid, bool start) {
+ // Okay to not track in app ops as audio server is us and if
+ // device is rooted security model is considered compromised.
+ if (isAudioServerOrRoot(uid)) return true;
- if (packages.isEmpty()) {
- ALOGE("No packages for calling UID");
- return false;
- }
- checkedOpPackageName = packages[0];
+ // We specify a pid and uid here as mediaserver (aka MediaRecorder or StageFrightRecorder)
+ // may open a record track on behalf of a client. Note that pid may be a tid.
+ // IMPORTANT: DON'T USE PermissionCache - RUNTIME PERMISSIONS CHANGE.
+ PermissionController permissionController;
+ const bool ok = permissionController.checkPermission(sAndroidPermissionRecordAudio, pid, uid);
+ if (!ok) {
+ ALOGE("Request requires %s", String8(sAndroidPermissionRecordAudio).c_str());
+ return false;
+ }
+
+ String16 resolvedOpPackageName = resolveCallingPackage(
+ permissionController, opPackageName, uid);
+ if (resolvedOpPackageName.size() == 0) {
+ return false;
}
AppOpsManager appOps;
- if (appOps.noteOp(AppOpsManager::OP_RECORD_AUDIO, uid, checkedOpPackageName)
- != AppOpsManager::MODE_ALLOWED) {
- ALOGE("Request denied by app op OP_RECORD_AUDIO");
- return false;
+ const int32_t op = appOps.permissionToOpCode(sAndroidPermissionRecordAudio);
+ if (start) {
+ if (appOps.startOpNoThrow(op, uid, resolvedOpPackageName, /*startIfModeDefault*/ false)
+ != AppOpsManager::MODE_ALLOWED) {
+ ALOGE("Request denied by app op: %d", op);
+ return false;
+ }
+ } else {
+ if (appOps.noteOp(op, uid, resolvedOpPackageName) != AppOpsManager::MODE_ALLOWED) {
+ ALOGE("Request denied by app op: %d", op);
+ return false;
+ }
}
return true;
}
+bool recordingAllowed(const String16& opPackageName, pid_t pid, uid_t uid) {
+ return checkRecordingInternal(opPackageName, pid, uid, /*start*/ false);
+}
+
+bool startRecording(const String16& opPackageName, pid_t pid, uid_t uid) {
+ return checkRecordingInternal(opPackageName, pid, uid, /*start*/ true);
+}
+
+void finishRecording(const String16& opPackageName, uid_t uid) {
+ // Okay to not track in app ops as audio server is us and if
+ // device is rooted security model is considered compromised.
+ if (isAudioServerOrRoot(uid)) return;
+
+ PermissionController permissionController;
+ String16 resolvedOpPackageName = resolveCallingPackage(
+ permissionController, opPackageName, uid);
+ if (resolvedOpPackageName.size() == 0) {
+ return;
+ }
+
+ AppOpsManager appOps;
+ const int32_t op = appOps.permissionToOpCode(sAndroidPermissionRecordAudio);
+ appOps.finishOp(op, uid, resolvedOpPackageName);
+}
+
bool captureAudioOutputAllowed(pid_t pid, uid_t uid) {
if (getpid_cached == IPCThreadState::self()->getCallingPid()) return true;
static const String16 sCaptureAudioOutput("android.permission.CAPTURE_AUDIO_OUTPUT");
- bool ok = checkPermission(sCaptureAudioOutput, pid, uid);
+ bool ok = PermissionCache::checkPermission(sCaptureAudioOutput, pid, uid);
if (!ok) ALOGE("Request requires android.permission.CAPTURE_AUDIO_OUTPUT");
return ok;
}
@@ -155,7 +191,7 @@
bool modifyPhoneStateAllowed(pid_t pid, uid_t uid) {
static const String16 sModifyPhoneState("android.permission.MODIFY_PHONE_STATE");
- bool ok = checkPermission(sModifyPhoneState, pid, uid);
+ bool ok = PermissionCache::checkPermission(sModifyPhoneState, pid, uid);
if (!ok) ALOGE("Request requires android.permission.MODIFY_PHONE_STATE");
return ok;
}
diff --git a/services/audioflinger/ServiceUtilities.h b/services/audioflinger/ServiceUtilities.h
index 83533dd..f45ada1 100644
--- a/services/audioflinger/ServiceUtilities.h
+++ b/services/audioflinger/ServiceUtilities.h
@@ -16,11 +16,15 @@
#include <unistd.h>
+#include <binder/PermissionController.h>
+
namespace android {
extern pid_t getpid_cached;
bool isTrustedCallingUid(uid_t uid);
bool recordingAllowed(const String16& opPackageName, pid_t pid, uid_t uid);
+bool startRecording(const String16& opPackageName, pid_t pid, uid_t uid);
+void finishRecording(const String16& opPackageName, uid_t uid);
bool captureAudioOutputAllowed(pid_t pid, uid_t uid);
bool captureHotwordAllowed(pid_t pid, uid_t uid);
bool settingsAllowed();
diff --git a/services/audiopolicy/common/include/Volume.h b/services/audiopolicy/common/include/Volume.h
index 1239fe0..4862684 100644
--- a/services/audiopolicy/common/include/Volume.h
+++ b/services/audiopolicy/common/include/Volume.h
@@ -125,6 +125,7 @@
case AUDIO_DEVICE_OUT_BLUETOOTH_A2DP:
case AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES:
case AUDIO_DEVICE_OUT_USB_HEADSET:
+ case AUDIO_DEVICE_OUT_HEARING_AID:
return DEVICE_CATEGORY_HEADSET;
case AUDIO_DEVICE_OUT_LINE:
case AUDIO_DEVICE_OUT_AUX_DIGITAL:
diff --git a/services/audiopolicy/common/managerdefinitions/include/AudioOutputDescriptor.h b/services/audiopolicy/common/managerdefinitions/include/AudioOutputDescriptor.h
index 78a184b..5e5d38b 100644
--- a/services/audiopolicy/common/managerdefinitions/include/AudioOutputDescriptor.h
+++ b/services/audiopolicy/common/managerdefinitions/include/AudioOutputDescriptor.h
@@ -203,6 +203,16 @@
*/
audio_io_handle_t getA2dpOutput() const;
+ /**
+ * returns true if primary HAL supports A2DP Offload
+ */
+ bool isA2dpOffloadedOnPrimary() const;
+
+ /**
+ * returns true if A2DP is supported (either via hardware offload or software encoding)
+ */
+ bool isA2dpSupported() const;
+
sp<SwAudioOutputDescriptor> getOutputFromId(audio_port_handle_t id) const;
sp<SwAudioOutputDescriptor> getPrimaryOutput() const;
diff --git a/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp b/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp
index caaa0f7..294a2a6 100644
--- a/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp
+++ b/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp
@@ -645,6 +645,29 @@
return 0;
}
+bool SwAudioOutputCollection::isA2dpOffloadedOnPrimary() const
+{
+ sp<SwAudioOutputDescriptor> primaryOutput = getPrimaryOutput();
+
+ if ((primaryOutput != NULL) && (primaryOutput->mProfile != NULL)
+ && (primaryOutput->mProfile->mModule != NULL)) {
+ sp<HwModule> primaryHwModule = primaryOutput->mProfile->mModule;
+ Vector <sp<IOProfile>> primaryHwModuleOutputProfiles =
+ primaryHwModule->getOutputProfiles();
+ for (size_t i = 0; i < primaryHwModuleOutputProfiles.size(); i++) {
+ if (primaryHwModuleOutputProfiles[i]->supportDevice(AUDIO_DEVICE_OUT_ALL_A2DP)) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+bool SwAudioOutputCollection::isA2dpSupported() const
+{
+ return (isA2dpOffloadedOnPrimary() || (getA2dpOutput() != 0));
+}
+
sp<SwAudioOutputDescriptor> SwAudioOutputCollection::getPrimaryOutput() const
{
for (size_t i = 0; i < size(); i++) {
diff --git a/services/audiopolicy/config/audio_policy_configuration.xml b/services/audiopolicy/config/audio_policy_configuration.xml
index 73efe8e..a75f1cb 100644
--- a/services/audiopolicy/config/audio_policy_configuration.xml
+++ b/services/audiopolicy/config/audio_policy_configuration.xml
@@ -182,6 +182,9 @@
<!-- Remote Submix Audio HAL -->
<xi:include href="r_submix_audio_policy_configuration.xml"/>
+ <!-- Hearing aid Audio HAL -->
+ <xi:include href="hearing_aid_audio_policy_configuration.xml"/>
+
</modules>
<!-- End of Modules section -->
diff --git a/services/audiopolicy/config/hearing_aid_audio_policy_configuration.xml b/services/audiopolicy/config/hearing_aid_audio_policy_configuration.xml
new file mode 100644
index 0000000..3c48e88
--- /dev/null
+++ b/services/audiopolicy/config/hearing_aid_audio_policy_configuration.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Hearing aid Audio HAL Audio Policy Configuration file -->
+<module name="hearing_aid" halVersion="2.0">
+ <mixPorts>
+ <mixPort name="hearing aid output" role="source" flags="AUDIO_OUTPUT_FLAG_PRIMARY">
+ <profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
+ samplingRates="24000,16000"
+ channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
+ </mixPort>
+ </mixPorts>
+ <devicePorts>
+ <devicePort tagName="BT Hearing Aid Out" type="AUDIO_DEVICE_OUT_HEARING_AID" role="sink"/>
+ </devicePorts>
+ <routes>
+ <route type="mix" sink="BT Hearing Aid Out" sources="hearing aid output"/>
+ </routes>
+</module>
diff --git a/services/audiopolicy/enginedefault/src/Engine.cpp b/services/audiopolicy/enginedefault/src/Engine.cpp
index 5ec0475..977a396 100644
--- a/services/audiopolicy/enginedefault/src/Engine.cpp
+++ b/services/audiopolicy/enginedefault/src/Engine.cpp
@@ -306,8 +306,14 @@
sp<AudioOutputDescriptor> primaryOutput = outputs.getPrimaryOutput();
audio_devices_t availPrimaryInputDevices =
availableInputDevices.getDevicesFromHwModule(primaryOutput->getModuleHandle());
+
+ // TODO: getPrimaryOutput return only devices from first module in
+ // audio_policy_configuration.xml, hearing aid is not there, but it's
+ // a primary device
+ // FIXME: this is not the right way of solving this problem
audio_devices_t availPrimaryOutputDevices =
- primaryOutput->supportedDevices() & availableOutputDevices.types();
+ (primaryOutput->supportedDevices() | AUDIO_DEVICE_OUT_HEARING_AID) &
+ availableOutputDevices.types();
if (((availableInputDevices.types() &
AUDIO_DEVICE_IN_TELEPHONY_RX & ~AUDIO_DEVICE_BIT_IN) == 0) ||
@@ -332,10 +338,12 @@
// FALL THROUGH
default: // FORCE_NONE
+ device = availableOutputDevicesType & AUDIO_DEVICE_OUT_HEARING_AID;
+ if (device) break;
// when not in a phone call, phone strategy should route STREAM_VOICE_CALL to A2DP
if (!isInCall() &&
(mForceUse[AUDIO_POLICY_FORCE_FOR_MEDIA] != AUDIO_POLICY_FORCE_NO_BT_A2DP) &&
- (outputs.getA2dpOutput() != 0)) {
+ outputs.isA2dpSupported()) {
device = availableOutputDevicesType & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP;
if (device) break;
device = availableOutputDevicesType & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES;
@@ -369,7 +377,7 @@
// A2DP speaker when forcing to speaker output
if (!isInCall() &&
(mForceUse[AUDIO_POLICY_FORCE_FOR_MEDIA] != AUDIO_POLICY_FORCE_NO_BT_A2DP) &&
- (outputs.getA2dpOutput() != 0)) {
+ outputs.isA2dpSupported()) {
device = availableOutputDevicesType & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER;
if (device) break;
}
@@ -481,9 +489,12 @@
outputDeviceTypesToIgnore);
break;
}
+ if (device2 == AUDIO_DEVICE_NONE) {
+ device2 = availableOutputDevicesType & AUDIO_DEVICE_OUT_HEARING_AID;
+ }
if ((device2 == AUDIO_DEVICE_NONE) &&
(mForceUse[AUDIO_POLICY_FORCE_FOR_MEDIA] != AUDIO_POLICY_FORCE_NO_BT_A2DP) &&
- (outputs.getA2dpOutput() != 0)) {
+ outputs.isA2dpSupported()) {
device2 = availableOutputDevicesType & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP;
if (device2 == AUDIO_DEVICE_NONE) {
device2 = availableOutputDevicesType & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES;
diff --git a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
index 42b199a..74ca902 100644
--- a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
+++ b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
@@ -26,6 +26,7 @@
#define AUDIO_POLICY_XML_CONFIG_FILE_PATH_MAX_LENGTH 128
#define AUDIO_POLICY_XML_CONFIG_FILE_NAME "audio_policy_configuration.xml"
+#define AUDIO_POLICY_A2DP_OFFLOAD_XML_CONFIG_FILE_NAME "audio_policy_a2dp_offload_configuration.xml"
#include <inttypes.h>
#include <math.h>
@@ -3517,11 +3518,14 @@
for (int i = 0; i < kConfigLocationListSize; i++) {
PolicySerializer serializer;
+ bool use_a2dp_offload_config =
+ property_get_bool("persist.bluetooth.a2dp_offload.enable", false);
snprintf(audioPolicyXmlConfigFile,
sizeof(audioPolicyXmlConfigFile),
"%s/%s",
kConfigLocationList[i],
- AUDIO_POLICY_XML_CONFIG_FILE_NAME);
+ use_a2dp_offload_config ? AUDIO_POLICY_A2DP_OFFLOAD_XML_CONFIG_FILE_NAME :
+ AUDIO_POLICY_XML_CONFIG_FILE_NAME);
ret = serializer.deserialize(audioPolicyXmlConfigFile, config);
if (ret == NO_ERROR) {
break;
@@ -4381,7 +4385,7 @@
void AudioPolicyManager::checkA2dpSuspend()
{
audio_io_handle_t a2dpOutput = mOutputs.getA2dpOutput();
- if (a2dpOutput == 0) {
+ if (a2dpOutput == 0 || mOutputs.isA2dpOffloadedOnPrimary()) {
mA2dpSuspended = false;
return;
}
diff --git a/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp b/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp
index 306de3f..8f0c846 100644
--- a/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp
+++ b/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp
@@ -412,7 +412,7 @@
}
// check calling permissions
- if (!recordingAllowed(client->opPackageName, client->pid, client->uid)) {
+ if (!startRecording(client->opPackageName, client->pid, client->uid)) {
ALOGE("%s permission denied: recording not allowed for uid %d pid %d",
__func__, client->uid, client->pid);
return PERMISSION_DENIED;
@@ -439,6 +439,8 @@
if (concurrency & AudioPolicyInterface::API_INPUT_CONCURRENCY_CAPTURE) {
//TODO: check concurrent capture permission
}
+ } else {
+ finishRecording(client->opPackageName, client->uid);
}
return status;
@@ -457,6 +459,9 @@
}
sp<AudioRecordClient> client = mAudioRecordClients.valueAt(index);
+ // finish the recording app op
+ finishRecording(client->opPackageName, client->uid);
+
return mAudioPolicyManager->stopInput(client->input, client->session);
}
diff --git a/services/camera/libcameraservice/api1/Camera2Client.cpp b/services/camera/libcameraservice/api1/Camera2Client.cpp
index 3578bba..65faac9 100644
--- a/services/camera/libcameraservice/api1/Camera2Client.cpp
+++ b/services/camera/libcameraservice/api1/Camera2Client.cpp
@@ -779,7 +779,35 @@
int lastJpegStreamId = mJpegProcessor->getStreamId();
// If jpeg stream will slow down preview, make sure we remove it before starting preview
if (params.slowJpegMode) {
- mJpegProcessor->deleteStream();
+ // Pause preview if we are streaming
+ int32_t activeRequestId = mStreamingProcessor->getActiveRequestId();
+ if (activeRequestId != 0) {
+ res = mStreamingProcessor->togglePauseStream(/*pause*/true);
+ if (res != OK) {
+ ALOGE("%s: Camera %d: Can't pause streaming: %s (%d)",
+ __FUNCTION__, mCameraId, strerror(-res), res);
+ }
+ res = mDevice->waitUntilDrained();
+ if (res != OK) {
+ ALOGE("%s: Camera %d: Waiting to stop streaming failed: %s (%d)",
+ __FUNCTION__, mCameraId, strerror(-res), res);
+ }
+ }
+
+ res = mJpegProcessor->deleteStream();
+
+ if (res != OK) {
+ ALOGE("%s: Camera %d: delete Jpeg stream failed: %s (%d)",
+ __FUNCTION__, mCameraId, strerror(-res), res);
+ }
+
+ if (activeRequestId != 0) {
+ res = mStreamingProcessor->togglePauseStream(/*pause*/false);
+ if (res != OK) {
+ ALOGE("%s: Camera %d: Can't unpause streaming: %s (%d)",
+ __FUNCTION__, mCameraId, strerror(-res), res);
+ }
+ }
} else {
res = updateProcessorStream(mJpegProcessor, params);
if (res != OK) {
diff --git a/services/camera/libcameraservice/api1/client2/JpegProcessor.cpp b/services/camera/libcameraservice/api1/client2/JpegProcessor.cpp
index cc4249f..b7020fe 100755
--- a/services/camera/libcameraservice/api1/client2/JpegProcessor.cpp
+++ b/services/camera/libcameraservice/api1/client2/JpegProcessor.cpp
@@ -199,7 +199,11 @@
return INVALID_OPERATION;
}
- device->deleteStream(mCaptureStreamId);
+ status_t res = device->deleteStream(mCaptureStreamId);
+ if (res != OK) {
+ ALOGE("%s: delete stream %d failed!", __FUNCTION__, mCaptureStreamId);
+ return res;
+ }
mCaptureHeap.clear();
mCaptureWindow.clear();
diff --git a/services/camera/libcameraservice/api2/CameraDeviceClient.cpp b/services/camera/libcameraservice/api2/CameraDeviceClient.cpp
index 9ab2d88..c49de8e 100644
--- a/services/camera/libcameraservice/api2/CameraDeviceClient.cpp
+++ b/services/camera/libcameraservice/api2/CameraDeviceClient.cpp
@@ -1002,10 +1002,6 @@
case HAL_PIXEL_FORMAT_BLOB:
case HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED:
case HAL_PIXEL_FORMAT_YCbCr_420_888:
- case HAL_PIXEL_FORMAT_YCbCr_422_888:
- case HAL_PIXEL_FORMAT_YCbCr_444_888:
- case HAL_PIXEL_FORMAT_FLEX_RGB_888:
- case HAL_PIXEL_FORMAT_FLEX_RGBA_8888:
case HAL_PIXEL_FORMAT_YCbCr_422_SP:
case HAL_PIXEL_FORMAT_YCrCb_420_SP:
case HAL_PIXEL_FORMAT_YCbCr_422_I:
diff --git a/services/camera/libcameraservice/device3/Camera3Device.cpp b/services/camera/libcameraservice/device3/Camera3Device.cpp
index 42bcb2d..67b5e06 100644
--- a/services/camera/libcameraservice/device3/Camera3Device.cpp
+++ b/services/camera/libcameraservice/device3/Camera3Device.cpp
@@ -1550,7 +1550,7 @@
// CameraDevice semantics require device to already be idle before
// deleteStream is called, unlike for createStream.
if (mStatus == STATUS_ACTIVE) {
- ALOGV("%s: Camera %s: Device not idle", __FUNCTION__, mId.string());
+ ALOGW("%s: Camera %s: Device not idle", __FUNCTION__, mId.string());
return -EBUSY;
}
diff --git a/services/codec2/Android.bp b/services/codec2/Android.bp
new file mode 100644
index 0000000..4cfca1d
--- /dev/null
+++ b/services/codec2/Android.bp
@@ -0,0 +1,101 @@
+cc_binary {
+ name: "vendor.google.media.c2@1.0-service",
+ defaults: ["hidl_defaults"],
+ soc_specific: true,
+ relative_install_path: "hw",
+ srcs: [
+ "vendor.cpp",
+ ],
+
+ init_rc: ["vendor.google.media.c2@1.0-service.rc"],
+
+ shared_libs: [
+ "vendor.google.media.c2@1.0",
+ "libavservices_minijail_vendor",
+ "libhidlbase",
+ "libhidltransport",
+ "libhwbinder",
+ "liblog",
+ "libstagefright_codec2_hidl@1.0",
+ "libstagefright_codec2_vndk",
+ "libutils",
+ ],
+
+ arch: {
+ arm: {
+ required: ["codec2.vendor.base.policy"],
+ },
+ x86: {
+ required: ["codec2.vendor.base.policy"],
+ },
+ },
+
+ compile_multilib: "32",
+}
+
+cc_binary {
+ name: "vendor.google.media.c2@1.0-service-system",
+ defaults: ["hidl_defaults"],
+ relative_install_path: "hw",
+ srcs: [
+ "system.cpp",
+ ],
+
+ init_rc: ["vendor.google.media.c2@1.0-service-system.rc"],
+
+ shared_libs: [
+ "vendor.google.media.c2@1.0",
+ "libavservices_minijail",
+ "libcutils",
+ "libhidlbase",
+ "libhidltransport",
+ "libhwbinder",
+ "liblog",
+ "libstagefright_codec2_hidl@1.0",
+ "libstagefright_codec2_vndk",
+ "libutils",
+ "libv4l2_c2componentstore",
+ ],
+
+ arch: {
+ arm: {
+ required: ["codec2.system.base.policy"],
+ },
+ x86: {
+ required: ["codec2.system.base.policy"],
+ },
+ },
+
+ required: [
+ "libstagefright_soft_c2avcdec",
+ "libstagefright_soft_c2avcenc",
+ "libstagefright_soft_c2aacdec",
+ "libstagefright_soft_c2aacenc",
+ "libstagefright_soft_c2amrnbdec",
+ "libstagefright_soft_c2amrnbenc",
+ "libstagefright_soft_c2amrwbdec",
+ "libstagefright_soft_c2amrwbenc",
+ "libstagefright_soft_c2hevcdec",
+ "libstagefright_soft_c2g711alawdec",
+ "libstagefright_soft_c2g711mlawdec",
+ "libstagefright_soft_c2mpeg2dec",
+ "libstagefright_soft_c2h263dec",
+ "libstagefright_soft_c2h263enc",
+ "libstagefright_soft_c2mpeg4dec",
+ "libstagefright_soft_c2mpeg4enc",
+ "libstagefright_soft_c2mp3dec",
+ "libstagefright_soft_c2vorbisdec",
+ "libstagefright_soft_c2opusdec",
+ "libstagefright_soft_c2vp8dec",
+ "libstagefright_soft_c2vp9dec",
+ "libstagefright_soft_c2vp8enc",
+ "libstagefright_soft_c2vp9enc",
+ "libstagefright_soft_c2rawdec",
+ "libstagefright_soft_c2flacdec",
+ "libstagefright_soft_c2flacenc",
+ "libstagefright_soft_c2gsmdec",
+ ],
+
+ compile_multilib: "32",
+}
+
diff --git a/services/codec2/Android.mk b/services/codec2/Android.mk
new file mode 100644
index 0000000..fa49875
--- /dev/null
+++ b/services/codec2/Android.mk
@@ -0,0 +1,43 @@
+LOCAL_PATH := $(call my-dir)
+
+# vendor service seccomp policy
+ifeq ($(TARGET_ARCH), $(filter $(TARGET_ARCH), x86 x86_64 arm arm64))
+include $(CLEAR_VARS)
+LOCAL_MODULE := codec2.vendor.base.policy
+LOCAL_VENDOR_MODULE := true
+LOCAL_MODULE_CLASS := ETC
+LOCAL_MODULE_PATH := $(TARGET_OUT_VENDOR)/etc/seccomp_policy
+LOCAL_REQUIRED_MODULES := crash_dump.policy
+ifdef TARGET_2ND_ARCH
+ ifneq ($(TARGET_TRANSLATE_2ND_ARCH),true)
+ LOCAL_SRC_FILES := seccomp_policy/codec2.vendor.base-$(TARGET_2ND_ARCH).policy
+ else
+ LOCAL_SRC_FILES := seccomp_policy/codec2.vendor.base-$(TARGET_ARCH).policy
+ endif
+else
+ LOCAL_SRC_FILES := seccomp_policy/codec2.vendor.base-$(TARGET_ARCH).policy
+endif
+include $(BUILD_PREBUILT)
+endif
+
+# system service seccomp policy
+ifeq ($(TARGET_ARCH), $(filter $(TARGET_ARCH), x86 x86_64 arm arm64))
+include $(CLEAR_VARS)
+LOCAL_MODULE := codec2.system.base.policy
+LOCAL_MODULE_CLASS := ETC
+LOCAL_MODULE_PATH := $(TARGET_OUT)/etc/seccomp_policy
+LOCAL_REQUIRED_MODULES := crash_dump.policy
+ifdef TARGET_2ND_ARCH
+ ifneq ($(TARGET_TRANSLATE_2ND_ARCH),true)
+ LOCAL_SRC_FILES := seccomp_policy/codec2.system.base-$(TARGET_2ND_ARCH).policy
+ else
+ LOCAL_SRC_FILES := seccomp_policy/codec2.system.base-$(TARGET_ARCH).policy
+ endif
+else
+ LOCAL_SRC_FILES := seccomp_policy/codec2.system.base-$(TARGET_ARCH).policy
+endif
+include $(BUILD_PREBUILT)
+endif
+
+include $(call all-makefiles-under, $(LOCAL_PATH))
+
diff --git a/services/codec2/seccomp_policy/codec2.system.base-arm.policy b/services/codec2/seccomp_policy/codec2.system.base-arm.policy
new file mode 100644
index 0000000..d5871d1
--- /dev/null
+++ b/services/codec2/seccomp_policy/codec2.system.base-arm.policy
@@ -0,0 +1,73 @@
+# Copyright (C) 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Organized by frequency of systemcall - in descending order for
+# best performance.
+futex: 1
+ioctl: 1
+write: 1
+prctl: 1
+clock_gettime: 1
+getpriority: 1
+read: 1
+close: 1
+writev: 1
+dup: 1
+ppoll: 1
+mmap2: 1
+getrandom: 1
+
+# mremap: Ensure |flags| are (MREMAP_MAYMOVE | MREMAP_FIXED) TODO: Once minijail
+# parser support for '<' is in this needs to be modified to also prevent
+# |old_address| and |new_address| from touching the exception vector page, which
+# on ARM is statically loaded at 0xffff 0000. See
+# http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0211h/Babfeega.html
+# for more details.
+mremap: arg3 == 3
+munmap: 1
+mprotect: 1
+madvise: 1
+openat: 1
+sigaltstack: 1
+clone: 1
+setpriority: 1
+getuid32: 1
+fstat64: 1
+fstatfs64: 1
+pread64: 1
+faccessat: 1
+readlinkat: 1
+exit: 1
+rt_sigprocmask: 1
+set_tid_address: 1
+restart_syscall: 1
+exit_group: 1
+rt_sigreturn: 1
+pipe2: 1
+gettimeofday: 1
+sched_yield: 1
+nanosleep: 1
+lseek: 1
+_llseek: 1
+sched_get_priority_max: 1
+sched_get_priority_min: 1
+statfs64: 1
+sched_setscheduler: 1
+fstatat64: 1
+ugetrlimit: 1
+getdents64: 1
+getrandom: 1
+
+@include /system/etc/seccomp_policy/crash_dump.arm.policy
+
diff --git a/services/codec2/seccomp_policy/codec2.system.base-x86.policy b/services/codec2/seccomp_policy/codec2.system.base-x86.policy
new file mode 100644
index 0000000..20c7625
--- /dev/null
+++ b/services/codec2/seccomp_policy/codec2.system.base-x86.policy
@@ -0,0 +1,57 @@
+# Copyright (C) 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+read: 1
+mprotect: 1
+prctl: 1
+openat: 1
+getuid32: 1
+writev: 1
+ioctl: 1
+close: 1
+mmap2: 1
+fstat64: 1
+madvise: 1
+fstatat64: 1
+futex: 1
+munmap: 1
+faccessat: 1
+_llseek: 1
+lseek: 1
+clone: 1
+sigaltstack: 1
+setpriority: 1
+restart_syscall: 1
+exit: 1
+exit_group: 1
+rt_sigreturn: 1
+ugetrlimit: 1
+readlinkat: 1
+_llseek: 1
+fstatfs64: 1
+pread64: 1
+mremap: 1
+dup: 1
+set_tid_address: 1
+write: 1
+nanosleep: 1
+
+# Required by AddressSanitizer
+gettid: 1
+sched_yield: 1
+getpid: 1
+gettid: 1
+
+@include /system/etc/seccomp_policy/crash_dump.x86.policy
+
diff --git a/services/codec2/seccomp_policy/codec2.vendor.base-arm.policy b/services/codec2/seccomp_policy/codec2.vendor.base-arm.policy
new file mode 100644
index 0000000..d5871d1
--- /dev/null
+++ b/services/codec2/seccomp_policy/codec2.vendor.base-arm.policy
@@ -0,0 +1,73 @@
+# Copyright (C) 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Organized by frequency of systemcall - in descending order for
+# best performance.
+futex: 1
+ioctl: 1
+write: 1
+prctl: 1
+clock_gettime: 1
+getpriority: 1
+read: 1
+close: 1
+writev: 1
+dup: 1
+ppoll: 1
+mmap2: 1
+getrandom: 1
+
+# mremap: Ensure |flags| are (MREMAP_MAYMOVE | MREMAP_FIXED) TODO: Once minijail
+# parser support for '<' is in this needs to be modified to also prevent
+# |old_address| and |new_address| from touching the exception vector page, which
+# on ARM is statically loaded at 0xffff 0000. See
+# http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0211h/Babfeega.html
+# for more details.
+mremap: arg3 == 3
+munmap: 1
+mprotect: 1
+madvise: 1
+openat: 1
+sigaltstack: 1
+clone: 1
+setpriority: 1
+getuid32: 1
+fstat64: 1
+fstatfs64: 1
+pread64: 1
+faccessat: 1
+readlinkat: 1
+exit: 1
+rt_sigprocmask: 1
+set_tid_address: 1
+restart_syscall: 1
+exit_group: 1
+rt_sigreturn: 1
+pipe2: 1
+gettimeofday: 1
+sched_yield: 1
+nanosleep: 1
+lseek: 1
+_llseek: 1
+sched_get_priority_max: 1
+sched_get_priority_min: 1
+statfs64: 1
+sched_setscheduler: 1
+fstatat64: 1
+ugetrlimit: 1
+getdents64: 1
+getrandom: 1
+
+@include /system/etc/seccomp_policy/crash_dump.arm.policy
+
diff --git a/services/codec2/seccomp_policy/codec2.vendor.base-x86.policy b/services/codec2/seccomp_policy/codec2.vendor.base-x86.policy
new file mode 100644
index 0000000..20c7625
--- /dev/null
+++ b/services/codec2/seccomp_policy/codec2.vendor.base-x86.policy
@@ -0,0 +1,57 @@
+# Copyright (C) 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+read: 1
+mprotect: 1
+prctl: 1
+openat: 1
+getuid32: 1
+writev: 1
+ioctl: 1
+close: 1
+mmap2: 1
+fstat64: 1
+madvise: 1
+fstatat64: 1
+futex: 1
+munmap: 1
+faccessat: 1
+_llseek: 1
+lseek: 1
+clone: 1
+sigaltstack: 1
+setpriority: 1
+restart_syscall: 1
+exit: 1
+exit_group: 1
+rt_sigreturn: 1
+ugetrlimit: 1
+readlinkat: 1
+_llseek: 1
+fstatfs64: 1
+pread64: 1
+mremap: 1
+dup: 1
+set_tid_address: 1
+write: 1
+nanosleep: 1
+
+# Required by AddressSanitizer
+gettid: 1
+sched_yield: 1
+getpid: 1
+gettid: 1
+
+@include /system/etc/seccomp_policy/crash_dump.x86.policy
+
diff --git a/services/codec2/system.cpp b/services/codec2/system.cpp
new file mode 100644
index 0000000..d6ec644
--- /dev/null
+++ b/services/codec2/system.cpp
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "vendor.google.media.c2@1.0-service"
+
+#include <C2PlatformSupport.h>
+#include <C2V4l2Support.h>
+#include <cutils/properties.h>
+
+#include <codec2/hidl/1.0/ComponentStore.h>
+#include <hidl/HidlTransportSupport.h>
+#include <minijail.h>
+
+// TODO: Remove this once "setenv()" call is removed.
+#include <stdlib.h>
+
+// This is created by module "codec2.system.base.policy". This can be modified.
+static constexpr char kBaseSeccompPolicyPath[] =
+ "/system/etc/seccomp_policy/codec2.system.base.policy";
+
+// Additional device-specific seccomp permissions can be added in this file.
+static constexpr char kExtSeccompPolicyPath[] =
+ "/system/etc/seccomp_policy/codec2.system.ext.policy";
+
+int main(int /* argc */, char** /* argv */) {
+ ALOGD("vendor.google.media.c2@1.0-service-system starting...");
+
+ // TODO: Remove this when all the build settings and sepolicies are in place.
+ setenv("TREBLE_TESTING_OVERRIDE", "true", true);
+
+ signal(SIGPIPE, SIG_IGN);
+ android::SetUpMinijail(kBaseSeccompPolicyPath, kExtSeccompPolicyPath);
+
+ // Extra threads may be needed to handle a stacked IPC sequence that
+ // contains alternating binder and hwbinder calls. (See b/35283480.)
+ android::hardware::configureRpcThreadpool(8, true /* callerWillJoin */);
+
+ // Create IComponentStore service.
+ {
+ using namespace ::vendor::google::media::c2::V1_0;
+ android::sp<IComponentStore> store =
+ new implementation::ComponentStore(
+ android::GetCodec2PlatformComponentStore());
+ if (store == nullptr) {
+ ALOGE("Cannot create Codec2's IComponentStore system service.");
+ } else {
+ if (store->registerAsService("system") != android::OK) {
+ ALOGE("Cannot register Codec2's "
+ "IComponentStore system service.");
+ } else {
+ ALOGI("Codec2's IComponentStore system service created.");
+ }
+ }
+
+ // To enable the v4l2 service, set this sysprop and add "v4l2" instance
+ // to the system manifest file.
+ if (property_get_bool("debug.stagefright.ccodec_v4l2", false)) {
+ store = new implementation::ComponentStore(
+ android::GetCodec2VDAComponentStore());
+ if (store == nullptr) {
+ ALOGE("Cannot create Codec2's IComponentStore V4L2 service.");
+ } else {
+ if (store->registerAsService("v4l2") != android::OK) {
+ ALOGE("Cannot register Codec2's "
+ "IComponentStore V4L2 service.");
+ } else {
+ ALOGI("Codec2's IComponentStore V4L2 service created.");
+ }
+ }
+ }
+ }
+
+ android::hardware::joinRpcThreadpool();
+ return 0;
+}
+
diff --git a/services/codec2/vendor.cpp b/services/codec2/vendor.cpp
new file mode 100644
index 0000000..60b51e2
--- /dev/null
+++ b/services/codec2/vendor.cpp
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "vendor.google.media.c2@1.0-service"
+
+#include <codec2/hidl/1.0/ComponentStore.h>
+#include <hidl/HidlTransportSupport.h>
+#include <minijail.h>
+
+#include <C2Component.h>
+
+// TODO: Remove this once "setenv()" call is removed.
+#include <stdlib.h>
+
+// This is created by module "codec2.vendor.base.policy". This can be modified.
+static constexpr char kBaseSeccompPolicyPath[] =
+ "/vendor/etc/seccomp_policy/codec2.vendor.base.policy";
+
+// Additional device-specific seccomp permissions can be added in this file.
+static constexpr char kExtSeccompPolicyPath[] =
+ "/vendor/etc/seccomp_policy/codec2.vendor.ext.policy";
+
+// TODO: Replace with a valid C2ComponentStore implementation.
+class DummyC2Store : public C2ComponentStore {
+public:
+ DummyC2Store() = default;
+
+ virtual ~DummyC2Store() override = default;
+
+ virtual C2String getName() const override {
+ return "default";
+ }
+
+ virtual c2_status_t createComponent(
+ C2String /*name*/,
+ std::shared_ptr<C2Component>* const /*component*/) override {
+ return C2_NOT_FOUND;
+ }
+
+ virtual c2_status_t createInterface(
+ C2String /* name */,
+ std::shared_ptr<C2ComponentInterface>* const /* interface */) override {
+ return C2_NOT_FOUND;
+ }
+
+ virtual std::vector<std::shared_ptr<const C2Component::Traits>>
+ listComponents() override {
+ return {};
+ }
+
+ virtual c2_status_t copyBuffer(
+ std::shared_ptr<C2GraphicBuffer> /* src */,
+ std::shared_ptr<C2GraphicBuffer> /* dst */) override {
+ return C2_OMITTED;
+ }
+
+ virtual c2_status_t query_sm(
+ const std::vector<C2Param*>& /* stackParams */,
+ const std::vector<C2Param::Index>& /* heapParamIndices */,
+ std::vector<std::unique_ptr<C2Param>>* const /* heapParams */) const override {
+ return C2_OMITTED;
+ }
+
+ virtual c2_status_t config_sm(
+ const std::vector<C2Param*>& /* params */,
+ std::vector<std::unique_ptr<C2SettingResult>>* const /* failures */) override {
+ return C2_OMITTED;
+ }
+
+ virtual std::shared_ptr<C2ParamReflector> getParamReflector() const override {
+ return nullptr;
+ }
+
+ virtual c2_status_t querySupportedParams_nb(
+ std::vector<std::shared_ptr<C2ParamDescriptor>>* const /* params */) const override {
+ return C2_OMITTED;
+ }
+
+ virtual c2_status_t querySupportedValues_sm(
+ std::vector<C2FieldSupportedValuesQuery>& /* fields */) const override {
+ return C2_OMITTED;
+ }
+};
+
+int main(int /* argc */, char** /* argv */) {
+ ALOGD("vendor.google.media.c2@1.0-service starting...");
+
+ // TODO: Remove this when all the build settings and sepolicies are in place.
+ setenv("TREBLE_TESTING_OVERRIDE", "true", true);
+
+ signal(SIGPIPE, SIG_IGN);
+ android::SetUpMinijail(kBaseSeccompPolicyPath, kExtSeccompPolicyPath);
+
+ // Extra threads may be needed to handle a stacked IPC sequence that
+ // contains alternating binder and hwbinder calls. (See b/35283480.)
+ android::hardware::configureRpcThreadpool(8, true /* callerWillJoin */);
+
+ // Create IComponentStore service.
+ {
+ using namespace ::vendor::google::media::c2::V1_0;
+ android::sp<IComponentStore> store =
+ new implementation::ComponentStore(
+ // TODO: Replace this with a valid C2ComponentStore
+ // implementation.
+ std::make_shared<DummyC2Store>());
+ if (store == nullptr) {
+ ALOGE("Cannot create Codec2's IComponentStore service.");
+ } else {
+ if (store->registerAsService("default") != android::OK) {
+ ALOGE("Cannot register Codec2's "
+ "IComponentStore service.");
+ } else {
+ ALOGI("Codec2's IComponentStore service created.");
+ }
+ }
+ }
+
+ android::hardware::joinRpcThreadpool();
+ return 0;
+}
+
diff --git a/services/codec2/vendor.google.media.c2@1.0-service-system.rc b/services/codec2/vendor.google.media.c2@1.0-service-system.rc
new file mode 100644
index 0000000..0577a1d
--- /dev/null
+++ b/services/codec2/vendor.google.media.c2@1.0-service-system.rc
@@ -0,0 +1,7 @@
+service vendor-google-media-c2-system-hal-1-0 /system/bin/hw/vendor.google.media.c2@1.0-service-system
+ class hal
+ user media
+ group mediadrm drmrpc
+ ioprio rt 4
+ writepid /dev/cpuset/foreground/tasks
+
diff --git a/services/codec2/vendor.google.media.c2@1.0-service.rc b/services/codec2/vendor.google.media.c2@1.0-service.rc
new file mode 100644
index 0000000..3e7e0a6
--- /dev/null
+++ b/services/codec2/vendor.google.media.c2@1.0-service.rc
@@ -0,0 +1,7 @@
+service vendor-google-media-c2-hal-1-0 /vendor/bin/hw/vendor.google.media.c2@1.0-service
+ class hal
+ user media
+ group mediadrm drmrpc
+ ioprio rt 4
+ writepid /dev/cpuset/foreground/tasks
+