Merge "Enable building libmp4extractor as a static library for fuzzing"
diff --git a/camera/CaptureResult.cpp b/camera/CaptureResult.cpp
index e6c0d00..928a6bc 100644
--- a/camera/CaptureResult.cpp
+++ b/camera/CaptureResult.cpp
@@ -60,6 +60,39 @@
return OK;
}
+status_t PhysicalCaptureResultInfo::readFromParcel(const android::Parcel* parcel) {
+ status_t res;
+
+ mPhysicalCameraId.remove(mPhysicalCameraId.size());
+ mPhysicalCameraMetadata.clear();
+
+ if ((res = parcel->readString16(&mPhysicalCameraId)) != OK) {
+ ALOGE("%s: Failed to read camera id: %d", __FUNCTION__, res);
+ return res;
+ }
+
+ if ((res = mPhysicalCameraMetadata.readFromParcel(parcel)) != OK) {
+ ALOGE("%s: Failed to read metadata from parcel: %d", __FUNCTION__, res);
+ return res;
+ }
+ return OK;
+}
+
+status_t PhysicalCaptureResultInfo::writeToParcel(android::Parcel* parcel) const {
+ status_t res;
+ if ((res = parcel->writeString16(mPhysicalCameraId)) != OK) {
+ ALOGE("%s: Failed to write physical camera ID to parcel: %d",
+ __FUNCTION__, res);
+ return res;
+ }
+ if ((res = mPhysicalCameraMetadata.writeToParcel(parcel)) != OK) {
+ ALOGE("%s: Failed to write physical camera metadata to parcel: %d",
+ __FUNCTION__, res);
+ return res;
+ }
+ return OK;
+}
+
CaptureResult::CaptureResult() :
mMetadata(), mResultExtras() {
}
@@ -67,6 +100,7 @@
CaptureResult::CaptureResult(const CaptureResult &otherResult) {
mResultExtras = otherResult.mResultExtras;
mMetadata = otherResult.mMetadata;
+ mPhysicalMetadatas = otherResult.mPhysicalMetadatas;
}
status_t CaptureResult::readFromParcel(android::Parcel *parcel) {
@@ -79,6 +113,7 @@
}
mMetadata.clear();
+ mPhysicalMetadatas.clear();
status_t res = OK;
res = mMetadata.readFromParcel(parcel);
@@ -89,6 +124,34 @@
}
ALOGV("%s: Read metadata from parcel", __FUNCTION__);
+ int32_t physicalMetadataCount;
+ if ((res = parcel->readInt32(&physicalMetadataCount)) != OK) {
+ ALOGE("%s: Failed to read the physical metadata count from parcel: %d", __FUNCTION__, res);
+ return res;
+ }
+ if (physicalMetadataCount < 0) {
+ ALOGE("%s: Invalid physical metadata count from parcel: %d",
+ __FUNCTION__, physicalMetadataCount);
+ return BAD_VALUE;
+ }
+
+ for (int32_t i = 0; i < physicalMetadataCount; i++) {
+ String16 cameraId;
+ if ((res = parcel->readString16(&cameraId)) != OK) {
+ ALOGE("%s: Failed to read camera id: %d", __FUNCTION__, res);
+ return res;
+ }
+
+ CameraMetadata physicalMetadata;
+ if ((res = physicalMetadata.readFromParcel(parcel)) != OK) {
+ ALOGE("%s: Failed to read metadata from parcel: %d", __FUNCTION__, res);
+ return res;
+ }
+
+ mPhysicalMetadatas.emplace(mPhysicalMetadatas.end(), cameraId, physicalMetadata);
+ }
+ ALOGV("%s: Read physical metadata from parcel", __FUNCTION__);
+
res = mResultExtras.readFromParcel(parcel);
if (res != OK) {
ALOGE("%s: Failed to read result extras from parcel.",
@@ -118,6 +181,27 @@
}
ALOGV("%s: Wrote metadata to parcel", __FUNCTION__);
+ int32_t physicalMetadataCount = static_cast<int32_t>(mPhysicalMetadatas.size());
+ res = parcel->writeInt32(physicalMetadataCount);
+ if (res != OK) {
+ ALOGE("%s: Failed to write physical metadata count to parcel: %d",
+ __FUNCTION__, res);
+ return BAD_VALUE;
+ }
+ for (const auto& physicalMetadata : mPhysicalMetadatas) {
+ if ((res = parcel->writeString16(physicalMetadata.mPhysicalCameraId)) != OK) {
+ ALOGE("%s: Failed to write physical camera ID to parcel: %d",
+ __FUNCTION__, res);
+ return res;
+ }
+ if ((res = physicalMetadata.mPhysicalCameraMetadata.writeToParcel(parcel)) != OK) {
+ ALOGE("%s: Failed to write physical camera metadata to parcel: %d",
+ __FUNCTION__, res);
+ return res;
+ }
+ }
+ ALOGV("%s: Wrote physical camera metadata to parcel", __FUNCTION__);
+
res = mResultExtras.writeToParcel(parcel);
if (res != OK) {
ALOGE("%s: Failed to write result extras to parcel", __FUNCTION__);
diff --git a/camera/aidl/android/hardware/camera2/ICameraDeviceCallbacks.aidl b/camera/aidl/android/hardware/camera2/ICameraDeviceCallbacks.aidl
index 28252c0..58b19a3 100644
--- a/camera/aidl/android/hardware/camera2/ICameraDeviceCallbacks.aidl
+++ b/camera/aidl/android/hardware/camera2/ICameraDeviceCallbacks.aidl
@@ -18,6 +18,7 @@
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.impl.CaptureResultExtras;
+import android.hardware.camera2.impl.PhysicalCaptureResultInfo;
/** @hide */
interface ICameraDeviceCallbacks
@@ -30,12 +31,14 @@
const int ERROR_CAMERA_REQUEST = 3;
const int ERROR_CAMERA_RESULT = 4;
const int ERROR_CAMERA_BUFFER = 5;
+ const int ERROR_CAMERA_DISABLED = 6;
oneway void onDeviceError(int errorCode, in CaptureResultExtras resultExtras);
oneway void onDeviceIdle();
oneway void onCaptureStarted(in CaptureResultExtras resultExtras, long timestamp);
oneway void onResultReceived(in CameraMetadataNative result,
- in CaptureResultExtras resultExtras);
+ in CaptureResultExtras resultExtras,
+ in PhysicalCaptureResultInfo[] physicalCaptureResultInfos);
oneway void onPrepared(int streamId);
/**
diff --git a/camera/aidl/android/hardware/camera2/impl/PhysicalCaptureResultInfo.aidl b/camera/aidl/android/hardware/camera2/impl/PhysicalCaptureResultInfo.aidl
new file mode 100644
index 0000000..78d9b7b
--- /dev/null
+++ b/camera/aidl/android/hardware/camera2/impl/PhysicalCaptureResultInfo.aidl
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+package android.hardware.camera2.impl;
+
+/** @hide */
+parcelable PhysicalCaptureResultInfo cpp_header "camera/CaptureResult.h";
diff --git a/camera/camera2/CaptureRequest.cpp b/camera/camera2/CaptureRequest.cpp
index 983d29b..1843ec4 100644
--- a/camera/camera2/CaptureRequest.cpp
+++ b/camera/camera2/CaptureRequest.cpp
@@ -18,6 +18,7 @@
// #define LOG_NDEBUG 0
#define LOG_TAG "CameraRequest"
#include <utils/Log.h>
+#include <utils/String16.h>
#include <camera/camera2/CaptureRequest.h>
@@ -42,18 +43,39 @@
return BAD_VALUE;
}
- mMetadata.clear();
mSurfaceList.clear();
mStreamIdxList.clear();
mSurfaceIdxList.clear();
+ mPhysicalCameraSettings.clear();
status_t err = OK;
- if ((err = mMetadata.readFromParcel(parcel)) != OK) {
- ALOGE("%s: Failed to read metadata from parcel", __FUNCTION__);
+ int32_t settingsCount;
+ if ((err = parcel->readInt32(&settingsCount)) != OK) {
+ ALOGE("%s: Failed to read the settings count from parcel: %d", __FUNCTION__, err);
return err;
}
- ALOGV("%s: Read metadata from parcel", __FUNCTION__);
+
+ if (settingsCount <= 0) {
+ ALOGE("%s: Settings count %d should always be positive!", __FUNCTION__, settingsCount);
+ return BAD_VALUE;
+ }
+
+ for (int32_t i = 0; i < settingsCount; i++) {
+ String16 id;
+ if ((err = parcel->readString16(&id)) != OK) {
+ ALOGE("%s: Failed to read camera id!", __FUNCTION__);
+ return BAD_VALUE;
+ }
+
+ CameraMetadata settings;
+ if ((err = settings.readFromParcel(parcel)) != OK) {
+ ALOGE("%s: Failed to read metadata from parcel", __FUNCTION__);
+ return err;
+ }
+ ALOGV("%s: Read metadata from parcel", __FUNCTION__);
+ mPhysicalCameraSettings.push_back({std::string(String8(id).string()), settings});
+ }
int isReprocess = 0;
if ((err = parcel->readInt32(&isReprocess)) != OK) {
@@ -135,10 +157,25 @@
status_t err = OK;
- if ((err = mMetadata.writeToParcel(parcel)) != OK) {
+ int32_t settingsCount = static_cast<int32_t>(mPhysicalCameraSettings.size());
+
+ if ((err = parcel->writeInt32(settingsCount)) != OK) {
+ ALOGE("%s: Failed to write settings count!", __FUNCTION__);
return err;
}
+ for (const auto &it : mPhysicalCameraSettings) {
+ if ((err = parcel->writeString16(String16(it.id.c_str()))) != OK) {
+ ALOGE("%s: Failed to camera id!", __FUNCTION__);
+ return err;
+ }
+
+ if ((err = it.settings.writeToParcel(parcel)) != OK) {
+ ALOGE("%s: Failed to write settings!", __FUNCTION__);
+ return err;
+ }
+ }
+
parcel->writeInt32(mIsReprocess ? 1 : 0);
if (mSurfaceConverted) {
diff --git a/camera/camera2/OutputConfiguration.cpp b/camera/camera2/OutputConfiguration.cpp
index 813d6c9..feb04c2 100644
--- a/camera/camera2/OutputConfiguration.cpp
+++ b/camera/camera2/OutputConfiguration.cpp
@@ -1,6 +1,6 @@
/*
**
-** Copyright 2015, The Android Open Source Project
+** Copyright 2015-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.
@@ -64,6 +64,10 @@
return mIsShared;
}
+String16 OutputConfiguration::getPhysicalCameraId() const {
+ return mPhysicalCameraId;
+}
+
OutputConfiguration::OutputConfiguration() :
mRotation(INVALID_ROTATION),
mSurfaceSetID(INVALID_SET_ID),
@@ -139,6 +143,8 @@
return err;
}
+ parcel->readString16(&mPhysicalCameraId);
+
mRotation = rotation;
mSurfaceSetID = setID;
mSurfaceType = surfaceType;
@@ -153,8 +159,9 @@
mGbps.push_back(surface.graphicBufferProducer);
}
- ALOGV("%s: OutputConfiguration: rotation = %d, setId = %d, surfaceType = %d",
- __FUNCTION__, mRotation, mSurfaceSetID, mSurfaceType);
+ ALOGV("%s: OutputConfiguration: rotation = %d, setId = %d, surfaceType = %d,"
+ " physicalCameraId = %s", __FUNCTION__, mRotation, mSurfaceSetID,
+ mSurfaceType, String8(mPhysicalCameraId).string());
return err;
}
@@ -204,6 +211,9 @@
err = parcel->writeParcelableVector(surfaceShims);
if (err != OK) return err;
+ err = parcel->writeString16(mPhysicalCameraId);
+ if (err != OK) return err;
+
return OK;
}
diff --git a/camera/include/camera/CaptureResult.h b/camera/include/camera/CaptureResult.h
index 917d953..56fa178 100644
--- a/camera/include/camera/CaptureResult.h
+++ b/camera/include/camera/CaptureResult.h
@@ -91,14 +91,36 @@
virtual status_t readFromParcel(const android::Parcel* parcel) override;
virtual status_t writeToParcel(android::Parcel* parcel) const override;
};
+
+struct PhysicalCaptureResultInfo : public android::Parcelable {
+
+ PhysicalCaptureResultInfo()
+ : mPhysicalCameraId(),
+ mPhysicalCameraMetadata() {
+ }
+ PhysicalCaptureResultInfo(const String16& cameraId,
+ const CameraMetadata& cameraMetadata)
+ : mPhysicalCameraId(cameraId),
+ mPhysicalCameraMetadata(cameraMetadata) {
+ }
+
+ String16 mPhysicalCameraId;
+ CameraMetadata mPhysicalCameraMetadata;
+
+ virtual status_t readFromParcel(const android::Parcel* parcel) override;
+ virtual status_t writeToParcel(android::Parcel* parcel) const override;
+};
+
} // namespace impl
} // namespace camera2
} // namespace hardware
using hardware::camera2::impl::CaptureResultExtras;
+using hardware::camera2::impl::PhysicalCaptureResultInfo;
struct CaptureResult : public virtual LightRefBase<CaptureResult> {
CameraMetadata mMetadata;
+ std::vector<PhysicalCaptureResultInfo> mPhysicalMetadatas;
CaptureResultExtras mResultExtras;
CaptureResult();
diff --git a/camera/include/camera/camera2/CaptureRequest.h b/camera/include/camera/camera2/CaptureRequest.h
index c53799f..506abab 100644
--- a/camera/include/camera/camera2/CaptureRequest.h
+++ b/camera/include/camera/camera2/CaptureRequest.h
@@ -40,7 +40,11 @@
CaptureRequest(CaptureRequest&& rhs) noexcept;
virtual ~CaptureRequest();
- CameraMetadata mMetadata;
+ struct PhysicalCameraSettings {
+ std::string id;
+ CameraMetadata settings;
+ };
+ std::vector<PhysicalCameraSettings> mPhysicalCameraSettings;
// Used by NDK client to pass surfaces by stream/surface index.
bool mSurfaceConverted = false;
diff --git a/camera/include/camera/camera2/OutputConfiguration.h b/camera/include/camera/camera2/OutputConfiguration.h
index 3599604..a80f44b 100644
--- a/camera/include/camera/camera2/OutputConfiguration.h
+++ b/camera/include/camera/camera2/OutputConfiguration.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2015-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.
@@ -46,6 +46,7 @@
int getHeight() const;
bool isDeferred() const;
bool isShared() const;
+ String16 getPhysicalCameraId() const;
/**
* Keep impl up-to-date with OutputConfiguration.java in frameworks/base
*/
@@ -74,7 +75,8 @@
mHeight == other.mHeight &&
mIsDeferred == other.mIsDeferred &&
mIsShared == other.mIsShared &&
- gbpsEqual(other));
+ gbpsEqual(other) &&
+ mPhysicalCameraId == other.mPhysicalCameraId );
}
bool operator != (const OutputConfiguration& other) const {
return !(*this == other);
@@ -102,6 +104,9 @@
if (mIsShared != other.mIsShared) {
return mIsShared < other.mIsShared;
}
+ if (mPhysicalCameraId != other.mPhysicalCameraId) {
+ return mPhysicalCameraId < other.mPhysicalCameraId;
+ }
return gbpsLessThan(other);
}
bool operator > (const OutputConfiguration& other) const {
@@ -120,8 +125,7 @@
int mHeight;
bool mIsDeferred;
bool mIsShared;
- // helper function
- static String16 readMaybeEmptyString16(const android::Parcel* parcel);
+ String16 mPhysicalCameraId;
};
} // namespace params
} // namespace camera2
diff --git a/camera/ndk/impl/ACameraDevice.cpp b/camera/ndk/impl/ACameraDevice.cpp
index f7cea4f..907debc 100644
--- a/camera/ndk/impl/ACameraDevice.cpp
+++ b/camera/ndk/impl/ACameraDevice.cpp
@@ -372,7 +372,8 @@
const ACaptureRequest* request, /*out*/sp<CaptureRequest>& outReq) {
camera_status_t ret;
sp<CaptureRequest> req(new CaptureRequest());
- req->mMetadata = request->settings->getInternalData();
+ req->mPhysicalCameraSettings.push_back({std::string(mCameraId.string()),
+ request->settings->getInternalData()});
req->mIsReprocess = false; // NDK does not support reprocessing yet
req->mContext = request->context;
req->mSurfaceConverted = true; // set to true, and fill in stream/surface idx to speed up IPC
@@ -418,7 +419,7 @@
ACaptureRequest*
CameraDevice::allocateACaptureRequest(sp<CaptureRequest>& req) {
ACaptureRequest* pRequest = new ACaptureRequest();
- CameraMetadata clone = req->mMetadata;
+ CameraMetadata clone = req->mPhysicalCameraSettings.begin()->settings;
pRequest->settings = new ACameraMetadata(clone.release(), ACameraMetadata::ACM_REQUEST);
pRequest->targets = new ACameraOutputTargets();
for (size_t i = 0; i < req->mSurfaceList.size(); i++) {
@@ -1405,7 +1406,9 @@
binder::Status
CameraDevice::ServiceCallback::onResultReceived(
const CameraMetadata& metadata,
- const CaptureResultExtras& resultExtras) {
+ const CaptureResultExtras& resultExtras,
+ const std::vector<PhysicalCaptureResultInfo>& physicalResultInfos) {
+ (void) physicalResultInfos;
binder::Status ret = binder::Status::ok();
sp<CameraDevice> dev = mDevice.promote();
diff --git a/camera/ndk/impl/ACameraDevice.h b/camera/ndk/impl/ACameraDevice.h
index 1db3dfb..1369148 100644
--- a/camera/ndk/impl/ACameraDevice.h
+++ b/camera/ndk/impl/ACameraDevice.h
@@ -74,7 +74,8 @@
binder::Status onCaptureStarted(const CaptureResultExtras& resultExtras,
int64_t timestamp) override;
binder::Status onResultReceived(const CameraMetadata& metadata,
- const CaptureResultExtras& resultExtras) override;
+ const CaptureResultExtras& resultExtras,
+ const std::vector<PhysicalCaptureResultInfo>& physicalResultInfos) override;
binder::Status onPrepared(int streamId) override;
binder::Status onRequestQueueEmpty() override;
binder::Status onRepeatingRequestError(int64_t lastFrameNumber,
diff --git a/camera/ndk/impl/ACameraManager.cpp b/camera/ndk/impl/ACameraManager.cpp
index 3f64bcc..a1a8cd6 100644
--- a/camera/ndk/impl/ACameraManager.cpp
+++ b/camera/ndk/impl/ACameraManager.cpp
@@ -340,6 +340,9 @@
msg->setString(kCameraIdKey, AString(cameraId));
msg->post();
}
+ if (status == hardware::ICameraServiceListener::STATUS_NOT_PRESENT) {
+ mDeviceStatusMap.erase(cameraId);
+ }
}
} // namespace android
diff --git a/camera/ndk/impl/ACameraMetadata.cpp b/camera/ndk/impl/ACameraMetadata.cpp
index 29ad09b..62b0ec9 100644
--- a/camera/ndk/impl/ACameraMetadata.cpp
+++ b/camera/ndk/impl/ACameraMetadata.cpp
@@ -305,6 +305,7 @@
case ACAMERA_STATISTICS_FACE_DETECT_MODE:
case ACAMERA_STATISTICS_HOT_PIXEL_MAP_MODE:
case ACAMERA_STATISTICS_LENS_SHADING_MAP_MODE:
+ case ACAMERA_STATISTICS_OIS_DATA_MODE:
case ACAMERA_TONEMAP_CURVE_BLUE:
case ACAMERA_TONEMAP_CURVE_GREEN:
case ACAMERA_TONEMAP_CURVE_RED:
diff --git a/camera/ndk/include/camera/NdkCameraMetadataTags.h b/camera/ndk/include/camera/NdkCameraMetadataTags.h
index 2c144b7..588e96a 100644
--- a/camera/ndk/include/camera/NdkCameraMetadataTags.h
+++ b/camera/ndk/include/camera/NdkCameraMetadataTags.h
@@ -69,6 +69,7 @@
ACAMERA_SYNC,
ACAMERA_REPROCESS,
ACAMERA_DEPTH,
+ ACAMERA_LOGICAL_MULTI_CAMERA,
ACAMERA_SECTION_COUNT,
ACAMERA_VENDOR = 0x8000
@@ -104,6 +105,9 @@
ACAMERA_SYNC_START = ACAMERA_SYNC << 16,
ACAMERA_REPROCESS_START = ACAMERA_REPROCESS << 16,
ACAMERA_DEPTH_START = ACAMERA_DEPTH << 16,
+ ACAMERA_LOGICAL_MULTI_CAMERA_START
+ = ACAMERA_LOGICAL_MULTI_CAMERA
+ << 16,
ACAMERA_VENDOR_START = ACAMERA_VENDOR << 16
} acamera_metadata_section_start_t;
@@ -1282,7 +1286,7 @@
* <p>State | Transition Cause | New State | Notes
* :------------:|:----------------:|:---------:|:-----------------------:
* INACTIVE | | INACTIVE | Camera device auto exposure algorithm is disabled</p>
- * <p>When ACAMERA_CONTROL_AE_MODE is AE_MODE_ON_*:</p>
+ * <p>When ACAMERA_CONTROL_AE_MODE is AE_MODE_ON*:</p>
* <p>State | Transition Cause | New State | Notes
* :-------------:|:--------------------------------------------:|:--------------:|:-----------------:
* INACTIVE | Camera device initiates AE scan | SEARCHING | Values changing
@@ -1303,10 +1307,13 @@
* LOCKED | aeLock is ON and aePrecaptureTrigger is CANCEL| LOCKED | Precapture trigger is ignored when AE is already locked
* Any state (excluding LOCKED) | ACAMERA_CONTROL_AE_PRECAPTURE_TRIGGER is START | PRECAPTURE | Start AE precapture metering sequence
* Any state (excluding LOCKED) | ACAMERA_CONTROL_AE_PRECAPTURE_TRIGGER is CANCEL| INACTIVE | Currently active precapture metering sequence is canceled</p>
+ * <p>If the camera device supports AE external flash mode (ON_EXTERNAL_FLASH is included in
+ * ACAMERA_CONTROL_AE_AVAILABLE_MODES), aeState must be FLASH_REQUIRED after the camera device
+ * finishes AE scan and it's too dark without flash.</p>
* <p>For the above table, the camera device may skip reporting any state changes that happen
* without application intervention (i.e. mode switch, trigger, locking). Any state that
* can be skipped in that manner is called a transient state.</p>
- * <p>For example, for above AE modes (AE_MODE_ON_*), in addition to the state transitions
+ * <p>For example, for above AE modes (AE_MODE_ON*), in addition to the state transitions
* listed in above table, it is also legal for the camera device to skip one or more
* transient states between two results. See below table for examples:</p>
* <p>State | Transition Cause | New State | Notes
@@ -1319,6 +1326,7 @@
* CONVERGED | Camera device finished AE scan | FLASH_REQUIRED | Converged but too dark w/o flash after a new scan, transient states are skipped by camera device.
* FLASH_REQUIRED | Camera device finished AE scan | CONVERGED | Converged after a new scan, transient states are skipped by camera device.</p>
*
+ * @see ACAMERA_CONTROL_AE_AVAILABLE_MODES
* @see ACAMERA_CONTROL_AE_LOCK
* @see ACAMERA_CONTROL_AE_MODE
* @see ACAMERA_CONTROL_AE_PRECAPTURE_TRIGGER
@@ -1642,7 +1650,7 @@
* <p>Whether a significant scene change is detected within the currently-set AF
* region(s).</p>
*
- * <p>Type: int32 (acamera_metadata_enum_android_control_af_scene_change_t)</p>
+ * <p>Type: byte (acamera_metadata_enum_android_control_af_scene_change_t)</p>
*
* <p>This tag may appear in:
* <ul>
@@ -1654,11 +1662,9 @@
* significant illumination change, this value will be set to DETECTED for a single capture
* result. Otherwise the value will be NOT_DETECTED. The threshold for detection is similar
* to what would trigger a new passive focus scan to begin in CONTINUOUS autofocus modes.</p>
- * <p>afSceneChange may be DETECTED only if afMode is AF_MODE_CONTINUOUS_VIDEO or
- * AF_MODE_CONTINUOUS_PICTURE. In other AF modes, afSceneChange must be NOT_DETECTED.</p>
* <p>This key will be available if the camera device advertises this key via {@link ACAMERA_REQUEST_AVAILABLE_RESULT_KEYS }.</p>
*/
- ACAMERA_CONTROL_AF_SCENE_CHANGE = // int32 (acamera_metadata_enum_android_control_af_scene_change_t)
+ ACAMERA_CONTROL_AF_SCENE_CHANGE = // byte (acamera_metadata_enum_android_control_af_scene_change_t)
ACAMERA_CONTROL_START + 42,
ACAMERA_CONTROL_END,
@@ -4558,6 +4564,85 @@
*/
ACAMERA_STATISTICS_LENS_SHADING_MAP_MODE = // byte (acamera_metadata_enum_android_statistics_lens_shading_map_mode_t)
ACAMERA_STATISTICS_START + 16,
+ /**
+ * <p>Whether the camera device outputs the OIS data in output
+ * result metadata.</p>
+ *
+ * <p>Type: byte (acamera_metadata_enum_android_statistics_ois_data_mode_t)</p>
+ *
+ * <p>This tag may appear in:
+ * <ul>
+ * <li>ACameraMetadata from ACameraCaptureSession_captureCallback_result callbacks</li>
+ * <li>ACaptureRequest</li>
+ * </ul></p>
+ *
+ * <p>When set to ON,
+ * ACAMERA_STATISTICS_OIS_TIMESTAMPS, android.statistics.oisShiftPixelX,
+ * android.statistics.oisShiftPixelY will provide OIS data in the output result metadata.</p>
+ *
+ * @see ACAMERA_STATISTICS_OIS_TIMESTAMPS
+ */
+ ACAMERA_STATISTICS_OIS_DATA_MODE = // byte (acamera_metadata_enum_android_statistics_ois_data_mode_t)
+ ACAMERA_STATISTICS_START + 17,
+ /**
+ * <p>An array of timestamps of OIS samples, in nanoseconds.</p>
+ *
+ * <p>Type: int64[n]</p>
+ *
+ * <p>This tag may appear in:
+ * <ul>
+ * <li>ACameraMetadata from ACameraCaptureSession_captureCallback_result callbacks</li>
+ * </ul></p>
+ *
+ * <p>The array contains the timestamps of OIS samples. The timestamps are in the same
+ * timebase as and comparable to ACAMERA_SENSOR_TIMESTAMP.</p>
+ *
+ * @see ACAMERA_SENSOR_TIMESTAMP
+ */
+ ACAMERA_STATISTICS_OIS_TIMESTAMPS = // int64[n]
+ ACAMERA_STATISTICS_START + 18,
+ /**
+ * <p>An array of shifts of OIS samples, in x direction.</p>
+ *
+ * <p>Type: float[n]</p>
+ *
+ * <p>This tag may appear in:
+ * <ul>
+ * <li>ACameraMetadata from ACameraCaptureSession_captureCallback_result callbacks</li>
+ * </ul></p>
+ *
+ * <p>The array contains the amount of shifts in x direction, in pixels, based on OIS samples.
+ * A positive value is a shift from left to right in active array coordinate system. For
+ * example, if the optical center is (1000, 500) in active array coordinates, an shift of
+ * (3, 0) puts the new optical center at (1003, 500).</p>
+ * <p>The number of shifts must match the number of timestamps in
+ * ACAMERA_STATISTICS_OIS_TIMESTAMPS.</p>
+ *
+ * @see ACAMERA_STATISTICS_OIS_TIMESTAMPS
+ */
+ ACAMERA_STATISTICS_OIS_X_SHIFTS = // float[n]
+ ACAMERA_STATISTICS_START + 19,
+ /**
+ * <p>An array of shifts of OIS samples, in y direction.</p>
+ *
+ * <p>Type: float[n]</p>
+ *
+ * <p>This tag may appear in:
+ * <ul>
+ * <li>ACameraMetadata from ACameraCaptureSession_captureCallback_result callbacks</li>
+ * </ul></p>
+ *
+ * <p>The array contains the amount of shifts in y direction, in pixels, based on OIS samples.
+ * A positive value is a shift from top to bottom in active array coordinate system. For
+ * example, if the optical center is (1000, 500) in active array coordinates, an shift of
+ * (0, 5) puts the new optical center at (1000, 505).</p>
+ * <p>The number of shifts must match the number of timestamps in
+ * ACAMERA_STATISTICS_OIS_TIMESTAMPS.</p>
+ *
+ * @see ACAMERA_STATISTICS_OIS_TIMESTAMPS
+ */
+ ACAMERA_STATISTICS_OIS_Y_SHIFTS = // float[n]
+ ACAMERA_STATISTICS_START + 20,
ACAMERA_STATISTICS_END,
/**
@@ -4630,6 +4715,24 @@
*/
ACAMERA_STATISTICS_INFO_AVAILABLE_LENS_SHADING_MAP_MODES = // byte[n]
ACAMERA_STATISTICS_INFO_START + 7,
+ /**
+ * <p>List of OIS data output modes for ACAMERA_STATISTICS_OIS_DATA_MODE that
+ * are supported by this camera device.</p>
+ *
+ * @see ACAMERA_STATISTICS_OIS_DATA_MODE
+ *
+ * <p>Type: byte[n]</p>
+ *
+ * <p>This tag may appear in:
+ * <ul>
+ * <li>ACameraMetadata from ACameraManager_getCameraCharacteristics</li>
+ * </ul></p>
+ *
+ * <p>If no OIS data output is available for this camera device, this key will
+ * contain only OFF.</p>
+ */
+ ACAMERA_STATISTICS_INFO_AVAILABLE_OIS_DATA_MODES = // byte[n]
+ ACAMERA_STATISTICS_INFO_START + 8,
ACAMERA_STATISTICS_INFO_END,
/**
@@ -5161,6 +5264,29 @@
ACAMERA_DEPTH_START + 4,
ACAMERA_DEPTH_END,
+ /**
+ * <p>The accuracy of frame timestamp synchronization between physical cameras</p>
+ *
+ * <p>Type: byte (acamera_metadata_enum_android_logical_multi_camera_sensor_sync_type_t)</p>
+ *
+ * <p>This tag may appear in:
+ * <ul>
+ * <li>ACameraMetadata from ACameraManager_getCameraCharacteristics</li>
+ * </ul></p>
+ *
+ * <p>The accuracy of the frame timestamp synchronization determines the physical cameras'
+ * ability to start exposure at the same time. If the sensorSyncType is CALIBRATED,
+ * the physical camera sensors usually run in master-slave mode so that their shutter
+ * time is synchronized. For APPROXIMATE sensorSyncType, the camera sensors usually run in
+ * master-master mode, and there could be offset between their start of exposure.</p>
+ * <p>In both cases, all images generated for a particular capture request still carry the same
+ * timestamps, so that they can be used to look up the matching frame number and
+ * onCaptureStarted callback.</p>
+ */
+ ACAMERA_LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE = // byte (acamera_metadata_enum_android_logical_multi_camera_sensor_sync_type_t)
+ ACAMERA_LOGICAL_MULTI_CAMERA_START + 1,
+ ACAMERA_LOGICAL_MULTI_CAMERA_END,
+
} acamera_metadata_tag_t;
/**
@@ -5374,6 +5500,19 @@
*/
ACAMERA_CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE = 4,
+ /**
+ * <p>An external flash has been turned on.</p>
+ * <p>It informs the camera device that an external flash has been turned on, and that
+ * metering (and continuous focus if active) should be quickly recaculated to account
+ * for the external flash. Otherwise, this mode acts like ON.</p>
+ * <p>When the external flash is turned off, AE mode should be changed to one of the
+ * other available AE modes.</p>
+ * <p>If the camera device supports AE external flash mode, aeState must be
+ * FLASH_REQUIRED after the camera device finishes AE scan and it's too dark without
+ * flash.</p>
+ */
+ ACAMERA_CONTROL_AE_MODE_ON_EXTERNAL_FLASH = 5,
+
} acamera_metadata_enum_android_control_ae_mode_t;
// ACAMERA_CONTROL_AE_PRECAPTURE_TRIGGER
@@ -6878,6 +7017,52 @@
*/
ACAMERA_REQUEST_AVAILABLE_CAPABILITIES_MOTION_TRACKING = 10,
+ /**
+ * <p>The camera device is a logical camera backed by two or more physical cameras that are
+ * also exposed to the application.</p>
+ * <p>This capability requires the camera device to support the following:</p>
+ * <ul>
+ * <li>This camera device must list the following static metadata entries in <a href="https://developer.android.com/reference/android/hardware/camera2/CameraCharacteristics.html">CameraCharacteristics</a>:<ul>
+ * <li>android.logicalMultiCamera.physicalIds</li>
+ * <li>ACAMERA_LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE</li>
+ * </ul>
+ * </li>
+ * <li>The underlying physical cameras' static metadata must list the following entries,
+ * so that the application can correlate pixels from the physical streams:<ul>
+ * <li>ACAMERA_LENS_POSE_REFERENCE</li>
+ * <li>ACAMERA_LENS_POSE_ROTATION</li>
+ * <li>ACAMERA_LENS_POSE_TRANSLATION</li>
+ * <li>ACAMERA_LENS_INTRINSIC_CALIBRATION</li>
+ * <li>ACAMERA_LENS_RADIAL_DISTORTION</li>
+ * </ul>
+ * </li>
+ * <li>The logical camera device must be LIMITED or higher device.</li>
+ * </ul>
+ * <p>Both the logical camera device and its underlying physical devices support the
+ * mandatory stream combinations required for their device levels.</p>
+ * <p>Additionally, for each guaranteed stream combination, the logical camera supports:</p>
+ * <ul>
+ * <li>Replacing one logical {@link AIMAGE_FORMAT_YUV_420_888 YUV_420_888}
+ * or raw stream with two physical streams of the same size and format, each from a
+ * separate physical camera, given that the size and format are supported by both
+ * physical cameras.</li>
+ * <li>Adding two raw streams, each from one physical camera, if the logical camera doesn't
+ * advertise RAW capability, but the underlying physical cameras do. This is usually
+ * the case when the physical cameras have different sensor sizes.</li>
+ * </ul>
+ * <p>Using physical streams in place of a logical stream of the same size and format will
+ * not slow down the frame rate of the capture, as long as the minimum frame duration
+ * of the physical and logical streams are the same.</p>
+ *
+ * @see ACAMERA_LENS_INTRINSIC_CALIBRATION
+ * @see ACAMERA_LENS_POSE_REFERENCE
+ * @see ACAMERA_LENS_POSE_ROTATION
+ * @see ACAMERA_LENS_POSE_TRANSLATION
+ * @see ACAMERA_LENS_RADIAL_DISTORTION
+ * @see ACAMERA_LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE
+ */
+ ACAMERA_REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA = 11,
+
} acamera_metadata_enum_android_request_available_capabilities_t;
@@ -7209,6 +7394,20 @@
} acamera_metadata_enum_android_statistics_lens_shading_map_mode_t;
+// ACAMERA_STATISTICS_OIS_DATA_MODE
+typedef enum acamera_metadata_enum_acamera_statistics_ois_data_mode {
+ /**
+ * <p>Do not include OIS data in the capture result.</p>
+ */
+ ACAMERA_STATISTICS_OIS_DATA_MODE_OFF = 0,
+
+ /**
+ * <p>Include OIS data in the capture result.</p>
+ */
+ ACAMERA_STATISTICS_OIS_DATA_MODE_ON = 1,
+
+} acamera_metadata_enum_android_statistics_ois_data_mode_t;
+
// ACAMERA_TONEMAP_MODE
@@ -7370,6 +7569,37 @@
*/
ACAMERA_INFO_SUPPORTED_HARDWARE_LEVEL_3 = 3,
+ /**
+ * <p>This camera device is backed by an external camera connected to this Android device.</p>
+ * <p>The device has capability identical to a LIMITED level device, with the following
+ * exceptions:</p>
+ * <ul>
+ * <li>The device may not report lens/sensor related information such as<ul>
+ * <li>ACAMERA_LENS_FOCAL_LENGTH</li>
+ * <li>ACAMERA_LENS_INFO_HYPERFOCAL_DISTANCE</li>
+ * <li>ACAMERA_SENSOR_INFO_PHYSICAL_SIZE</li>
+ * <li>ACAMERA_SENSOR_INFO_WHITE_LEVEL</li>
+ * <li>ACAMERA_SENSOR_BLACK_LEVEL_PATTERN</li>
+ * <li>ACAMERA_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT</li>
+ * <li>ACAMERA_SENSOR_ROLLING_SHUTTER_SKEW</li>
+ * </ul>
+ * </li>
+ * <li>The device will report 0 for ACAMERA_SENSOR_ORIENTATION</li>
+ * <li>The device has less guarantee on stable framerate, as the framerate partly depends
+ * on the external camera being used.</li>
+ * </ul>
+ *
+ * @see ACAMERA_LENS_FOCAL_LENGTH
+ * @see ACAMERA_LENS_INFO_HYPERFOCAL_DISTANCE
+ * @see ACAMERA_SENSOR_BLACK_LEVEL_PATTERN
+ * @see ACAMERA_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT
+ * @see ACAMERA_SENSOR_INFO_PHYSICAL_SIZE
+ * @see ACAMERA_SENSOR_INFO_WHITE_LEVEL
+ * @see ACAMERA_SENSOR_ORIENTATION
+ * @see ACAMERA_SENSOR_ROLLING_SHUTTER_SKEW
+ */
+ ACAMERA_INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL = 4,
+
} acamera_metadata_enum_android_info_supported_hardware_level_t;
@@ -7457,6 +7687,25 @@
} acamera_metadata_enum_android_depth_depth_is_exclusive_t;
+// ACAMERA_LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE
+typedef enum acamera_metadata_enum_acamera_logical_multi_camera_sensor_sync_type {
+ /**
+ * <p>A software mechanism is used to synchronize between the physical cameras. As a result,
+ * the timestamp of an image from a physical stream is only an approximation of the
+ * image sensor start-of-exposure time.</p>
+ */
+ ACAMERA_LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE_APPROXIMATE = 0,
+
+ /**
+ * <p>The camera device supports frame timestamp synchronization at the hardware level,
+ * and the timestamp of a physical stream image accurately reflects its
+ * start-of-exposure time.</p>
+ */
+ ACAMERA_LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE_CALIBRATED = 1,
+
+} acamera_metadata_enum_android_logical_multi_camera_sensor_sync_type_t;
+
+
#endif /* __ANDROID_API__ >= 24 */
__END_DECLS
diff --git a/camera/tests/CameraBinderTests.cpp b/camera/tests/CameraBinderTests.cpp
index 8fe9a86..1de7013 100644
--- a/camera/tests/CameraBinderTests.cpp
+++ b/camera/tests/CameraBinderTests.cpp
@@ -198,9 +198,11 @@
virtual binder::Status onResultReceived(const CameraMetadata& metadata,
- const CaptureResultExtras& resultExtras) {
+ const CaptureResultExtras& resultExtras,
+ const std::vector<PhysicalCaptureResultInfo>& physicalResultInfos) {
(void) metadata;
(void) resultExtras;
+ (void) physicalResultInfos;
Mutex::Autolock l(mLock);
mLastStatus = SENT_RESULT;
mStatusesHit.push_back(mLastStatus);
@@ -317,6 +319,9 @@
EXPECT_TRUE(res.isOk()) << res;
EXPECT_EQ(numCameras, static_cast<const int>(statuses.size()));
+ for (const auto &it : statuses) {
+ listener->onStatusChanged(it.status, String16(it.cameraId));
+ }
for (int32_t i = 0; i < numCameras; i++) {
String16 cameraId = String16(String8::format("%d", i));
@@ -421,6 +426,9 @@
serviceListener = new TestCameraServiceListener();
std::vector<hardware::CameraStatus> statuses;
service->addListener(serviceListener, &statuses);
+ for (const auto &it : statuses) {
+ serviceListener->onStatusChanged(it.status, String16(it.cameraId));
+ }
service->getNumberOfCameras(hardware::ICameraService::CAMERA_TYPE_BACKWARD_COMPATIBLE,
&numCameras);
}
@@ -439,8 +447,9 @@
ASSERT_NOT_NULL(service);
EXPECT_TRUE(serviceListener->waitForNumCameras(numCameras));
for (int32_t i = 0; i < numCameras; i++) {
+ String8 cameraId8 = String8::format("%d", i);
// Make sure we're available, or skip device tests otherwise
- String16 cameraId(String8::format("%d",i));
+ String16 cameraId(cameraId8);
int32_t s = serviceListener->getStatus(cameraId);
EXPECT_EQ(hardware::ICameraServiceListener::STATUS_PRESENT, s);
if (s != hardware::ICameraServiceListener::STATUS_PRESENT) {
@@ -488,7 +497,7 @@
EXPECT_TRUE(res.isOk()) << res;
hardware::camera2::CaptureRequest request;
- request.mMetadata = requestTemplate;
+ request.mPhysicalCameraSettings.push_back({cameraId8.string(), requestTemplate});
request.mSurfaceList.add(surface);
request.mIsReprocess = false;
int64_t lastFrameNumber = 0;
@@ -515,7 +524,7 @@
/*out*/&requestTemplate);
EXPECT_TRUE(res.isOk()) << res;
hardware::camera2::CaptureRequest request2;
- request2.mMetadata = requestTemplate;
+ request2.mPhysicalCameraSettings.push_back({cameraId8.string(), requestTemplate});
request2.mSurfaceList.add(surface);
request2.mIsReprocess = false;
callbacks->clearStatus();
@@ -548,10 +557,10 @@
EXPECT_TRUE(res.isOk()) << res;
android::hardware::camera2::CaptureRequest request3;
android::hardware::camera2::CaptureRequest request4;
- request3.mMetadata = requestTemplate;
+ request3.mPhysicalCameraSettings.push_back({cameraId8.string(), requestTemplate});
request3.mSurfaceList.add(surface);
request3.mIsReprocess = false;
- request4.mMetadata = requestTemplate2;
+ request4.mPhysicalCameraSettings.push_back({cameraId8.string(), requestTemplate2});
request4.mSurfaceList.add(surface);
request4.mIsReprocess = false;
std::vector<hardware::camera2::CaptureRequest> requestList;
@@ -585,3 +594,62 @@
}
};
+
+TEST_F(CameraClientBinderTest, CheckBinderCaptureRequest) {
+ sp<CaptureRequest> requestOriginal, requestParceled;
+ sp<IGraphicBufferProducer> gbProducer;
+ sp<IGraphicBufferConsumer> gbConsumer;
+ BufferQueue::createBufferQueue(&gbProducer, &gbConsumer);
+ sp<Surface> surface(new Surface(gbProducer, /*controlledByApp*/false));
+ Vector<sp<Surface>> surfaceList;
+ surfaceList.push_back(surface);
+ std::string physicalDeviceId1 = "0";
+ std::string physicalDeviceId2 = "1";
+ CameraMetadata physicalDeviceSettings1, physicalDeviceSettings2;
+ uint8_t intent1 = ANDROID_CONTROL_CAPTURE_INTENT_PREVIEW;
+ uint8_t intent2 = ANDROID_CONTROL_CAPTURE_INTENT_VIDEO_RECORD;
+ EXPECT_EQ(OK, physicalDeviceSettings1.update(ANDROID_CONTROL_CAPTURE_INTENT, &intent1, 1));
+ EXPECT_EQ(OK, physicalDeviceSettings2.update(ANDROID_CONTROL_CAPTURE_INTENT, &intent2, 1));
+
+ requestParceled = new CaptureRequest();
+ Parcel p;
+ EXPECT_TRUE(requestParceled->readFromParcel(&p) != OK);
+ p.writeInt32(0);
+ p.setDataPosition(0);
+ EXPECT_TRUE(requestParceled->readFromParcel(&p) != OK);
+ p.freeData();
+ p.writeInt32(-1);
+ p.setDataPosition(0);
+ EXPECT_TRUE(requestParceled->readFromParcel(&p) != OK);
+ p.freeData();
+ p.writeInt32(1);
+ p.setDataPosition(0);
+ EXPECT_TRUE(requestParceled->readFromParcel(&p) != OK);
+
+ requestOriginal = new CaptureRequest();
+ requestOriginal->mPhysicalCameraSettings.push_back({physicalDeviceId1,
+ physicalDeviceSettings1});
+ requestOriginal->mPhysicalCameraSettings.push_back({physicalDeviceId2,
+ physicalDeviceSettings2});
+ requestOriginal->mSurfaceList.push_back(surface);
+ requestOriginal->mIsReprocess = false;
+ requestOriginal->mSurfaceConverted = false;
+
+ p.freeData();
+ EXPECT_TRUE(requestOriginal->writeToParcel(&p) == OK);
+ p.setDataPosition(0);
+ EXPECT_TRUE(requestParceled->readFromParcel(&p) == OK);
+ EXPECT_EQ(requestParceled->mIsReprocess, false);
+ EXPECT_FALSE(requestParceled->mSurfaceList.empty());
+ EXPECT_EQ(2u, requestParceled->mPhysicalCameraSettings.size());
+ auto it = requestParceled->mPhysicalCameraSettings.begin();
+ EXPECT_EQ(physicalDeviceId1, it->id);
+ EXPECT_TRUE(it->settings.exists(ANDROID_CONTROL_CAPTURE_INTENT));
+ auto entry = it->settings.find(ANDROID_CONTROL_CAPTURE_INTENT);
+ EXPECT_EQ(entry.data.u8[0], intent1);
+ it++;
+ EXPECT_EQ(physicalDeviceId2, it->id);
+ EXPECT_TRUE(it->settings.exists(ANDROID_CONTROL_CAPTURE_INTENT));
+ entry = it->settings.find(ANDROID_CONTROL_CAPTURE_INTENT);
+ EXPECT_EQ(entry.data.u8[0], intent2);
+};
diff --git a/camera/tests/VendorTagDescriptorTests.cpp b/camera/tests/VendorTagDescriptorTests.cpp
index 75cfb73..0ee358d 100644
--- a/camera/tests/VendorTagDescriptorTests.cpp
+++ b/camera/tests/VendorTagDescriptorTests.cpp
@@ -142,6 +142,7 @@
EXPECT_EQ(OK, vDescOriginal->writeToParcel(&p));
p.setDataPosition(0);
+ vDescParceled = new VendorTagDescriptor();
ASSERT_EQ(OK, vDescParceled->readFromParcel(&p));
// Ensure consistent tag count
diff --git a/cmds/screenrecord/screenrecord.cpp b/cmds/screenrecord/screenrecord.cpp
index f9e4639..46bd8f0 100644
--- a/cmds/screenrecord/screenrecord.cpp
+++ b/cmds/screenrecord/screenrecord.cpp
@@ -73,6 +73,7 @@
static enum {
FORMAT_MP4, FORMAT_H264, FORMAT_FRAMES, FORMAT_RAW_FRAMES
} gOutputFormat = FORMAT_MP4; // data format for output
+static AString gCodecName = ""; // codec name override
static bool gSizeSpecified = false; // was size explicitly requested?
static bool gWantInfoScreen = false; // do we want initial info screen?
static bool gWantFrameTime = false; // do we want times on each frame?
@@ -154,6 +155,7 @@
if (gVerbose) {
printf("Configuring recorder for %dx%d %s at %.2fMbps\n",
gVideoWidth, gVideoHeight, kMimeTypeAvc, gBitRate / 1000000.0);
+ fflush(stdout);
}
sp<AMessage> format = new AMessage;
@@ -169,11 +171,21 @@
looper->setName("screenrecord_looper");
looper->start();
ALOGV("Creating codec");
- sp<MediaCodec> codec = MediaCodec::CreateByType(looper, kMimeTypeAvc, true);
- if (codec == NULL) {
- fprintf(stderr, "ERROR: unable to create %s codec instance\n",
- kMimeTypeAvc);
- return UNKNOWN_ERROR;
+ sp<MediaCodec> codec;
+ if (gCodecName.empty()) {
+ codec = MediaCodec::CreateByType(looper, kMimeTypeAvc, true);
+ if (codec == NULL) {
+ fprintf(stderr, "ERROR: unable to create %s codec instance\n",
+ kMimeTypeAvc);
+ return UNKNOWN_ERROR;
+ }
+ } else {
+ codec = MediaCodec::CreateByComponentName(looper, gCodecName);
+ if (codec == NULL) {
+ fprintf(stderr, "ERROR: unable to create %s codec instance\n",
+ gCodecName.c_str());
+ return UNKNOWN_ERROR;
+ }
}
err = codec->configure(format, NULL, NULL,
@@ -275,9 +287,11 @@
if (gRotate) {
printf("Rotated content area is %ux%u at offset x=%d y=%d\n",
outHeight, outWidth, offY, offX);
+ fflush(stdout);
} else {
printf("Content area is %ux%u at offset x=%d y=%d\n",
outWidth, outHeight, offX, offY);
+ fflush(stdout);
}
}
@@ -346,6 +360,7 @@
if (systemTime(CLOCK_MONOTONIC) > endWhenNsec) {
if (gVerbose) {
printf("Time limit reached\n");
+ fflush(stdout);
}
break;
}
@@ -483,6 +498,7 @@
printf("Encoder stopping; recorded %u frames in %" PRId64 " seconds\n",
debugNumFrames, nanoseconds_to_seconds(
systemTime(CLOCK_MONOTONIC) - startWhenNsec));
+ fflush(stdout);
}
return NO_ERROR;
}
@@ -556,6 +572,7 @@
printf("Main display is %dx%d @%.2ffps (orientation=%u)\n",
mainDpyInfo.w, mainDpyInfo.h, mainDpyInfo.fps,
mainDpyInfo.orientation);
+ fflush(stdout);
}
bool rotated = isDeviceRotated(mainDpyInfo.orientation);
@@ -623,6 +640,7 @@
}
if (gVerbose) {
printf("Bugreport overlay created\n");
+ fflush(stdout);
}
} else {
// Use the encoder's input surface as the virtual display surface.
@@ -715,6 +733,7 @@
if (gVerbose) {
printf("Stopping encoder and muxer\n");
+ fflush(stdout);
}
}
@@ -761,6 +780,7 @@
printf(" %s", argv[i]);
}
putchar('\n');
+ fflush(stdout);
}
pid_t pid = fork();
@@ -898,6 +918,7 @@
{ "show-frame-time", no_argument, NULL, 'f' },
{ "rotate", no_argument, NULL, 'r' },
{ "output-format", required_argument, NULL, 'o' },
+ { "codec-name", required_argument, NULL, 'N' },
{ "monotonic-time", no_argument, NULL, 'm' },
{ NULL, 0, NULL, 0 }
};
@@ -978,6 +999,9 @@
return 2;
}
break;
+ case 'N':
+ gCodecName = optarg;
+ break;
case 'm':
gMonotonicTime = true;
break;
diff --git a/cmds/stagefright/audioloop.cpp b/cmds/stagefright/audioloop.cpp
index 67017eb..fc24646 100644
--- a/cmds/stagefright/audioloop.cpp
+++ b/cmds/stagefright/audioloop.cpp
@@ -14,6 +14,10 @@
* limitations under the License.
*/
+#define LOG_NDEBUG 0
+#define LOG_TAG "audioloop"
+#include <utils/Log.h>
+
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
@@ -29,7 +33,6 @@
#include <media/stagefright/AudioSource.h>
#include <media/stagefright/MediaCodecSource.h>
#include <media/stagefright/MediaDefs.h>
-#include <media/stagefright/MetaData.h>
#include <media/stagefright/SimpleDecodingSource.h>
#include "SineSource.h"
@@ -37,11 +40,13 @@
static void usage(const char* name)
{
- fprintf(stderr, "Usage: %s [-d du.ration] [-m] [-w] [<output-file>]\n", name);
+ fprintf(stderr, "Usage: %s [-d du.ration] [-m] [-w] [-N name] [<output-file>]\n", name);
fprintf(stderr, "Encodes either a sine wave or microphone input to AMR format\n");
fprintf(stderr, " -d duration in seconds, default 5 seconds\n");
fprintf(stderr, " -m use microphone for input, default sine source\n");
fprintf(stderr, " -w use AMR wideband (default narrowband)\n");
+ fprintf(stderr, " -N name of the encoder; must be set with -M\n");
+ fprintf(stderr, " -M media type of the encoder; must be set with -N\n");
fprintf(stderr, " <output-file> output file for AMR encoding,"
" if unspecified, decode to speaker.\n");
}
@@ -54,8 +59,10 @@
bool outputWBAMR = false;
bool playToSpeaker = true;
const char* fileOut = NULL;
+ AString name;
+ AString mediaType;
int ch;
- while ((ch = getopt(argc, argv, "d:mw")) != -1) {
+ while ((ch = getopt(argc, argv, "d:mwN:M:")) != -1) {
switch (ch) {
case 'd':
duration = atoi(optarg);
@@ -66,6 +73,12 @@
case 'w':
outputWBAMR = true;
break;
+ case 'N':
+ name.setTo(optarg);
+ break;
+ case 'M':
+ mediaType.setTo(optarg);
+ break;
default:
usage(argv[0]);
return -1;
@@ -76,8 +89,18 @@
if (argc == 1) {
fileOut = argv[0];
}
- const int32_t kSampleRate = outputWBAMR ? 16000 : 8000;
- const int32_t kBitRate = outputWBAMR ? 16000 : 8000;
+ if ((name.empty() && !mediaType.empty()) || (!name.empty() && mediaType.empty())) {
+ fprintf(stderr, "-N and -M must be set together\n");
+ usage(argv[0]);
+ return -1;
+ }
+ if (!name.empty() && fileOut != NULL) {
+ fprintf(stderr, "-N and -M cannot be used with <output file>\n");
+ usage(argv[0]);
+ return -1;
+ }
+ int32_t sampleRate = !name.empty() ? 44100 : outputWBAMR ? 16000 : 8000;
+ int32_t bitRate = sampleRate;
android::ProcessState::self()->startThreadPool();
sp<MediaSource> source;
@@ -87,22 +110,27 @@
source = new AudioSource(
AUDIO_SOURCE_MIC,
String16(),
- kSampleRate,
+ sampleRate,
channels);
} else {
// use a sine source at 500 hz.
- source = new SineSource(kSampleRate, channels);
+ source = new SineSource(sampleRate, channels);
}
sp<AMessage> meta = new AMessage;
- meta->setString(
- "mime",
- outputWBAMR ? MEDIA_MIMETYPE_AUDIO_AMR_WB
- : MEDIA_MIMETYPE_AUDIO_AMR_NB);
+ if (name.empty()) {
+ meta->setString(
+ "mime",
+ outputWBAMR ? MEDIA_MIMETYPE_AUDIO_AMR_WB
+ : MEDIA_MIMETYPE_AUDIO_AMR_NB);
+ } else {
+ meta->setString("mime", mediaType);
+ meta->setString("testing-name", name);
+ }
meta->setInt32("channel-count", channels);
- meta->setInt32("sample-rate", kSampleRate);
- meta->setInt32("bitrate", kBitRate);
+ meta->setInt32("sample-rate", sampleRate);
+ meta->setInt32("bitrate", bitRate);
int32_t maxInputSize;
if (source->getFormat()->findInt32(kKeyMaxInputSize, &maxInputSize)) {
meta->setInt32("max-input-size", maxInputSize);
@@ -131,13 +159,14 @@
sp<MediaSource> decoder = SimpleDecodingSource::Create(encoder);
if (playToSpeaker) {
- AudioPlayer *player = new AudioPlayer(NULL);
- player->setSource(decoder);
- player->start();
+ AudioPlayer player(NULL);
+ player.setSource(decoder);
+ player.start();
sleep(duration);
+ALOGI("Line: %d", __LINE__);
decoder.clear(); // must clear |decoder| otherwise delete player will hang.
- delete player; // there is no player->stop()...
+ALOGI("Line: %d", __LINE__);
} else {
CHECK_EQ(decoder->start(), (status_t)OK);
MediaBuffer* buffer;
@@ -151,6 +180,7 @@
}
CHECK_EQ(decoder->stop(), (status_t)OK);
}
+ALOGI("Line: %d", __LINE__);
}
return 0;
diff --git a/drm/libmediadrm/Android.bp b/drm/libmediadrm/Android.bp
index 0c14201..ea239c5 100644
--- a/drm/libmediadrm/Android.bp
+++ b/drm/libmediadrm/Android.bp
@@ -2,7 +2,9 @@
// libmediadrm
//
-cc_library_shared {
+// TODO: change it back to cc_library_shared when MediaPlayer2 switches to
+// using NdkMediaDrm, instead of MediaDrm.java.
+cc_library {
name: "libmediadrm",
@@ -16,6 +18,7 @@
"PluginMetricsReporting.cpp",
"SharedLibrary.cpp",
"DrmHal.cpp",
+ "DrmMetrics.cpp",
"CryptoHal.cpp",
"protos/plugin_metrics.proto",
],
@@ -34,6 +37,7 @@
"libstagefright_foundation",
"libutils",
"android.hardware.drm@1.0",
+ "android.hardware.drm@1.1",
"libhidlallocatorutils",
"libhidlbase",
"libhidltransport",
diff --git a/drm/libmediadrm/DrmHal.cpp b/drm/libmediadrm/DrmHal.cpp
index bc37557..7ab0bcd 100644
--- a/drm/libmediadrm/DrmHal.cpp
+++ b/drm/libmediadrm/DrmHal.cpp
@@ -30,6 +30,7 @@
#include <media/DrmHal.h>
#include <media/DrmSessionClientInterface.h>
#include <media/DrmSessionManager.h>
+#include <media/EventMetric.h>
#include <media/PluginMetricsReporting.h>
#include <media/drm/DrmAPI.h>
#include <media/stagefright/foundation/ADebug.h>
@@ -37,17 +38,14 @@
#include <media/stagefright/foundation/hexdump.h>
#include <media/stagefright/MediaErrors.h>
-using ::android::hardware::drm::V1_0::EventType;
-using ::android::hardware::drm::V1_0::IDrmFactory;
-using ::android::hardware::drm::V1_0::IDrmPlugin;
-using ::android::hardware::drm::V1_0::KeyedVector;
-using ::android::hardware::drm::V1_0::KeyRequestType;
-using ::android::hardware::drm::V1_0::KeyStatus;
-using ::android::hardware::drm::V1_0::KeyStatusType;
-using ::android::hardware::drm::V1_0::KeyType;
-using ::android::hardware::drm::V1_0::KeyValue;
-using ::android::hardware::drm::V1_0::SecureStop;
-using ::android::hardware::drm::V1_0::Status;
+using drm::V1_0::KeyedVector;
+using drm::V1_0::KeyStatusType;
+using drm::V1_0::KeyType;
+using drm::V1_0::KeyValue;
+using drm::V1_1::HdcpLevel;;
+using drm::V1_0::SecureStop;
+using drm::V1_1::SecurityLevel;
+using drm::V1_0::Status;
using ::android::hardware::hidl_array;
using ::android::hardware::hidl_string;
using ::android::hardware::hidl_vec;
@@ -56,8 +54,18 @@
using ::android::hidl::manager::V1_0::IServiceManager;
using ::android::sp;
+namespace {
+
+// This constant corresponds to the PROPERTY_DEVICE_UNIQUE_ID constant
+// in the MediaDrm API.
+constexpr char kPropertyDeviceUniqueId[] = "deviceUniqueId";
+
+}
+
namespace android {
+#define INIT_CHECK() {if (mInitCheck != OK) return mInitCheck;}
+
static inline int getCallingPid() {
return IPCThreadState::self()->getCallingPid();
}
@@ -89,6 +97,42 @@
return hidl_string(string.string());
}
+static DrmPlugin::SecurityLevel toSecurityLevel(SecurityLevel level) {
+ switch(level) {
+ case SecurityLevel::SW_SECURE_CRYPTO:
+ return DrmPlugin::kSecurityLevelSwSecureCrypto;
+ case SecurityLevel::SW_SECURE_DECODE:
+ return DrmPlugin::kSecurityLevelSwSecureDecode;
+ case SecurityLevel::HW_SECURE_CRYPTO:
+ return DrmPlugin::kSecurityLevelHwSecureCrypto;
+ case SecurityLevel::HW_SECURE_DECODE:
+ return DrmPlugin::kSecurityLevelHwSecureDecode;
+ case SecurityLevel::HW_SECURE_ALL:
+ return DrmPlugin::kSecurityLevelHwSecureAll;
+ default:
+ return DrmPlugin::kSecurityLevelUnknown;
+ }
+}
+
+static DrmPlugin::HdcpLevel toHdcpLevel(HdcpLevel level) {
+ switch(level) {
+ case HdcpLevel::HDCP_NONE:
+ return DrmPlugin::kHdcpNone;
+ case HdcpLevel::HDCP_V1:
+ return DrmPlugin::kHdcpV1;
+ case HdcpLevel::HDCP_V2:
+ return DrmPlugin::kHdcpV2;
+ case HdcpLevel::HDCP_V2_1:
+ return DrmPlugin::kHdcpV2_1;
+ case HdcpLevel::HDCP_V2_2:
+ return DrmPlugin::kHdcpV2_2;
+ case HdcpLevel::HDCP_NO_OUTPUT:
+ return DrmPlugin::kHdcpNoOutput;
+ default:
+ return DrmPlugin::kHdcpLevelUnknown;
+ }
+}
+
static ::KeyedVector toHidlKeyedVector(const KeyedVector<String8, String8>&
keyedVector) {
@@ -197,9 +241,9 @@
void DrmHal::closeOpenSessions() {
if (mPlugin != NULL) {
- for (size_t i = 0; i < mOpenSessions.size(); i++) {
- mPlugin->closeSession(toHidlVec(mOpenSessions[i]));
- DrmSessionManager::Instance()->removeSession(mOpenSessions[i]);
+ auto openSessions = mOpenSessions;
+ for (size_t i = 0; i < openSessions.size(); i++) {
+ closeSession(openSessions[i]);
}
}
mOpenSessions.clear();
@@ -284,6 +328,7 @@
Return<void> DrmHal::sendEvent(EventType hEventType,
const hidl_vec<uint8_t>& sessionId, const hidl_vec<uint8_t>& data) {
+ mMetrics.mEventCounter.Increment(hEventType);
mEventLock.lock();
sp<IDrmClient> listener = mListener;
@@ -374,12 +419,21 @@
break;
}
obj.writeInt32(type);
+ mMetrics.mKeyStatusChangeCounter.Increment(keyStatus.type);
}
obj.writeInt32(hasNewUsableKey);
Mutex::Autolock lock(mNotifyLock);
listener->notify(DrmPlugin::kDrmPluginEventKeysChange, 0, &obj);
+ } else {
+ // There's no listener. But we still want to count the key change
+ // events.
+ size_t nKeys = keyStatusList.size();
+ for (size_t i = 0; i < nKeys; i++) {
+ mMetrics.mKeyStatusChangeCounter.Increment(keyStatusList[i].type);
+ }
}
+
return Void();
}
@@ -407,6 +461,7 @@
for (size_t i = 0; i < mFactories.size(); i++) {
if (mFactories[i]->isCryptoSchemeSupported(uuid)) {
mPlugin = makeDrmPlugin(mFactories[i], uuid, appPackageName);
+ mPluginV1_1 = drm::V1_1::IDrmPlugin::castFrom(mPlugin);
}
}
@@ -425,9 +480,7 @@
status_t DrmHal::destroyPlugin() {
Mutex::Autolock autoLock(mLock);
- if (mInitCheck != OK) {
- return mInitCheck;
- }
+ INIT_CHECK();
closeOpenSessions();
reportMetrics();
@@ -445,10 +498,7 @@
status_t DrmHal::openSession(Vector<uint8_t> &sessionId) {
Mutex::Autolock autoLock(mLock);
-
- if (mInitCheck != OK) {
- return mInitCheck;
- }
+ INIT_CHECK();
status_t err = UNKNOWN_ERROR;
@@ -486,15 +536,14 @@
mDrmSessionClient, sessionId);
mOpenSessions.push(sessionId);
}
+
+ mMetrics.mOpenSessionCounter.Increment(err);
return err;
}
status_t DrmHal::closeSession(Vector<uint8_t> const &sessionId) {
Mutex::Autolock autoLock(mLock);
-
- if (mInitCheck != OK) {
- return mInitCheck;
- }
+ INIT_CHECK();
Return<Status> status = mPlugin->closeSession(toHidlVec(sessionId));
if (status.isOk()) {
@@ -508,8 +557,11 @@
}
}
reportMetrics();
- return toStatusT(status);
+ status_t response = toStatusT(status);
+ mMetrics.mCloseSessionCounter.Increment(response);
+ return response;
}
+ mMetrics.mCloseSessionCounter.Increment(DEAD_OBJECT);
return DEAD_OBJECT;
}
@@ -519,10 +571,8 @@
String8> const &optionalParameters, Vector<uint8_t> &request,
String8 &defaultUrl, DrmPlugin::KeyRequestType *keyRequestType) {
Mutex::Autolock autoLock(mLock);
-
- if (mInitCheck != OK) {
- return mInitCheck;
- }
+ INIT_CHECK();
+ EventTimer<status_t> keyRequestTimer(&mMetrics.mGetKeyRequestTiming);
DrmSessionManager::Instance()->useSession(sessionId);
@@ -534,6 +584,7 @@
} else if (keyType == DrmPlugin::kKeyType_Release) {
hKeyType = KeyType::RELEASE;
} else {
+ keyRequestTimer.SetAttribute(BAD_VALUE);
return BAD_VALUE;
}
@@ -541,23 +592,63 @@
status_t err = UNKNOWN_ERROR;
+ if (mPluginV1_1 != NULL) {
+ Return<void> hResult =
+ mPluginV1_1->getKeyRequest_1_1(
+ toHidlVec(sessionId), toHidlVec(initData),
+ toHidlString(mimeType), hKeyType, hOptionalParameters,
+ [&](Status status, const hidl_vec<uint8_t>& hRequest,
+ drm::V1_1::KeyRequestType hKeyRequestType,
+ const hidl_string& hDefaultUrl) {
+
+ if (status == Status::OK) {
+ request = toVector(hRequest);
+ defaultUrl = toString8(hDefaultUrl);
+
+ switch (hKeyRequestType) {
+ case drm::V1_1::KeyRequestType::INITIAL:
+ *keyRequestType = DrmPlugin::kKeyRequestType_Initial;
+ break;
+ case drm::V1_1::KeyRequestType::RENEWAL:
+ *keyRequestType = DrmPlugin::kKeyRequestType_Renewal;
+ break;
+ case drm::V1_1::KeyRequestType::RELEASE:
+ *keyRequestType = DrmPlugin::kKeyRequestType_Release;
+ break;
+ case drm::V1_1::KeyRequestType::NONE:
+ *keyRequestType = DrmPlugin::kKeyRequestType_None;
+ break;
+ case drm::V1_1::KeyRequestType::UPDATE:
+ *keyRequestType = DrmPlugin::kKeyRequestType_Update;
+ break;
+ default:
+ *keyRequestType = DrmPlugin::kKeyRequestType_Unknown;
+ break;
+ }
+ err = toStatusT(status);
+ }
+ });
+ return hResult.isOk() ? err : DEAD_OBJECT;
+ }
+
Return<void> hResult = mPlugin->getKeyRequest(toHidlVec(sessionId),
toHidlVec(initData), toHidlString(mimeType), hKeyType, hOptionalParameters,
[&](Status status, const hidl_vec<uint8_t>& hRequest,
- KeyRequestType hKeyRequestType, const hidl_string& hDefaultUrl) {
+ drm::V1_0::KeyRequestType hKeyRequestType,
+ const hidl_string& hDefaultUrl) {
if (status == Status::OK) {
request = toVector(hRequest);
defaultUrl = toString8(hDefaultUrl);
switch (hKeyRequestType) {
- case KeyRequestType::INITIAL:
+ case drm::V1_0::KeyRequestType::INITIAL:
*keyRequestType = DrmPlugin::kKeyRequestType_Initial;
break;
- case KeyRequestType::RENEWAL:
+ case drm::V1_0::KeyRequestType::RENEWAL:
*keyRequestType = DrmPlugin::kKeyRequestType_Renewal;
break;
- case KeyRequestType::RELEASE:
+ case drm::V1_0::KeyRequestType::RELEASE:
*keyRequestType = DrmPlugin::kKeyRequestType_Release;
break;
default:
@@ -568,16 +659,17 @@
}
});
- return hResult.isOk() ? err : DEAD_OBJECT;
+ err = hResult.isOk() ? err : DEAD_OBJECT;
+ keyRequestTimer.SetAttribute(err);
+ return err;
}
status_t DrmHal::provideKeyResponse(Vector<uint8_t> const &sessionId,
Vector<uint8_t> const &response, Vector<uint8_t> &keySetId) {
Mutex::Autolock autoLock(mLock);
+ EventTimer<status_t> keyResponseTimer(&mMetrics.mProvideKeyResponseTiming);
- if (mInitCheck != OK) {
- return mInitCheck;
- }
+ INIT_CHECK();
DrmSessionManager::Instance()->useSession(sessionId);
@@ -592,16 +684,14 @@
err = toStatusT(status);
}
);
-
- return hResult.isOk() ? err : DEAD_OBJECT;
+ err = hResult.isOk() ? err : DEAD_OBJECT;
+ keyResponseTimer.SetAttribute(err);
+ return err;
}
status_t DrmHal::removeKeys(Vector<uint8_t> const &keySetId) {
Mutex::Autolock autoLock(mLock);
-
- if (mInitCheck != OK) {
- return mInitCheck;
- }
+ INIT_CHECK();
return toStatusT(mPlugin->removeKeys(toHidlVec(keySetId)));
}
@@ -609,10 +699,7 @@
status_t DrmHal::restoreKeys(Vector<uint8_t> const &sessionId,
Vector<uint8_t> const &keySetId) {
Mutex::Autolock autoLock(mLock);
-
- if (mInitCheck != OK) {
- return mInitCheck;
- }
+ INIT_CHECK();
DrmSessionManager::Instance()->useSession(sessionId);
@@ -623,10 +710,7 @@
status_t DrmHal::queryKeyStatus(Vector<uint8_t> const &sessionId,
KeyedVector<String8, String8> &infoMap) const {
Mutex::Autolock autoLock(mLock);
-
- if (mInitCheck != OK) {
- return mInitCheck;
- }
+ INIT_CHECK();
DrmSessionManager::Instance()->useSession(sessionId);
@@ -650,10 +734,7 @@
String8 const &certAuthority, Vector<uint8_t> &request,
String8 &defaultUrl) {
Mutex::Autolock autoLock(mLock);
-
- if (mInitCheck != OK) {
- return mInitCheck;
- }
+ INIT_CHECK();
status_t err = UNKNOWN_ERROR;
@@ -669,16 +750,15 @@
}
);
- return hResult.isOk() ? err : DEAD_OBJECT;
+ err = hResult.isOk() ? err : DEAD_OBJECT;
+ mMetrics.mGetProvisionRequestCounter.Increment(err);
+ return err;
}
status_t DrmHal::provideProvisionResponse(Vector<uint8_t> const &response,
Vector<uint8_t> &certificate, Vector<uint8_t> &wrappedKey) {
Mutex::Autolock autoLock(mLock);
-
- if (mInitCheck != OK) {
- return mInitCheck;
- }
+ INIT_CHECK();
status_t err = UNKNOWN_ERROR;
@@ -693,15 +773,14 @@
}
);
- return hResult.isOk() ? err : DEAD_OBJECT;
+ err = hResult.isOk() ? err : DEAD_OBJECT;
+ mMetrics.mProvideProvisionResponseCounter.Increment(err);
+ return err;
}
status_t DrmHal::getSecureStops(List<Vector<uint8_t>> &secureStops) {
Mutex::Autolock autoLock(mLock);
-
- if (mInitCheck != OK) {
- return mInitCheck;
- }
+ INIT_CHECK();
status_t err = UNKNOWN_ERROR;
@@ -720,10 +799,7 @@
status_t DrmHal::getSecureStop(Vector<uint8_t> const &ssid, Vector<uint8_t> &secureStop) {
Mutex::Autolock autoLock(mLock);
-
- if (mInitCheck != OK) {
- return mInitCheck;
- }
+ INIT_CHECK();
status_t err = UNKNOWN_ERROR;
@@ -741,24 +817,141 @@
status_t DrmHal::releaseSecureStops(Vector<uint8_t> const &ssRelease) {
Mutex::Autolock autoLock(mLock);
-
- if (mInitCheck != OK) {
- return mInitCheck;
- }
+ INIT_CHECK();
return toStatusT(mPlugin->releaseSecureStop(toHidlVec(ssRelease)));
}
status_t DrmHal::releaseAllSecureStops() {
Mutex::Autolock autoLock(mLock);
-
- if (mInitCheck != OK) {
- return mInitCheck;
- }
+ INIT_CHECK();
return toStatusT(mPlugin->releaseAllSecureStops());
}
+status_t DrmHal::getHdcpLevels(DrmPlugin::HdcpLevel *connected,
+ DrmPlugin::HdcpLevel *max) const {
+ Mutex::Autolock autoLock(mLock);
+ INIT_CHECK();
+
+ if (connected == NULL || max == NULL) {
+ return BAD_VALUE;
+ }
+ status_t err = UNKNOWN_ERROR;
+
+ if (mPluginV1_1 == NULL) {
+ return ERROR_DRM_CANNOT_HANDLE;
+ }
+
+ *connected = DrmPlugin::kHdcpLevelUnknown;
+ *max = DrmPlugin::kHdcpLevelUnknown;
+
+ Return<void> hResult = mPluginV1_1->getHdcpLevels(
+ [&](Status status, const HdcpLevel& hConnected, const HdcpLevel& hMax) {
+ if (status == Status::OK) {
+ *connected = toHdcpLevel(hConnected);
+ *max = toHdcpLevel(hMax);
+ }
+ err = toStatusT(status);
+ }
+ );
+
+ return hResult.isOk() ? err : DEAD_OBJECT;
+}
+
+status_t DrmHal::getNumberOfSessions(uint32_t *open, uint32_t *max) const {
+ Mutex::Autolock autoLock(mLock);
+ INIT_CHECK();
+
+ if (open == NULL || max == NULL) {
+ return BAD_VALUE;
+ }
+ status_t err = UNKNOWN_ERROR;
+
+ *open = 0;
+ *max = 0;
+
+ if (mPluginV1_1 == NULL) {
+ return ERROR_DRM_CANNOT_HANDLE;
+ }
+
+ Return<void> hResult = mPluginV1_1->getNumberOfSessions(
+ [&](Status status, uint32_t hOpen, uint32_t hMax) {
+ if (status == Status::OK) {
+ *open = hOpen;
+ *max = hMax;
+ }
+ err = toStatusT(status);
+ }
+ );
+
+ return hResult.isOk() ? err : DEAD_OBJECT;
+}
+
+status_t DrmHal::getSecurityLevel(Vector<uint8_t> const &sessionId,
+ DrmPlugin::SecurityLevel *level) const {
+ Mutex::Autolock autoLock(mLock);
+ INIT_CHECK();
+
+ if (level == NULL) {
+ return BAD_VALUE;
+ }
+ status_t err = UNKNOWN_ERROR;
+
+ if (mPluginV1_1 == NULL) {
+ return ERROR_DRM_CANNOT_HANDLE;
+ }
+
+ *level = DrmPlugin::kSecurityLevelUnknown;
+
+ Return<void> hResult = mPluginV1_1->getSecurityLevel(toHidlVec(sessionId),
+ [&](Status status, SecurityLevel hLevel) {
+ if (status == Status::OK) {
+ *level = toSecurityLevel(hLevel);
+ }
+ err = toStatusT(status);
+ }
+ );
+
+ return hResult.isOk() ? err : DEAD_OBJECT;
+}
+
+status_t DrmHal::setSecurityLevel(Vector<uint8_t> const &sessionId,
+ const DrmPlugin::SecurityLevel& level) {
+ Mutex::Autolock autoLock(mLock);
+ INIT_CHECK();
+
+ if (mPluginV1_1 == NULL) {
+ return ERROR_DRM_CANNOT_HANDLE;
+ }
+
+ SecurityLevel hSecurityLevel;
+
+ switch(level) {
+ case DrmPlugin::kSecurityLevelSwSecureCrypto:
+ hSecurityLevel = SecurityLevel::SW_SECURE_CRYPTO;
+ break;
+ case DrmPlugin::kSecurityLevelSwSecureDecode:
+ hSecurityLevel = SecurityLevel::SW_SECURE_DECODE;
+ break;
+ case DrmPlugin::kSecurityLevelHwSecureCrypto:
+ hSecurityLevel = SecurityLevel::HW_SECURE_CRYPTO;
+ break;
+ case DrmPlugin::kSecurityLevelHwSecureDecode:
+ hSecurityLevel = SecurityLevel::HW_SECURE_DECODE;
+ break;
+ case DrmPlugin::kSecurityLevelHwSecureAll:
+ hSecurityLevel = SecurityLevel::HW_SECURE_ALL;
+ break;
+ default:
+ return ERROR_DRM_CANNOT_HANDLE;
+ }
+
+ Status status = mPluginV1_1->setSecurityLevel(toHidlVec(sessionId),
+ hSecurityLevel);
+ return toStatusT(status);
+}
+
status_t DrmHal::getPropertyString(String8 const &name, String8 &value ) const {
Mutex::Autolock autoLock(mLock);
return getPropertyStringInternal(name, value);
@@ -767,10 +960,7 @@
status_t DrmHal::getPropertyStringInternal(String8 const &name, String8 &value) const {
// This function is internal to the class and should only be called while
// mLock is already held.
-
- if (mInitCheck != OK) {
- return mInitCheck;
- }
+ INIT_CHECK();
status_t err = UNKNOWN_ERROR;
@@ -794,10 +984,7 @@
status_t DrmHal::getPropertyByteArrayInternal(String8 const &name, Vector<uint8_t> &value ) const {
// This function is internal to the class and should only be called while
// mLock is already held.
-
- if (mInitCheck != OK) {
- return mInitCheck;
- }
+ INIT_CHECK();
status_t err = UNKNOWN_ERROR;
@@ -810,17 +997,18 @@
}
);
- return hResult.isOk() ? err : DEAD_OBJECT;
+ err = hResult.isOk() ? err : DEAD_OBJECT;
+ if (name == kPropertyDeviceUniqueId) {
+ mMetrics.mGetDeviceUniqueIdCounter.Increment(err);
+ }
+ return err;
}
status_t DrmHal::setPropertyString(String8 const &name, String8 const &value ) const {
Mutex::Autolock autoLock(mLock);
+ INIT_CHECK();
- if (mInitCheck != OK) {
- return mInitCheck;
- }
-
- Status status = mPlugin->setPropertyString(toHidlString(name),
+ Status status = mPlugin->setPropertyString(toHidlString(name),
toHidlString(value));
return toStatusT(status);
}
@@ -828,24 +1016,26 @@
status_t DrmHal::setPropertyByteArray(String8 const &name,
Vector<uint8_t> const &value ) const {
Mutex::Autolock autoLock(mLock);
-
- if (mInitCheck != OK) {
- return mInitCheck;
- }
+ INIT_CHECK();
Status status = mPlugin->setPropertyByteArray(toHidlString(name),
toHidlVec(value));
return toStatusT(status);
}
+status_t DrmHal::getMetrics(MediaAnalyticsItem* item) {
+ if (item == nullptr) {
+ return UNEXPECTED_NULL;
+ }
+
+ mMetrics.Export(item);
+ return OK;
+}
status_t DrmHal::setCipherAlgorithm(Vector<uint8_t> const &sessionId,
String8 const &algorithm) {
Mutex::Autolock autoLock(mLock);
-
- if (mInitCheck != OK) {
- return mInitCheck;
- }
+ INIT_CHECK();
DrmSessionManager::Instance()->useSession(sessionId);
@@ -857,10 +1047,7 @@
status_t DrmHal::setMacAlgorithm(Vector<uint8_t> const &sessionId,
String8 const &algorithm) {
Mutex::Autolock autoLock(mLock);
-
- if (mInitCheck != OK) {
- return mInitCheck;
- }
+ INIT_CHECK();
DrmSessionManager::Instance()->useSession(sessionId);
@@ -873,10 +1060,7 @@
Vector<uint8_t> const &keyId, Vector<uint8_t> const &input,
Vector<uint8_t> const &iv, Vector<uint8_t> &output) {
Mutex::Autolock autoLock(mLock);
-
- if (mInitCheck != OK) {
- return mInitCheck;
- }
+ INIT_CHECK();
DrmSessionManager::Instance()->useSession(sessionId);
@@ -899,10 +1083,7 @@
Vector<uint8_t> const &keyId, Vector<uint8_t> const &input,
Vector<uint8_t> const &iv, Vector<uint8_t> &output) {
Mutex::Autolock autoLock(mLock);
-
- if (mInitCheck != OK) {
- return mInitCheck;
- }
+ INIT_CHECK();
DrmSessionManager::Instance()->useSession(sessionId);
@@ -925,10 +1106,7 @@
Vector<uint8_t> const &keyId, Vector<uint8_t> const &message,
Vector<uint8_t> &signature) {
Mutex::Autolock autoLock(mLock);
-
- if (mInitCheck != OK) {
- return mInitCheck;
- }
+ INIT_CHECK();
DrmSessionManager::Instance()->useSession(sessionId);
@@ -951,10 +1129,7 @@
Vector<uint8_t> const &keyId, Vector<uint8_t> const &message,
Vector<uint8_t> const &signature, bool &match) {
Mutex::Autolock autoLock(mLock);
-
- if (mInitCheck != OK) {
- return mInitCheck;
- }
+ INIT_CHECK();
DrmSessionManager::Instance()->useSession(sessionId);
@@ -979,10 +1154,7 @@
String8 const &algorithm, Vector<uint8_t> const &message,
Vector<uint8_t> const &wrappedKey, Vector<uint8_t> &signature) {
Mutex::Autolock autoLock(mLock);
-
- if (mInitCheck != OK) {
- return mInitCheck;
- }
+ INIT_CHECK();
if (!checkPermission("android.permission.ACCESS_DRM_CERTIFICATES")) {
return -EPERM;
@@ -1030,6 +1202,7 @@
}
}
+
void DrmHal::reportMetrics() const
{
Vector<uint8_t> metrics;
diff --git a/drm/libmediadrm/DrmMetrics.cpp b/drm/libmediadrm/DrmMetrics.cpp
new file mode 100644
index 0000000..258c4b0
--- /dev/null
+++ b/drm/libmediadrm/DrmMetrics.cpp
@@ -0,0 +1,152 @@
+/*
+ * 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.
+ */
+
+#include <android-base/macros.h>
+#include <media/DrmMetrics.h>
+
+using ::android::hardware::drm::V1_0::EventType;
+using ::android::hardware::drm::V1_0::KeyStatusType;
+
+namespace {
+
+template<typename T>
+std::string GetAttributeName(T type);
+
+template<>
+std::string GetAttributeName<KeyStatusType>(KeyStatusType type) {
+ static const char* type_names[] = {
+ "USABLE", "EXPIRED", "OUTPUT_NOT_ALLOWED",
+ "STATUS_PENDING", "INTERNAL_ERROR" };
+ if (((size_t) type) > arraysize(type_names)) {
+ return "UNKNOWN_TYPE";
+ }
+ return type_names[(size_t) type];
+}
+
+template<>
+std::string GetAttributeName<EventType>(EventType type) {
+ static const char* type_names[] = {
+ "PROVISION_REQUIRED", "KEY_NEEDED", "KEY_EXPIRED",
+ "VENDOR_DEFINED", "SESSION_RECLAIMED" };
+ if (((size_t) type) > arraysize(type_names)) {
+ return "UNKNOWN_TYPE";
+ }
+ return type_names[(size_t) type];
+}
+
+template<typename T>
+void ExportCounterMetric(const android::CounterMetric<T>& counter,
+ android::MediaAnalyticsItem* item) {
+ if (!item) {
+ ALOGE("item was unexpectedly null.");
+ return;
+ }
+ std::string success_count_name = counter.metric_name() + ".ok.count";
+ std::string error_count_name = counter.metric_name() + ".error.count";
+ counter.ExportValues(
+ [&] (const android::status_t status, const int64_t value) {
+ if (status == android::OK) {
+ item->setInt64(success_count_name.c_str(), value);
+ } else {
+ int64_t total_errors(0);
+ item->getInt64(error_count_name.c_str(), &total_errors);
+ item->setInt64(error_count_name.c_str(), total_errors + value);
+ // TODO: Add support for exporting the list of error values.
+ // This probably needs to be added to MediaAnalyticsItem.
+ }
+ });
+}
+
+template<typename T>
+void ExportCounterMetricWithAttributeNames(
+ const android::CounterMetric<T>& counter,
+ android::MediaAnalyticsItem* item) {
+ if (!item) {
+ ALOGE("item was unexpectedly null.");
+ return;
+ }
+ counter.ExportValues(
+ [&] (const T& attribute, const int64_t value) {
+ std::string name = counter.metric_name()
+ + "." + GetAttributeName(attribute) + ".count";
+ item->setInt64(name.c_str(), value);
+ });
+}
+
+template<typename T>
+void ExportEventMetric(const android::EventMetric<T>& event,
+ android::MediaAnalyticsItem* item) {
+ if (!item) {
+ ALOGE("item was unexpectedly null.");
+ return;
+ }
+ std::string success_count_name = event.metric_name() + ".ok.count";
+ std::string error_count_name = event.metric_name() + ".error.count";
+ std::string timing_name = event.metric_name() + ".ok.average_time_micros";
+ event.ExportValues(
+ [&] (const android::status_t& status,
+ const android::EventStatistics& value) {
+ if (status == android::OK) {
+ item->setInt64(success_count_name.c_str(), value.count);
+ item->setInt64(timing_name.c_str(), value.mean);
+ } else {
+ int64_t total_errors(0);
+ item->getInt64(error_count_name.c_str(), &total_errors);
+ item->setInt64(error_count_name.c_str(),
+ total_errors + value.count);
+ // TODO: Add support for exporting the list of error values.
+ // This probably needs to be added to MediaAnalyticsItem.
+ }
+ });
+}
+
+} // namespace anonymous
+
+namespace android {
+
+MediaDrmMetrics::MediaDrmMetrics()
+ : mOpenSessionCounter("drm.mediadrm.open_session", "status"),
+ mCloseSessionCounter("drm.mediadrm.close_session", "status"),
+ mGetKeyRequestTiming("drm.mediadrm.get_key_request", "status"),
+ mProvideKeyResponseTiming("drm.mediadrm.provide_key_response", "status"),
+ mGetProvisionRequestCounter(
+ "drm.mediadrm.get_provision_request", "status"),
+ mProvideProvisionResponseCounter(
+ "drm.mediadrm.provide_provision_response", "status"),
+ mKeyStatusChangeCounter(
+ "drm.mediadrm.key_status_change", "key_status_type"),
+ mEventCounter("drm.mediadrm.event", "event_type"),
+ mGetDeviceUniqueIdCounter(
+ "drm.mediadrm.get_device_unique_id", "status") {
+}
+
+void MediaDrmMetrics::Export(MediaAnalyticsItem* item) {
+ if (!item) {
+ ALOGE("item was unexpectedly null.");
+ return;
+ }
+ ExportCounterMetric(mOpenSessionCounter, item);
+ ExportCounterMetric(mCloseSessionCounter, item);
+ ExportEventMetric(mGetKeyRequestTiming, item);
+ ExportEventMetric(mProvideKeyResponseTiming, item);
+ ExportCounterMetric(mGetProvisionRequestCounter, item);
+ ExportCounterMetric(mProvideProvisionResponseCounter, item);
+ ExportCounterMetricWithAttributeNames(mKeyStatusChangeCounter, item);
+ ExportCounterMetricWithAttributeNames(mEventCounter, item);
+ ExportCounterMetric(mGetDeviceUniqueIdCounter, item);
+}
+
+} // namespace android
diff --git a/drm/libmediadrm/IDrm.cpp b/drm/libmediadrm/IDrm.cpp
index 8ff6e6a..e7417cc 100644
--- a/drm/libmediadrm/IDrm.cpp
+++ b/drm/libmediadrm/IDrm.cpp
@@ -46,6 +46,7 @@
GET_PROPERTY_BYTE_ARRAY,
SET_PROPERTY_STRING,
SET_PROPERTY_BYTE_ARRAY,
+ GET_METRICS,
SET_CIPHER_ALGORITHM,
SET_MAC_ALGORITHM,
ENCRYPT,
@@ -55,7 +56,11 @@
VERIFY,
SET_LISTENER,
GET_SECURE_STOP,
- RELEASE_ALL_SECURE_STOPS
+ RELEASE_ALL_SECURE_STOPS,
+ GET_HDCP_LEVELS,
+ GET_NUMBER_OF_SESSIONS,
+ GET_SECURITY_LEVEL,
+ SET_SECURITY_LEVEL,
};
struct BpDrm : public BpInterface<IDrm> {
@@ -350,6 +355,82 @@
return reply.readInt32();
}
+ virtual status_t getHdcpLevels(DrmPlugin::HdcpLevel *connected,
+ DrmPlugin::HdcpLevel *max) const {
+ Parcel data, reply;
+
+ if (connected == NULL || max == NULL) {
+ return BAD_VALUE;
+ }
+
+ data.writeInterfaceToken(IDrm::getInterfaceDescriptor());
+
+ status_t status = remote()->transact(GET_HDCP_LEVELS, data, &reply);
+ if (status != OK) {
+ return status;
+ }
+
+ *connected = static_cast<DrmPlugin::HdcpLevel>(reply.readInt32());
+ *max = static_cast<DrmPlugin::HdcpLevel>(reply.readInt32());
+ return reply.readInt32();
+ }
+
+ virtual status_t getNumberOfSessions(uint32_t *open, uint32_t *max) const {
+ Parcel data, reply;
+
+ if (open == NULL || max == NULL) {
+ return BAD_VALUE;
+ }
+
+ data.writeInterfaceToken(IDrm::getInterfaceDescriptor());
+
+ status_t status = remote()->transact(GET_NUMBER_OF_SESSIONS, data, &reply);
+ if (status != OK) {
+ return status;
+ }
+
+ *open = reply.readInt32();
+ *max = reply.readInt32();
+ return reply.readInt32();
+ }
+
+ virtual status_t getSecurityLevel(Vector<uint8_t> const &sessionId,
+ DrmPlugin::SecurityLevel *level) const {
+ Parcel data, reply;
+
+ if (level == NULL) {
+ return BAD_VALUE;
+ }
+
+ data.writeInterfaceToken(IDrm::getInterfaceDescriptor());
+
+ writeVector(data, sessionId);
+ status_t status = remote()->transact(GET_SECURITY_LEVEL, data, &reply);
+ if (status != OK) {
+ return status;
+ }
+
+ *level = static_cast<DrmPlugin::SecurityLevel>(reply.readInt32());
+ return reply.readInt32();
+ }
+
+ virtual status_t setSecurityLevel(Vector<uint8_t> const &sessionId,
+ const DrmPlugin::SecurityLevel& level) {
+ Parcel data, reply;
+
+ data.writeInterfaceToken(IDrm::getInterfaceDescriptor());
+
+ writeVector(data, sessionId);
+ data.writeInt32(static_cast<uint32_t>(level));
+
+ status_t status = remote()->transact(SET_SECURITY_LEVEL, data, &reply);
+ if (status != OK) {
+ return status;
+ }
+
+ return reply.readInt32();
+ }
+
virtual status_t getPropertyByteArray(String8 const &name, Vector<uint8_t> &value) const {
Parcel data, reply;
data.writeInterfaceToken(IDrm::getInterfaceDescriptor());
@@ -393,6 +474,18 @@
return reply.readInt32();
}
+ virtual status_t getMetrics(MediaAnalyticsItem *item) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IDrm::getInterfaceDescriptor());
+
+ status_t status = remote()->transact(GET_METRICS, data, &reply);
+ if (status != OK) {
+ return status;
+ }
+
+ item->readFromParcel(reply);
+ return reply.readInt32();
+ }
virtual status_t setCipherAlgorithm(Vector<uint8_t> const &sessionId,
String8 const &algorithm) {
@@ -788,6 +881,53 @@
return OK;
}
+ case GET_HDCP_LEVELS:
+ {
+ CHECK_INTERFACE(IDrm, data, reply);
+ DrmPlugin::HdcpLevel connected = DrmPlugin::kHdcpLevelUnknown;
+ DrmPlugin::HdcpLevel max = DrmPlugin::kHdcpLevelUnknown;
+ status_t result = getHdcpLevels(&connected, &max);
+ reply->writeInt32(connected);
+ reply->writeInt32(max);
+ reply->writeInt32(result);
+ return OK;
+ }
+
+ case GET_NUMBER_OF_SESSIONS:
+ {
+ CHECK_INTERFACE(IDrm, data, reply);
+ uint32_t open = 0, max = 0;
+ status_t result = getNumberOfSessions(&open, &max);
+ reply->writeInt32(open);
+ reply->writeInt32(max);
+ reply->writeInt32(result);
+ return OK;
+ }
+
+ case GET_SECURITY_LEVEL:
+ {
+ CHECK_INTERFACE(IDrm, data, reply);
+ Vector<uint8_t> sessionId;
+ readVector(data, sessionId);
+ DrmPlugin::SecurityLevel level = DrmPlugin::kSecurityLevelUnknown;
+ status_t result = getSecurityLevel(sessionId, &level);
+ reply->writeInt32(level);
+ reply->writeInt32(result);
+ return OK;
+ }
+
+ case SET_SECURITY_LEVEL:
+ {
+ CHECK_INTERFACE(IDrm, data, reply);
+ Vector<uint8_t> sessionId;
+ readVector(data, sessionId);
+ DrmPlugin::SecurityLevel level =
+ static_cast<DrmPlugin::SecurityLevel>(data.readInt32());
+ status_t result = setSecurityLevel(sessionId, level);
+ reply->writeInt32(result);
+ return OK;
+ }
+
case GET_PROPERTY_STRING:
{
CHECK_INTERFACE(IDrm, data, reply);
@@ -829,6 +969,17 @@
return OK;
}
+ case GET_METRICS:
+ {
+ CHECK_INTERFACE(IDrm, data, reply);
+
+ MediaAnalyticsItem item;
+ status_t result = getMetrics(&item);
+ item.writeToParcel(reply);
+ reply->writeInt32(result);
+ return OK;
+ }
+
case SET_CIPHER_ALGORITHM:
{
CHECK_INTERFACE(IDrm, data, reply);
diff --git a/drm/libmediadrm/tests/Android.bp b/drm/libmediadrm/tests/Android.bp
new file mode 100644
index 0000000..fdc982d
--- /dev/null
+++ b/drm/libmediadrm/tests/Android.bp
@@ -0,0 +1,44 @@
+// Build definitions for unit tests.
+
+cc_test {
+ name: "CounterMetric_test",
+ srcs: ["CounterMetric_test.cpp"],
+ shared_libs: ["libmediadrm"],
+ include_dirs: ["frameworks/av/include/media"],
+ cflags: [
+ "-Werror",
+ "-Wall",
+ ],
+}
+
+cc_test {
+ name: "DrmMetrics_test",
+ srcs: ["DrmMetrics_test.cpp"],
+ shared_libs: [
+ "android.hardware.drm@1.0",
+ "liblog",
+ "libmediadrm",
+ "libmediametrics",
+ "libutils",
+ ],
+ include_dirs: ["frameworks/av/include/media"],
+ cflags: [
+ "-Werror",
+ "-Wall",
+ ],
+}
+
+cc_test {
+ name: "EventMetric_test",
+ srcs: ["EventMetric_test.cpp"],
+ shared_libs: [
+ "liblog",
+ "libmediadrm",
+ "libutils",
+ ],
+ include_dirs: ["frameworks/av/include/media"],
+ cflags: [
+ "-Werror",
+ "-Wall",
+ ],
+}
diff --git a/drm/libmediadrm/tests/CounterMetric_test.cpp b/drm/libmediadrm/tests/CounterMetric_test.cpp
new file mode 100644
index 0000000..6bca0da
--- /dev/null
+++ b/drm/libmediadrm/tests/CounterMetric_test.cpp
@@ -0,0 +1,80 @@
+/*
+ * 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.
+ */
+
+#include <gtest/gtest.h>
+
+#include "CounterMetric.h"
+
+namespace android {
+
+/**
+ * Unit tests for the CounterMetric class.
+ */
+class CounterMetricTest : public ::testing::Test {
+};
+
+TEST_F(CounterMetricTest, IntDataTypeEmpty) {
+ CounterMetric<int> metric("MyMetricName", "MetricAttributeName");
+
+ std::map<int, int64_t> values;
+
+ metric.ExportValues(
+ [&] (int attribute_value, int64_t value) {
+ values[attribute_value] = value;
+ });
+
+ EXPECT_TRUE(values.empty());
+}
+
+TEST_F(CounterMetricTest, IntDataType) {
+ CounterMetric<int> metric("MyMetricName", "MetricAttributeName");
+
+ std::map<int, int64_t> values;
+
+ metric.Increment(7);
+ metric.Increment(8);
+ metric.Increment(8);
+
+ metric.ExportValues(
+ [&] (int attribute_value, int64_t value) {
+ values[attribute_value] = value;
+ });
+
+ ASSERT_EQ(2u, values.size());
+ EXPECT_EQ(1, values[7]);
+ EXPECT_EQ(2, values[8]);
+}
+
+TEST_F(CounterMetricTest, StringDataType) {
+ CounterMetric<std::string> metric("MyMetricName", "MetricAttributeName");
+
+ std::map<std::string, int64_t> values;
+
+ metric.Increment("a");
+ metric.Increment("b");
+ metric.Increment("b");
+
+ metric.ExportValues(
+ [&] (std::string attribute_value, int64_t value) {
+ values[attribute_value] = value;
+ });
+
+ ASSERT_EQ(2u, values.size());
+ EXPECT_EQ(1, values["a"]);
+ EXPECT_EQ(2, values["b"]);
+}
+
+} // namespace android
diff --git a/drm/libmediadrm/tests/DrmMetrics_test.cpp b/drm/libmediadrm/tests/DrmMetrics_test.cpp
new file mode 100644
index 0000000..d1948b4
--- /dev/null
+++ b/drm/libmediadrm/tests/DrmMetrics_test.cpp
@@ -0,0 +1,189 @@
+/*
+ * 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.
+ */
+
+#include <gtest/gtest.h>
+
+#include "DrmMetrics.h"
+
+using ::android::hardware::drm::V1_0::EventType;
+using ::android::hardware::drm::V1_0::KeyStatusType;
+
+namespace android {
+
+/**
+ * Unit tests for the MediaDrmMetrics class.
+ */
+class MediaDrmMetricsTest : public ::testing::Test {
+};
+
+TEST_F(MediaDrmMetricsTest, EmptySuccess) {
+ MediaDrmMetrics metrics;
+ MediaAnalyticsItem item;
+
+ metrics.Export(&item);
+ EXPECT_EQ(0, item.count());
+}
+
+TEST_F(MediaDrmMetricsTest, AllValuesSuccessCounts) {
+ MediaDrmMetrics metrics;
+
+ metrics.mOpenSessionCounter.Increment(OK);
+ metrics.mCloseSessionCounter.Increment(OK);
+
+ {
+ EventTimer<status_t> get_key_request_timer(&metrics.mGetKeyRequestTiming);
+ EventTimer<status_t> provide_key_response_timer(
+ &metrics.mProvideKeyResponseTiming);
+ get_key_request_timer.SetAttribute(OK);
+ provide_key_response_timer.SetAttribute(OK);
+ }
+
+ metrics.mGetProvisionRequestCounter.Increment(OK);
+ metrics.mProvideProvisionResponseCounter.Increment(OK);
+ metrics.mGetDeviceUniqueIdCounter.Increment(OK);
+
+ metrics.mKeyStatusChangeCounter.Increment(KeyStatusType::USABLE);
+ metrics.mEventCounter.Increment(EventType::PROVISION_REQUIRED);
+
+ MediaAnalyticsItem item;
+
+ metrics.Export(&item);
+ EXPECT_EQ(11, item.count());
+
+ // Verify the list of pairs of int64 metrics.
+ std::vector<std::pair<std::string, int64_t>> expected_values = {
+ { "drm.mediadrm.open_session.ok.count", 1 },
+ { "drm.mediadrm.close_session.ok.count", 1 },
+ { "drm.mediadrm.get_key_request.ok.count", 1 },
+ { "drm.mediadrm.provide_key_response.ok.count", 1 },
+ { "drm.mediadrm.get_provision_request.ok.count", 1 },
+ { "drm.mediadrm.provide_provision_response.ok.count", 1 },
+ { "drm.mediadrm.key_status_change.USABLE.count", 1 },
+ { "drm.mediadrm.event.PROVISION_REQUIRED.count", 1 },
+ { "drm.mediadrm.get_device_unique_id.ok.count", 1 }};
+ for (const auto& expected_pair : expected_values) {
+ int64_t value = -1;
+ EXPECT_TRUE(item.getInt64(expected_pair.first.c_str(), &value))
+ << "Failed to get " << expected_pair.first;
+ EXPECT_EQ(expected_pair.second, value)
+ << "Unexpected value for " << expected_pair.first;
+ }
+
+ // Validate timing values exist.
+ int64_t value = -1;
+ EXPECT_TRUE(
+ item.getInt64("drm.mediadrm.get_key_request.ok.average_time_micros",
+ &value));
+ EXPECT_GE(value, 0);
+
+ value = -1;
+ EXPECT_TRUE(
+ item.getInt64("drm.mediadrm.provide_key_response.ok.average_time_micros",
+ &value));
+ EXPECT_GE(value, 0);
+}
+
+TEST_F(MediaDrmMetricsTest, AllValuesFull) {
+ MediaDrmMetrics metrics;
+
+ metrics.mOpenSessionCounter.Increment(OK);
+ metrics.mOpenSessionCounter.Increment(UNEXPECTED_NULL);
+
+ metrics.mCloseSessionCounter.Increment(OK);
+ metrics.mCloseSessionCounter.Increment(UNEXPECTED_NULL);
+
+ for (status_t s : {OK, UNEXPECTED_NULL}) {
+ {
+ EventTimer<status_t> get_key_request_timer(&metrics.mGetKeyRequestTiming);
+ EventTimer<status_t> provide_key_response_timer(
+ &metrics.mProvideKeyResponseTiming);
+ get_key_request_timer.SetAttribute(s);
+ provide_key_response_timer.SetAttribute(s);
+ }
+ }
+
+ metrics.mGetProvisionRequestCounter.Increment(OK);
+ metrics.mGetProvisionRequestCounter.Increment(UNEXPECTED_NULL);
+ metrics.mProvideProvisionResponseCounter.Increment(OK);
+ metrics.mProvideProvisionResponseCounter.Increment(UNEXPECTED_NULL);
+ metrics.mGetDeviceUniqueIdCounter.Increment(OK);
+ metrics.mGetDeviceUniqueIdCounter.Increment(UNEXPECTED_NULL);
+
+ metrics.mKeyStatusChangeCounter.Increment(KeyStatusType::USABLE);
+ metrics.mKeyStatusChangeCounter.Increment(KeyStatusType::EXPIRED);
+ metrics.mKeyStatusChangeCounter.Increment(KeyStatusType::OUTPUTNOTALLOWED);
+ metrics.mKeyStatusChangeCounter.Increment(KeyStatusType::STATUSPENDING);
+ metrics.mKeyStatusChangeCounter.Increment(KeyStatusType::INTERNALERROR);
+ metrics.mEventCounter.Increment(EventType::PROVISION_REQUIRED);
+ metrics.mEventCounter.Increment(EventType::KEY_NEEDED);
+ metrics.mEventCounter.Increment(EventType::KEY_EXPIRED);
+ metrics.mEventCounter.Increment(EventType::VENDOR_DEFINED);
+ metrics.mEventCounter.Increment(EventType::SESSION_RECLAIMED);
+
+ MediaAnalyticsItem item;
+
+ metrics.Export(&item);
+ EXPECT_EQ(26, item.count());
+
+ // Verify the list of pairs of int64 metrics.
+ std::vector<std::pair<std::string, int64_t>> expected_values = {
+ { "drm.mediadrm.open_session.ok.count", 1 },
+ { "drm.mediadrm.close_session.ok.count", 1 },
+ { "drm.mediadrm.get_key_request.ok.count", 1 },
+ { "drm.mediadrm.provide_key_response.ok.count", 1 },
+ { "drm.mediadrm.get_provision_request.ok.count", 1 },
+ { "drm.mediadrm.provide_provision_response.ok.count", 1 },
+ { "drm.mediadrm.get_device_unique_id.ok.count", 1 },
+ { "drm.mediadrm.open_session.error.count", 1 },
+ { "drm.mediadrm.close_session.error.count", 1 },
+ { "drm.mediadrm.get_key_request.error.count", 1 },
+ { "drm.mediadrm.provide_key_response.error.count", 1 },
+ { "drm.mediadrm.get_provision_request.error.count", 1 },
+ { "drm.mediadrm.provide_provision_response.error.count", 1 },
+ { "drm.mediadrm.get_device_unique_id.error.count", 1 },
+ { "drm.mediadrm.key_status_change.USABLE.count", 1 },
+ { "drm.mediadrm.key_status_change.EXPIRED.count", 1 },
+ { "drm.mediadrm.key_status_change.OUTPUT_NOT_ALLOWED.count", 1 },
+ { "drm.mediadrm.key_status_change.STATUS_PENDING.count", 1 },
+ { "drm.mediadrm.key_status_change.INTERNAL_ERROR.count", 1 },
+ { "drm.mediadrm.event.PROVISION_REQUIRED.count", 1 },
+ { "drm.mediadrm.event.KEY_NEEDED.count", 1 },
+ { "drm.mediadrm.event.KEY_EXPIRED.count", 1 },
+ { "drm.mediadrm.event.VENDOR_DEFINED.count", 1 },
+ { "drm.mediadrm.event.SESSION_RECLAIMED.count", 1 }};
+ for (const auto& expected_pair : expected_values) {
+ int64_t value = -1;
+ EXPECT_TRUE(item.getInt64(expected_pair.first.c_str(), &value))
+ << "Failed to get " << expected_pair.first;
+ EXPECT_EQ(expected_pair.second, value)
+ << "Unexpected value for " << expected_pair.first;
+ }
+
+ // Validate timing values exist.
+ int64_t value = -1;
+ EXPECT_TRUE(
+ item.getInt64("drm.mediadrm.get_key_request.ok.average_time_micros",
+ &value));
+ EXPECT_GE(value, 0);
+
+ value = -1;
+ EXPECT_TRUE(
+ item.getInt64("drm.mediadrm.provide_key_response.ok.average_time_micros",
+ &value));
+ EXPECT_GE(value, 0);
+}
+
+} // namespace android
diff --git a/drm/libmediadrm/tests/EventMetric_test.cpp b/drm/libmediadrm/tests/EventMetric_test.cpp
new file mode 100644
index 0000000..eb6c4f6
--- /dev/null
+++ b/drm/libmediadrm/tests/EventMetric_test.cpp
@@ -0,0 +1,142 @@
+/*
+ * 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.
+ */
+
+#include <gtest/gtest.h>
+
+#include "EventMetric.h"
+
+namespace android {
+
+/**
+ * Unit tests for the EventMetric class.
+ */
+
+TEST(EventMetricTest, IntDataTypeEmpty) {
+ EventMetric<int> metric("MyMetricName", "MetricAttributeName");
+
+ std::map<int, EventStatistics> values;
+
+ metric.ExportValues(
+ [&] (int attribute_value, const EventStatistics& value) {
+ values[attribute_value] = value;
+ });
+
+ EXPECT_TRUE(values.empty());
+}
+
+TEST(EventMetricTest, IntDataType) {
+ EventMetric<int> metric("MyMetricName", "MetricAttributeName");
+
+ std::map<int, EventStatistics> values;
+
+ metric.Record(4, 7);
+ metric.Record(5, 8);
+ metric.Record(5, 8);
+ metric.Record(5, 8);
+ metric.Record(6, 8);
+ metric.Record(6, 8);
+ metric.Record(6, 8);
+
+ metric.ExportValues(
+ [&] (int attribute_value, const EventStatistics& value) {
+ values[attribute_value] = value;
+ });
+
+ ASSERT_EQ(2u, values.size());
+ EXPECT_EQ(4, values[7].min);
+ EXPECT_EQ(4, values[7].max);
+ EXPECT_EQ(4, values[7].mean);
+ EXPECT_EQ(1, values[7].count);
+
+ EXPECT_EQ(5, values[8].min);
+ EXPECT_EQ(6, values[8].max);
+ // This is an approximate value because of the technique we're using.
+ EXPECT_NEAR(5.5, values[8].mean, 0.2);
+ EXPECT_EQ(6, values[8].count);
+}
+
+TEST(EventMetricTest, StringDataType) {
+ EventMetric<std::string> metric("MyMetricName", "MetricAttributeName");
+
+ std::map<std::string, EventStatistics> values;
+
+ metric.Record(1, "a");
+ metric.Record(2, "b");
+ metric.Record(2, "b");
+ metric.Record(3, "b");
+ metric.Record(3, "b");
+
+ metric.ExportValues(
+ [&] (std::string attribute_value, const EventStatistics& value) {
+ values[attribute_value] = value;
+ });
+
+ ASSERT_EQ(2u, values.size());
+ EXPECT_EQ(1, values["a"].min);
+ EXPECT_EQ(1, values["a"].max);
+ EXPECT_EQ(1, values["a"].mean);
+ EXPECT_EQ(1, values["a"].count);
+
+ EXPECT_EQ(2, values["b"].min);
+ EXPECT_EQ(3, values["b"].max);
+ EXPECT_NEAR(2.5, values["b"].mean, 0.2);
+ EXPECT_EQ(4, values["b"].count);
+}
+
+// Helper class that allows us to mock the clock.
+template<typename AttributeType>
+class MockEventTimer : public EventTimer<AttributeType> {
+ public:
+ explicit MockEventTimer(nsecs_t time_delta_ns,
+ EventMetric<AttributeType>* metric)
+ : EventTimer<AttributeType>(metric) {
+ // Pretend the event started earlier.
+ this->start_time_ = systemTime() - time_delta_ns;
+ }
+};
+
+TEST(EventTimerTest, IntDataType) {
+ EventMetric<int> metric("MyMetricName", "MetricAttributeName");
+
+ for (int i = 0; i < 5; i++) {
+ {
+ // Add a mock time delta.
+ MockEventTimer<int> metric_timer(i * 1000000, &metric);
+ metric_timer.SetAttribute(i % 2);
+ }
+ }
+
+ std::map<int, EventStatistics> values;
+ metric.ExportValues(
+ [&] (int attribute_value, const EventStatistics& value) {
+ values[attribute_value] = value;
+ });
+
+ ASSERT_EQ(2u, values.size());
+ EXPECT_LT(values[0].min, values[0].max);
+ EXPECT_GE(4000, values[0].max);
+ EXPECT_GT(values[0].mean, values[0].min);
+ EXPECT_LE(values[0].mean, values[0].max);
+ EXPECT_EQ(3, values[0].count);
+
+ EXPECT_LT(values[1].min, values[1].max);
+ EXPECT_GE(3000, values[1].max);
+ EXPECT_GT(values[1].mean, values[1].min);
+ EXPECT_LE(values[1].mean, values[1].max);
+ EXPECT_EQ(2, values[1].count);
+}
+
+} // namespace android
diff --git a/drm/mediadrm/plugins/clearkey/DrmPlugin.h b/drm/mediadrm/plugins/clearkey/DrmPlugin.h
index 62bc86f..4fa42e5 100644
--- a/drm/mediadrm/plugins/clearkey/DrmPlugin.h
+++ b/drm/mediadrm/plugins/clearkey/DrmPlugin.h
@@ -133,6 +133,35 @@
return android::ERROR_DRM_CANNOT_HANDLE;
}
+ virtual status_t getHdcpLevels(HdcpLevel *connectedLevel,
+ HdcpLevel *maxLevel) const {
+ UNUSED(connectedLevel);
+ UNUSED(maxLevel);
+ return android::ERROR_DRM_CANNOT_HANDLE;
+ }
+
+
+ virtual status_t getNumberOfSessions(uint32_t *currentSessions,
+ uint32_t *maxSessions) const {
+ UNUSED(currentSessions);
+ UNUSED(maxSessions);
+ return android::ERROR_DRM_CANNOT_HANDLE;
+ }
+
+ virtual status_t getSecurityLevel(Vector<uint8_t> const &sessionId,
+ SecurityLevel *level) const {
+ UNUSED(sessionId);
+ UNUSED(level);
+ return android::ERROR_DRM_CANNOT_HANDLE;
+ }
+
+ virtual status_t setSecurityLevel(Vector<uint8_t> const &sessionId,
+ const SecurityLevel& level) {
+ UNUSED(sessionId);
+ UNUSED(level);
+ return android::ERROR_DRM_CANNOT_HANDLE;
+ }
+
virtual status_t getPropertyString(
const String8& name, String8& value) const;
diff --git a/include/media/CounterMetric.h b/include/media/CounterMetric.h
new file mode 120000
index 0000000..baba043
--- /dev/null
+++ b/include/media/CounterMetric.h
@@ -0,0 +1 @@
+../../media/libmedia/include/media/CounterMetric.h
\ No newline at end of file
diff --git a/include/media/DrmMetrics.h b/include/media/DrmMetrics.h
new file mode 120000
index 0000000..abc966b
--- /dev/null
+++ b/include/media/DrmMetrics.h
@@ -0,0 +1 @@
+../../media/libmedia/include/media/DrmMetrics.h
\ No newline at end of file
diff --git a/include/media/EventMetric.h b/include/media/EventMetric.h
new file mode 120000
index 0000000..5707d9a
--- /dev/null
+++ b/include/media/EventMetric.h
@@ -0,0 +1 @@
+../../media/libmedia/include/media/EventMetric.h
\ No newline at end of file
diff --git a/include/media/MmapStreamInterface.h b/include/media/MmapStreamInterface.h
index d689e25..0196a0c 100644
--- a/include/media/MmapStreamInterface.h
+++ b/include/media/MmapStreamInterface.h
@@ -52,6 +52,9 @@
* \param[in,out] deviceId audio device the stream should preferably be routed to/from
* Requested as input,
* Actual as output
+ * \param[in,out] sessionId audio sessionId for the stream
+ * Requested as input, may be AUDIO_SESSION_ALLOCATE
+ * Actual as output
* \param[in] callback the MmapStreamCallback interface used by AudioFlinger to notify
* condition changes affecting the stream operation
* \param[out] interface the MmapStreamInterface interface controlling the created stream
@@ -66,6 +69,7 @@
audio_config_base_t *config,
const AudioClient& client,
audio_port_handle_t *deviceId,
+ audio_session_t *sessionId,
const sp<MmapStreamCallback>& callback,
sp<MmapStreamInterface>& interface,
audio_port_handle_t *handle);
diff --git a/media/extractors/mp4/SampleIterator.cpp b/media/extractors/mp4/SampleIterator.cpp
index 78cc691..c194397 100644
--- a/media/extractors/mp4/SampleIterator.cpp
+++ b/media/extractors/mp4/SampleIterator.cpp
@@ -244,13 +244,14 @@
switch (mTable->mSampleSizeFieldSize) {
case 32:
{
+ uint32_t x;
if (mTable->mDataSource->readAt(
mTable->mSampleSizeOffset + 12 + 4 * sampleIndex,
- size, sizeof(*size)) < (ssize_t)sizeof(*size)) {
+ &x, sizeof(x)) < (ssize_t)sizeof(x)) {
return ERROR_IO;
}
- *size = ntohl(*size);
+ *size = ntohl(x);
break;
}
diff --git a/media/libaaudio/examples/loopback/src/loopback.cpp b/media/libaaudio/examples/loopback/src/loopback.cpp
index d23d907..f2254ce 100644
--- a/media/libaaudio/examples/loopback/src/loopback.cpp
+++ b/media/libaaudio/examples/loopback/src/loopback.cpp
@@ -160,16 +160,9 @@
static void usage() {
printf("Usage: aaudio_loopback [OPTION]...\n\n");
- printf(" -c{channels} number of output channels\n");
+ AAudioArgsParser::usage();
printf(" -C{channels} number of input channels\n");
printf(" -g{gain} recirculating loopback gain\n");
- printf(" -m{0|1|2|3} set MMAP policy\n");
- printf(" 0 = _UNSPECIFIED\n");
- printf(" 1 = _NEVER\n");
- printf(" 2 = _AUTO, also if -m is used with no number\n");
- printf(" 3 = _ALWAYS\n");
- printf(" -n{numBursts} buffer size, for example 2 for double buffered\n");
- printf(" -p{outPerf} set output AAUDIO_PERFORMANCE_MODE*\n");
printf(" -P{inPerf} set input AAUDIO_PERFORMANCE_MODE*\n");
printf(" n for _NONE\n");
printf(" l for _LATENCY\n");
@@ -178,8 +171,7 @@
printf(" m for sine magnitude\n");
printf(" e for echo latency (default)\n");
printf(" f for file latency, analyzes %s\n\n", FILENAME_ECHOS);
- printf(" -x use EXCLUSIVE mode for output\n");
- printf(" -X use EXCLUSIVE mode for input\n");
+ printf(" -X use EXCLUSIVE mode for input\n");
printf("Example: aaudio_loopback -n2 -pl -Pl -x\n");
}
diff --git a/media/libaaudio/examples/utils/AAudioArgsParser.h b/media/libaaudio/examples/utils/AAudioArgsParser.h
index 142b295..4fc5b9f 100644
--- a/media/libaaudio/examples/utils/AAudioArgsParser.h
+++ b/media/libaaudio/examples/utils/AAudioArgsParser.h
@@ -88,6 +88,30 @@
mPerformanceMode = performanceMode;
}
+ aaudio_usage_t getUsage() const {
+ return mUsage;
+ }
+
+ void setUsage(aaudio_usage_t usage) {
+ mUsage = usage;
+ }
+
+ aaudio_content_type_t getContentType() const {
+ return mContentType;
+ }
+
+ void setContentType(aaudio_content_type_t contentType) {
+ mContentType = contentType;
+ }
+
+ aaudio_input_preset_t getInputPreset() const {
+ return mInputPreset;
+ }
+
+ void setInputPreset(aaudio_input_preset_t inputPreset) {
+ mInputPreset = inputPreset;
+ }
+
int32_t getDeviceId() const {
return mDeviceId;
}
@@ -116,6 +140,9 @@
AAudioStreamBuilder_setDeviceId(builder, mDeviceId);
AAudioStreamBuilder_setSharingMode(builder, mSharingMode);
AAudioStreamBuilder_setPerformanceMode(builder, mPerformanceMode);
+ AAudioStreamBuilder_setUsage(builder, mUsage);
+ AAudioStreamBuilder_setContentType(builder, mContentType);
+ AAudioStreamBuilder_setInputPreset(builder, mInputPreset);
}
private:
@@ -128,6 +155,10 @@
aaudio_sharing_mode_t mSharingMode = AAUDIO_SHARING_MODE_SHARED;
aaudio_performance_mode_t mPerformanceMode = AAUDIO_PERFORMANCE_MODE_NONE;
+ aaudio_usage_t mUsage = AAUDIO_UNSPECIFIED;
+ aaudio_content_type_t mContentType = AAUDIO_UNSPECIFIED;
+ aaudio_input_preset_t mInputPreset = AAUDIO_UNSPECIFIED;
+
int32_t mNumberOfBursts = AAUDIO_UNSPECIFIED;
};
@@ -158,8 +189,8 @@
case 'd':
setDeviceId(atoi(&arg[2]));
break;
- case 's':
- mDurationSeconds = atoi(&arg[2]);
+ case 'i':
+ setInputPreset(atoi(&arg[2]));
break;
case 'm': {
aaudio_policy_t policy = AAUDIO_POLICY_AUTO;
@@ -177,9 +208,18 @@
case 'r':
setSampleRate(atoi(&arg[2]));
break;
+ case 's':
+ mDurationSeconds = atoi(&arg[2]);
+ break;
+ case 'u':
+ setUsage(atoi(&arg[2]));
+ break;
case 'x':
setSharingMode(AAUDIO_SHARING_MODE_EXCLUSIVE);
break;
+ case 'y':
+ setContentType(atoi(&arg[2]));
+ break;
default:
unrecognized = true;
break;
@@ -207,24 +247,28 @@
}
static void usage() {
- printf("-c{channels} -d{duration} -m -n{burstsPerBuffer} -p{perfMode} -r{rate} -x\n");
+ printf("-c{channels} -d{deviceId} -m{mmapPolicy} -n{burstsPerBuffer} -p{perfMode}");
+ printf(" -r{rate} -s{seconds} -x\n");
printf(" Default values are UNSPECIFIED unless otherwise stated.\n");
printf(" -b{bufferCapacity} frames\n");
printf(" -c{channels} for example 2 for stereo\n");
printf(" -d{deviceId} default is %d\n", AAUDIO_UNSPECIFIED);
- printf(" -s{duration} in seconds, default is %d\n", DEFAULT_DURATION_SECONDS);
+ printf(" -i{inputPreset} eg. 5 for AAUDIO_INPUT_PRESET_CAMCORDER\n");
printf(" -m{0|1|2|3} set MMAP policy\n");
- printf(" 0 = _UNSPECIFIED, default\n");
- printf(" 1 = _NEVER\n");
- printf(" 2 = _AUTO, also if -m is used with no number\n");
- printf(" 3 = _ALWAYS\n");
+ printf(" 0 = _UNSPECIFIED, use aaudio.mmap_policy system property, default\n");
+ printf(" 1 = _NEVER, never use MMAP\n");
+ printf(" 2 = _AUTO, use MMAP if available, default for -m with no number\n");
+ printf(" 3 = _ALWAYS, use MMAP or fail\n");
printf(" -n{numberOfBursts} for setBufferSize\n");
printf(" -p{performanceMode} set output AAUDIO_PERFORMANCE_MODE*, default NONE\n");
printf(" n for _NONE\n");
printf(" l for _LATENCY\n");
printf(" p for _POWER_SAVING;\n");
printf(" -r{sampleRate} for example 44100\n");
+ printf(" -s{duration} in seconds, default is %d\n", DEFAULT_DURATION_SECONDS);
+ printf(" -u{usage} eg. 14 for AAUDIO_USAGE_GAME\n");
printf(" -x to use EXCLUSIVE mode\n");
+ printf(" -y{contentType} eg. 1 for AAUDIO_CONTENT_TYPE_SPEECH\n");
}
static aaudio_performance_mode_t parsePerformanceMode(char c) {
@@ -287,6 +331,17 @@
printf(" PerformanceMode: requested = %d, actual = %d\n",
getPerformanceMode(), AAudioStream_getPerformanceMode(stream));
+
+ printf(" Usage: requested = %d, actual = %d\n",
+ getUsage(), AAudioStream_getUsage(stream));
+ printf(" ContentType: requested = %d, actual = %d\n",
+ getContentType(), AAudioStream_getContentType(stream));
+
+ if (AAudioStream_getDirection(stream) == AAUDIO_DIRECTION_INPUT) {
+ printf(" InputPreset: requested = %d, actual = %d\n",
+ getInputPreset(), AAudioStream_getInputPreset(stream));
+ }
+
printf(" Is MMAP used? %s\n", AAudioStream_isMMapUsed(stream)
? "yes" : "no");
diff --git a/media/libaaudio/examples/write_sine/src/write_sine_callback.cpp b/media/libaaudio/examples/write_sine/src/write_sine_callback.cpp
index 5d41fd0..e167773 100644
--- a/media/libaaudio/examples/write_sine/src/write_sine_callback.cpp
+++ b/media/libaaudio/examples/write_sine/src/write_sine_callback.cpp
@@ -204,7 +204,7 @@
AAudioArgsParser::usage();
printf(" -l{count} loopCount start/stop, every other one is silent\n");
printf(" -t{msec} play a high pitched tone at the beginning\n");
- printf(" -u force periodic Underruns by sleeping in callback\n");
+ printf(" -f force periodic underruns by sleeping in callback\n");
}
int main(int argc, const char **argv)
@@ -234,7 +234,7 @@
case 't':
prefixToneMsec = atoi(&arg[2]);
break;
- case 'u':
+ case 'f':
forceUnderruns = true;
break;
default:
diff --git a/media/libaaudio/include/aaudio/AAudio.h b/media/libaaudio/include/aaudio/AAudio.h
index 00c43dc..e40a6cd 100644
--- a/media/libaaudio/include/aaudio/AAudio.h
+++ b/media/libaaudio/include/aaudio/AAudio.h
@@ -123,17 +123,20 @@
/**
* No particular performance needs. Default.
*/
- AAUDIO_PERFORMANCE_MODE_NONE = 10,
+ AAUDIO_PERFORMANCE_MODE_NONE = 10,
/**
* Extending battery life is most important.
+ *
+ * This mode is not supported in input streams.
+ * Mode NONE will be used if this is requested.
*/
- AAUDIO_PERFORMANCE_MODE_POWER_SAVING,
+ AAUDIO_PERFORMANCE_MODE_POWER_SAVING,
/**
* Reducing latency is most important.
*/
- AAUDIO_PERFORMANCE_MODE_LOW_LATENCY
+ AAUDIO_PERFORMANCE_MODE_LOW_LATENCY
};
typedef int32_t aaudio_performance_mode_t;
@@ -280,6 +283,25 @@
};
typedef int32_t aaudio_input_preset_t;
+enum {
+ /**
+ * Do not allocate a session ID.
+ * Effects cannot be used with this stream.
+ * Default.
+ */
+ AAUDIO_SESSION_ID_NONE = -1,
+
+ /**
+ * Allocate a session ID that can be used to attach and control
+ * effects using the Java AudioEffects API.
+ * Note that the use of this flag may result in higher latency.
+ *
+ * Note that this matches the value of AudioManager.AUDIO_SESSION_ID_GENERATE.
+ */
+ AAUDIO_SESSION_ID_ALLOCATE = 0,
+};
+typedef int32_t aaudio_session_id_t;
+
typedef struct AAudioStreamStruct AAudioStream;
typedef struct AAudioStreamBuilderStruct AAudioStreamBuilder;
@@ -488,7 +510,9 @@
* This could, for example, affect which microphones are used and how the
* recorded data is processed.
*
- * The default, if you do not call this function, is AAUDIO_INPUT_PRESET_GENERIC.
+ * The default, if you do not call this function, is AAUDIO_INPUT_PRESET_VOICE_RECOGNITION.
+ * That is because VOICE_RECOGNITION is the preset with the lowest latency
+ * on many platforms.
*
* @param builder reference provided by AAudio_createStreamBuilder()
* @param inputPreset the desired configuration for recording
@@ -496,6 +520,32 @@
AAUDIO_API void AAudioStreamBuilder_setInputPreset(AAudioStreamBuilder* builder,
aaudio_input_preset_t inputPreset);
+/** Set the requested session ID.
+ *
+ * The session ID can be used to associate a stream with effects processors.
+ * The effects are controlled using the Android AudioEffect Java API.
+ *
+ * The default, if you do not call this function, is AAUDIO_SESSION_ID_NONE.
+ *
+ * If set to AAUDIO_SESSION_ID_ALLOCATE then a session ID will be allocated
+ * when the stream is opened.
+ *
+ * The allocated session ID can be obtained by calling AAudioStream_getSessionId()
+ * and then used with this function when opening another stream.
+ * This allows effects to be shared between streams.
+ *
+ * Session IDs from AAudio can be used the Android Java APIs and vice versa.
+ * So a session ID from an AAudio stream can be passed to Java
+ * and effects applied using the Java AudioEffect API.
+ *
+ * Allocated session IDs will always be positive and nonzero.
+ *
+ * @param builder reference provided by AAudio_createStreamBuilder()
+ * @param sessionId an allocated sessionID or AAUDIO_SESSION_ID_ALLOCATE
+ */
+AAUDIO_API void AAudioStreamBuilder_setSessionId(AAudioStreamBuilder* builder,
+ aaudio_session_id_t sessionId);
+
/**
* Return one of these values from the data callback function.
*/
@@ -526,7 +576,13 @@
* For an input stream, this function should read and process numFrames of data
* from the audioData buffer.
*
- * Note that this callback function should be considered a "real-time" function.
+ * The audio data is passed through the buffer. So do NOT call AAudioStream_read() or
+ * AAudioStream_write() on the stream that is making the callback.
+ *
+ * Note that numFrames can vary unless AAudioStreamBuilder_setFramesPerDataCallback()
+ * is called.
+ *
+ * Also note that this callback function should be considered a "real-time" function.
* It must not do anything that could cause an unbounded delay because that can cause the
* audio to glitch or pop.
*
@@ -537,6 +593,7 @@
* <li>any network operations such as streaming</li>
* <li>use any mutexes or other synchronization primitives</li>
* <li>sleep</li>
+ * <li>stop or close the stream</li>
* </ul>
*
* If you need to move data, eg. MIDI commands, in or out of the callback function then
@@ -545,7 +602,7 @@
* @param stream reference provided by AAudioStreamBuilder_openStream()
* @param userData the same address that was passed to AAudioStreamBuilder_setCallback()
* @param audioData a pointer to the audio data
- * @param numFrames the number of frames to be processed
+ * @param numFrames the number of frames to be processed, which can vary
* @return AAUDIO_CALLBACK_RESULT_*
*/
typedef aaudio_data_callback_result_t (*AAudioStream_dataCallback)(
@@ -620,18 +677,19 @@
aaudio_result_t error);
/**
- * Request that AAudio call this functions if any error occurs on a callback thread.
+ * Request that AAudio call this function if any error occurs or the stream is disconnected.
*
* It will be called, for example, if a headset or a USB device is unplugged causing the stream's
- * device to be unavailable.
- * In response, this function could signal or launch another thread to reopen a
- * stream on another device. Do not reopen the stream in this callback.
- *
- * This will not be called because of actions by the application, such as stopping
- * or closing a stream.
- *
+ * device to be unavailable or "disconnected".
* Another possible cause of error would be a timeout or an unanticipated internal error.
*
+ * In response, this function should signal or create another thread to stop
+ * and close this stream. The other thread could then reopen a stream on another device.
+ * Do not stop or close the stream, or reopen the new stream, directly from this callback.
+ *
+ * This callback will not be called because of actions by the application, such as stopping
+ * or closing a stream.
+ *
* Note that the AAudio callbacks will never be called simultaneously from multiple threads.
*
* @param builder reference provided by AAudio_createStreamBuilder()
@@ -743,11 +801,13 @@
* This will update the current client state.
*
* <pre><code>
- * aaudio_stream_state_t currentState;
- * aaudio_result_t result = AAudioStream_getState(stream, ¤tState);
- * while (result == AAUDIO_OK && currentState != AAUDIO_STREAM_STATE_PAUSING) {
+ * aaudio_result_t result = AAUDIO_OK;
+ * aaudio_stream_state_t currentState = AAudioStream_getState(stream);
+ * aaudio_stream_state_t inputState = currentState;
+ * while (result == AAUDIO_OK && currentState != AAUDIO_STREAM_STATE_PAUSED) {
* result = AAudioStream_waitForStateChange(
- * stream, currentState, ¤tState, MY_TIMEOUT_NANOS);
+ * stream, inputState, ¤tState, MY_TIMEOUT_NANOS);
+ * inputState = currentState;
* }
* </code></pre>
*
@@ -872,10 +932,10 @@
* This call can be used if the application needs to know the value of numFrames before
* the stream is started. This is not normally necessary.
*
- * If a specific size was requested by calling AAudioStreamBuilder_setCallbackSizeInFrames()
+ * If a specific size was requested by calling AAudioStreamBuilder_setFramesPerDataCallback()
* then this will be the same size.
*
- * If AAudioStreamBuilder_setCallbackSizeInFrames() was not called then this will
+ * If AAudioStreamBuilder_setFramesPerDataCallback() was not called then this will
* return the size chosen by AAudio, or AAUDIO_UNSPECIFIED.
*
* AAUDIO_UNSPECIFIED indicates that the callback buffer size for this stream
@@ -983,6 +1043,28 @@
AAUDIO_API int64_t AAudioStream_getFramesRead(AAudioStream* stream);
/**
+ * Passes back the session ID associated with this stream.
+ *
+ * The session ID can be used to associate a stream with effects processors.
+ * The effects are controlled using the Android AudioEffect Java API.
+ *
+ * If AAudioStreamBuilder_setSessionId() was called with AAUDIO_SESSION_ID_ALLOCATE
+ * then a new session ID should be allocated once when the stream is opened.
+ *
+ * If AAudioStreamBuilder_setSessionId() was called with a previously allocated
+ * session ID then that value should be returned.
+ *
+ * If AAudioStreamBuilder_setSessionId() was not called then this function should
+ * return AAUDIO_SESSION_ID_NONE.
+ *
+ * The sessionID for a stream should not change once the stream has been opened.
+ *
+ * @param stream reference provided by AAudioStreamBuilder_openStream()
+ * @return session ID or AAUDIO_SESSION_ID_NONE
+ */
+AAUDIO_API aaudio_session_id_t AAudioStream_getSessionId(AAudioStream* stream);
+
+/**
* Passes back the time at which a particular frame was presented.
* This can be used to synchronize audio with video or MIDI.
* It can also be used to align a recorded stream with a playback stream.
diff --git a/media/libaaudio/libaaudio.map.txt b/media/libaaudio/libaaudio.map.txt
index 98fbb6f..cbf5921 100644
--- a/media/libaaudio/libaaudio.map.txt
+++ b/media/libaaudio/libaaudio.map.txt
@@ -20,6 +20,7 @@
AAudioStreamBuilder_setUsage; # introduced=28
AAudioStreamBuilder_setContentType; # introduced=28
AAudioStreamBuilder_setInputPreset; # introduced=28
+ AAudioStreamBuilder_setSessionId; # introduced=28
AAudioStreamBuilder_openStream;
AAudioStreamBuilder_delete;
AAudioStream_close;
@@ -50,6 +51,7 @@
AAudioStream_getInputPreset; # introduced=28
AAudioStream_getFramesWritten;
AAudioStream_getFramesRead;
+ AAudioStream_getSessionId; # introduced=28
AAudioStream_getTimestamp;
AAudioStream_isMMapUsed;
local:
diff --git a/media/libaaudio/src/binding/AAudioServiceMessage.h b/media/libaaudio/src/binding/AAudioServiceMessage.h
index 9779f24..3981454 100644
--- a/media/libaaudio/src/binding/AAudioServiceMessage.h
+++ b/media/libaaudio/src/binding/AAudioServiceMessage.h
@@ -36,7 +36,6 @@
AAUDIO_SERVICE_EVENT_PAUSED,
AAUDIO_SERVICE_EVENT_STOPPED,
AAUDIO_SERVICE_EVENT_FLUSHED,
- AAUDIO_SERVICE_EVENT_CLOSED,
AAUDIO_SERVICE_EVENT_DISCONNECTED,
AAUDIO_SERVICE_EVENT_VOLUME,
AAUDIO_SERVICE_EVENT_XRUN
diff --git a/media/libaaudio/src/binding/AAudioStreamConfiguration.cpp b/media/libaaudio/src/binding/AAudioStreamConfiguration.cpp
index 97672a0..959db61 100644
--- a/media/libaaudio/src/binding/AAudioStreamConfiguration.cpp
+++ b/media/libaaudio/src/binding/AAudioStreamConfiguration.cpp
@@ -56,6 +56,8 @@
if (status != NO_ERROR) goto error;
status = parcel->writeInt32((int32_t) getInputPreset());
if (status != NO_ERROR) goto error;
+ status = parcel->writeInt32(getSessionId());
+ if (status != NO_ERROR) goto error;
return NO_ERROR;
error:
ALOGE("AAudioStreamConfiguration.writeToParcel(): write failed = %d", status);
@@ -94,6 +96,9 @@
status = parcel->readInt32(&value);
if (status != NO_ERROR) goto error;
setInputPreset((aaudio_input_preset_t) value);
+ status = parcel->readInt32(&value);
+ if (status != NO_ERROR) goto error;
+ setSessionId(value);
return NO_ERROR;
error:
ALOGE("AAudioStreamConfiguration.readFromParcel(): read failed = %d", status);
diff --git a/media/libaaudio/src/client/AudioStreamInternal.cpp b/media/libaaudio/src/client/AudioStreamInternal.cpp
index 5856ec3..b611160 100644
--- a/media/libaaudio/src/client/AudioStreamInternal.cpp
+++ b/media/libaaudio/src/client/AudioStreamInternal.cpp
@@ -133,6 +133,7 @@
setSampleRate(configurationOutput.getSampleRate());
setSamplesPerFrame(configurationOutput.getSamplesPerFrame());
setDeviceId(configurationOutput.getDeviceId());
+ setSessionId(configurationOutput.getSessionId());
setSharingMode(configurationOutput.getSharingMode());
setUsage(configurationOutput.getUsage());
@@ -339,8 +340,13 @@
}
}
-aaudio_result_t AudioStreamInternal::requestStopInternal()
+aaudio_result_t AudioStreamInternal::requestStop()
{
+ aaudio_result_t result = stopCallback();
+ if (result != AAUDIO_OK) {
+ return result;
+ }
+
if (mServiceStreamHandle == AAUDIO_HANDLE_INVALID) {
ALOGE("requestStopInternal() mServiceStreamHandle invalid = 0x%08X",
mServiceStreamHandle);
@@ -354,16 +360,6 @@
return mServiceInterface.stopStream(mServiceStreamHandle);
}
-aaudio_result_t AudioStreamInternal::requestStop()
-{
- aaudio_result_t result = stopCallback();
- if (result != AAUDIO_OK) {
- return result;
- }
- result = requestStopInternal();
- return result;
-}
-
aaudio_result_t AudioStreamInternal::registerThread() {
if (mServiceStreamHandle == AAUDIO_HANDLE_INVALID) {
ALOGE("registerThread() mServiceStreamHandle invalid");
@@ -482,10 +478,6 @@
onFlushFromServer();
}
break;
- case AAUDIO_SERVICE_EVENT_CLOSED:
- ALOGD("%s - got AAUDIO_SERVICE_EVENT_CLOSED", __func__);
- setState(AAUDIO_STREAM_STATE_CLOSED);
- break;
case AAUDIO_SERVICE_EVENT_DISCONNECTED:
// Prevent hardware from looping on old data and making buzzing sounds.
if (getDirection() == AAUDIO_DIRECTION_OUTPUT) {
diff --git a/media/libaaudio/src/client/AudioStreamInternal.h b/media/libaaudio/src/client/AudioStreamInternal.h
index 117756d..0f54f8c 100644
--- a/media/libaaudio/src/client/AudioStreamInternal.h
+++ b/media/libaaudio/src/client/AudioStreamInternal.h
@@ -121,8 +121,6 @@
aaudio_result_t processCommands();
- aaudio_result_t requestStopInternal();
-
aaudio_result_t stopCallback();
virtual void advanceClientToMatchServerPosition() = 0;
diff --git a/media/libaaudio/src/client/AudioStreamInternalPlay.cpp b/media/libaaudio/src/client/AudioStreamInternalPlay.cpp
index 5de6a11..5660c1b 100644
--- a/media/libaaudio/src/client/AudioStreamInternalPlay.cpp
+++ b/media/libaaudio/src/client/AudioStreamInternalPlay.cpp
@@ -38,9 +38,12 @@
AudioStreamInternalPlay::~AudioStreamInternalPlay() {}
-
-aaudio_result_t AudioStreamInternalPlay::requestPauseInternal()
+aaudio_result_t AudioStreamInternalPlay::requestPause()
{
+ aaudio_result_t result = stopCallback();
+ if (result != AAUDIO_OK) {
+ return result;
+ }
if (mServiceStreamHandle == AAUDIO_HANDLE_INVALID) {
ALOGE("AudioStreamInternal::requestPauseInternal() mServiceStreamHandle invalid = 0x%08X",
mServiceStreamHandle);
@@ -53,16 +56,6 @@
return mServiceInterface.pauseStream(mServiceStreamHandle);
}
-aaudio_result_t AudioStreamInternalPlay::requestPause()
-{
- aaudio_result_t result = stopCallback();
- if (result != AAUDIO_OK) {
- return result;
- }
- result = requestPauseInternal();
- return result;
-}
-
aaudio_result_t AudioStreamInternalPlay::requestFlush() {
if (mServiceStreamHandle == AAUDIO_HANDLE_INVALID) {
ALOGE("AudioStreamInternal::requestFlush() mServiceStreamHandle invalid = 0x%08X",
diff --git a/media/libaaudio/src/client/AudioStreamInternalPlay.h b/media/libaaudio/src/client/AudioStreamInternalPlay.h
index d5c1b1e..04e4a62 100644
--- a/media/libaaudio/src/client/AudioStreamInternalPlay.h
+++ b/media/libaaudio/src/client/AudioStreamInternalPlay.h
@@ -37,6 +37,16 @@
aaudio_result_t requestFlush() override;
+ bool isFlushSupported() const override {
+ // Only implement FLUSH for OUTPUT streams.
+ return true;
+ }
+
+ bool isPauseSupported() const override {
+ // Only implement PAUSE for OUTPUT streams.
+ return true;
+ }
+
aaudio_result_t write(const void *buffer,
int32_t numFrames,
int64_t timeoutNanoseconds) override;
@@ -52,8 +62,6 @@
protected:
- aaudio_result_t requestPauseInternal();
-
void advanceClientToMatchServerPosition() override;
void onFlushFromServer() override;
diff --git a/media/libaaudio/src/core/AAudioAudio.cpp b/media/libaaudio/src/core/AAudioAudio.cpp
index 9e5ca8e..df0db79 100644
--- a/media/libaaudio/src/core/AAudioAudio.cpp
+++ b/media/libaaudio/src/core/AAudioAudio.cpp
@@ -196,12 +196,19 @@
}
AAUDIO_API void AAudioStreamBuilder_setBufferCapacityInFrames(AAudioStreamBuilder* builder,
- int32_t frames)
+ int32_t frames)
{
AudioStreamBuilder *streamBuilder = convertAAudioBuilderToStreamBuilder(builder);
streamBuilder->setBufferCapacity(frames);
}
+AAUDIO_API void AAudioStreamBuilder_setSessionId(AAudioStreamBuilder* builder,
+ aaudio_session_id_t sessionId)
+{
+ AudioStreamBuilder *streamBuilder = convertAAudioBuilderToStreamBuilder(builder);
+ streamBuilder->setSessionId(sessionId);
+}
+
AAUDIO_API void AAudioStreamBuilder_setDataCallback(AAudioStreamBuilder* builder,
AAudioStream_dataCallback callback,
void *userData)
@@ -483,6 +490,12 @@
return audioStream->getInputPreset();
}
+AAUDIO_API int32_t AAudioStream_getSessionId(AAudioStream* stream)
+{
+ AudioStream *audioStream = convertAAudioStreamToAudioStream(stream);
+ return audioStream->getSessionId();
+}
+
AAUDIO_API int64_t AAudioStream_getFramesWritten(AAudioStream* stream)
{
AudioStream *audioStream = convertAAudioStreamToAudioStream(stream);
diff --git a/media/libaaudio/src/core/AAudioStreamParameters.cpp b/media/libaaudio/src/core/AAudioStreamParameters.cpp
index 23c4eb8..d56701b 100644
--- a/media/libaaudio/src/core/AAudioStreamParameters.cpp
+++ b/media/libaaudio/src/core/AAudioStreamParameters.cpp
@@ -17,7 +17,7 @@
#define LOG_TAG "AAudioStreamParameters"
#include <utils/Log.h>
-#include <hardware/audio.h>
+#include <system/audio.h>
#include "AAudioStreamParameters.h"
@@ -38,6 +38,7 @@
mSamplesPerFrame = other.mSamplesPerFrame;
mSampleRate = other.mSampleRate;
mDeviceId = other.mDeviceId;
+ mSessionId = other.mSessionId;
mSharingMode = other.mSharingMode;
mAudioFormat = other.mAudioFormat;
mDirection = other.mDirection;
@@ -59,6 +60,15 @@
return AAUDIO_ERROR_OUT_OF_RANGE;
}
+ // All Session ID values are legal.
+ switch (mSessionId) {
+ case AAUDIO_SESSION_ID_NONE:
+ case AAUDIO_SESSION_ID_ALLOCATE:
+ break;
+ default:
+ break;
+ }
+
switch (mSharingMode) {
case AAUDIO_SHARING_MODE_EXCLUSIVE:
case AAUDIO_SHARING_MODE_SHARED:
@@ -154,6 +164,7 @@
void AAudioStreamParameters::dump() const {
ALOGD("mDeviceId = %6d", mDeviceId);
+ ALOGD("mSessionId = %6d", mSessionId);
ALOGD("mSampleRate = %6d", mSampleRate);
ALOGD("mSamplesPerFrame = %6d", mSamplesPerFrame);
ALOGD("mSharingMode = %6d", (int)mSharingMode);
@@ -164,4 +175,3 @@
ALOGD("mContentType = %6d", mContentType);
ALOGD("mInputPreset = %6d", mInputPreset);
}
-
diff --git a/media/libaaudio/src/core/AAudioStreamParameters.h b/media/libaaudio/src/core/AAudioStreamParameters.h
index 0c173f5..ce5dacd 100644
--- a/media/libaaudio/src/core/AAudioStreamParameters.h
+++ b/media/libaaudio/src/core/AAudioStreamParameters.h
@@ -112,6 +112,14 @@
mInputPreset = inputPreset;
}
+ aaudio_session_id_t getSessionId() const {
+ return mSessionId;
+ }
+
+ void setSessionId(aaudio_session_id_t sessionId) {
+ mSessionId = sessionId;
+ }
+
int32_t calculateBytesPerFrame() const {
return getSamplesPerFrame() * AAudioConvert_formatToSizeInBytes(getFormat());
}
@@ -137,6 +145,7 @@
aaudio_content_type_t mContentType = AAUDIO_UNSPECIFIED;
aaudio_input_preset_t mInputPreset = AAUDIO_UNSPECIFIED;
int32_t mBufferCapacity = AAUDIO_UNSPECIFIED;
+ aaudio_session_id_t mSessionId = AAUDIO_SESSION_ID_NONE;
};
} /* namespace aaudio */
diff --git a/media/libaaudio/src/core/AudioStream.cpp b/media/libaaudio/src/core/AudioStream.cpp
index 1b2ca39..c4465fd 100644
--- a/media/libaaudio/src/core/AudioStream.cpp
+++ b/media/libaaudio/src/core/AudioStream.cpp
@@ -93,7 +93,7 @@
}
mInputPreset = builder.getInputPreset();
if (mInputPreset == AAUDIO_UNSPECIFIED) {
- mInputPreset = AAUDIO_INPUT_PRESET_GENERIC;
+ mInputPreset = AAUDIO_INPUT_PRESET_VOICE_RECOGNITION;
}
// callbacks
@@ -108,8 +108,10 @@
mSampleRate, mSamplesPerFrame, mFormat,
AudioStream_convertSharingModeToShortText(mSharingMode),
(getDirection() == AAUDIO_DIRECTION_OUTPUT) ? "OUTPUT" : "INPUT");
- ALOGI("open() device = %d, perfMode = %d, callback: %s with frames = %d",
- mDeviceId, mPerformanceMode,
+ ALOGI("open() device = %d, sessionId = %d, perfMode = %d, callback: %s with frames = %d",
+ mDeviceId,
+ mSessionId,
+ mPerformanceMode,
(isDataCallbackSet() ? "ON" : "OFF"),
mFramesPerDataCallback);
ALOGI("open() usage = %d, contentType = %d, inputPreset = %d",
@@ -128,29 +130,106 @@
}
aaudio_result_t AudioStream::safePause() {
+ if (!isPauseSupported()) {
+ return AAUDIO_ERROR_UNIMPLEMENTED;
+ }
+
std::lock_guard<std::mutex> lock(mStreamLock);
if (collidesWithCallback()) {
ALOGE("%s cannot be called from a callback!", __func__);
return AAUDIO_ERROR_INVALID_STATE;
}
+
+ switch (getState()) {
+ // Proceed with pausing.
+ case AAUDIO_STREAM_STATE_STARTING:
+ case AAUDIO_STREAM_STATE_STARTED:
+ case AAUDIO_STREAM_STATE_DISCONNECTED:
+ break;
+
+ // Transition from one inactive state to another.
+ case AAUDIO_STREAM_STATE_OPEN:
+ case AAUDIO_STREAM_STATE_STOPPED:
+ case AAUDIO_STREAM_STATE_FLUSHED:
+ setState(AAUDIO_STREAM_STATE_PAUSED);
+ return AAUDIO_OK;
+
+ // Redundant?
+ case AAUDIO_STREAM_STATE_PAUSING:
+ case AAUDIO_STREAM_STATE_PAUSED:
+ return AAUDIO_OK;
+
+ // Don't interfere with transitional states or when closed.
+ case AAUDIO_STREAM_STATE_STOPPING:
+ case AAUDIO_STREAM_STATE_FLUSHING:
+ case AAUDIO_STREAM_STATE_CLOSING:
+ case AAUDIO_STREAM_STATE_CLOSED:
+ default:
+ ALOGW("safePause() stream not running, state = %s",
+ AAudio_convertStreamStateToText(getState()));
+ return AAUDIO_ERROR_INVALID_STATE;
+ }
+
return requestPause();
}
aaudio_result_t AudioStream::safeFlush() {
+ if (!isFlushSupported()) {
+ ALOGE("flush not supported for this stream");
+ return AAUDIO_ERROR_UNIMPLEMENTED;
+ }
+
std::lock_guard<std::mutex> lock(mStreamLock);
if (collidesWithCallback()) {
- ALOGE("%s cannot be called from a callback!", __func__);
+ ALOGE("stream cannot be flushed from a callback!");
return AAUDIO_ERROR_INVALID_STATE;
}
+
+ aaudio_result_t result = AAudio_isFlushAllowed(getState());
+ if (result != AAUDIO_OK) {
+ return result;
+ }
+
return requestFlush();
}
aaudio_result_t AudioStream::safeStop() {
std::lock_guard<std::mutex> lock(mStreamLock);
if (collidesWithCallback()) {
- ALOGE("%s cannot be called from a callback!", __func__);
+ ALOGE("stream cannot be stopped from a callback!");
return AAUDIO_ERROR_INVALID_STATE;
}
+
+ switch (getState()) {
+ // Proceed with stopping.
+ case AAUDIO_STREAM_STATE_STARTING:
+ case AAUDIO_STREAM_STATE_STARTED:
+ case AAUDIO_STREAM_STATE_DISCONNECTED:
+ break;
+
+ // Transition from one inactive state to another.
+ case AAUDIO_STREAM_STATE_OPEN:
+ case AAUDIO_STREAM_STATE_PAUSED:
+ case AAUDIO_STREAM_STATE_FLUSHED:
+ setState(AAUDIO_STREAM_STATE_STOPPED);
+ return AAUDIO_OK;
+
+ // Redundant?
+ case AAUDIO_STREAM_STATE_STOPPING:
+ case AAUDIO_STREAM_STATE_STOPPED:
+ return AAUDIO_OK;
+
+ // Don't interfere with transitional states or when closed.
+ case AAUDIO_STREAM_STATE_PAUSING:
+ case AAUDIO_STREAM_STATE_FLUSHING:
+ case AAUDIO_STREAM_STATE_CLOSING:
+ case AAUDIO_STREAM_STATE_CLOSED:
+ default:
+ ALOGW("requestStop() stream not running, state = %s",
+ AAudio_convertStreamStateToText(getState()));
+ return AAUDIO_ERROR_INVALID_STATE;
+ }
+
return requestStop();
}
@@ -236,6 +315,7 @@
if (err != 0) {
return AAudioConvert_androidToAAudioResult(-errno);
} else {
+ // TODO Use AAudioThread or maybe AndroidThread
// Name the thread with an increasing index, "AAudio_#", for debugging.
static std::atomic<uint32_t> nextThreadIndex{1};
char name[16]; // max length for a pthread_name
diff --git a/media/libaaudio/src/core/AudioStream.h b/media/libaaudio/src/core/AudioStream.h
index 21fa595..c0db0f9 100644
--- a/media/libaaudio/src/core/AudioStream.h
+++ b/media/libaaudio/src/core/AudioStream.h
@@ -73,6 +73,24 @@
*/
virtual aaudio_result_t requestStart() = 0;
+ /**
+ * Check the state to see if Pause if currently legal.
+ *
+ * @param result pointer to return code
+ * @return true if OK to continue, if false then return result
+ */
+ bool checkPauseStateTransition(aaudio_result_t *result);
+
+ virtual bool isFlushSupported() const {
+ // Only implement FLUSH for OUTPUT streams.
+ return false;
+ }
+
+ virtual bool isPauseSupported() const {
+ // Only implement PAUSE for OUTPUT streams.
+ return false;
+ }
+
virtual aaudio_result_t requestPause()
{
// Only implement this for OUTPUT streams.
@@ -216,6 +234,10 @@
return mInputPreset;
}
+ int32_t getSessionId() const {
+ return mSessionId;
+ }
+
/**
* This is only valid after setSamplesPerFrame() and setFormat() have been called.
*/
@@ -337,11 +359,13 @@
return mPlayerBase->getResult();
}
+ // Pass pause request through PlayerBase for tracking.
aaudio_result_t systemPause() {
mPlayerBase->pause();
return mPlayerBase->getResult();
}
+ // Pass stop request through PlayerBase for tracking.
aaudio_result_t systemStop() {
mPlayerBase->stop();
return mPlayerBase->getResult();
@@ -448,17 +472,29 @@
}
void setState(aaudio_stream_state_t state) {
- mState = state;
+ if (mState == AAUDIO_STREAM_STATE_CLOSED) {
+ ; // CLOSED is a final state
+ } else if (mState == AAUDIO_STREAM_STATE_DISCONNECTED
+ && state != AAUDIO_STREAM_STATE_CLOSED) {
+ ; // Once DISCONNECTED, we can only move to CLOSED state.
+ } else {
+ mState = state;
+ }
}
void setDeviceId(int32_t deviceId) {
mDeviceId = deviceId;
}
+ void setSessionId(int32_t sessionId) {
+ mSessionId = sessionId;
+ }
+
std::atomic<bool> mCallbackEnabled{false};
float mDuckAndMuteVolume = 1.0f;
+
protected:
void setPeriodNanoseconds(int64_t periodNanoseconds) {
@@ -505,9 +541,12 @@
aaudio_format_t mFormat = AAUDIO_FORMAT_UNSPECIFIED;
aaudio_stream_state_t mState = AAUDIO_STREAM_STATE_UNINITIALIZED;
aaudio_performance_mode_t mPerformanceMode = AAUDIO_PERFORMANCE_MODE_NONE;
- aaudio_usage_t mUsage = AAUDIO_USAGE_MEDIA;
- aaudio_content_type_t mContentType = AAUDIO_CONTENT_TYPE_MUSIC;
- aaudio_input_preset_t mInputPreset = AAUDIO_INPUT_PRESET_GENERIC;
+
+ aaudio_usage_t mUsage = AAUDIO_UNSPECIFIED;
+ aaudio_content_type_t mContentType = AAUDIO_UNSPECIFIED;
+ aaudio_input_preset_t mInputPreset = AAUDIO_UNSPECIFIED;
+
+ int32_t mSessionId = AAUDIO_UNSPECIFIED;
// callback ----------------------------------
diff --git a/media/libaaudio/src/core/AudioStreamBuilder.cpp b/media/libaaudio/src/core/AudioStreamBuilder.cpp
index f7cb8d6..293a6a8 100644
--- a/media/libaaudio/src/core/AudioStreamBuilder.cpp
+++ b/media/libaaudio/src/core/AudioStreamBuilder.cpp
@@ -141,6 +141,13 @@
// TODO Support other performance settings in MMAP mode.
// Disable MMAP if low latency not requested.
if (getPerformanceMode() != AAUDIO_PERFORMANCE_MODE_LOW_LATENCY) {
+ ALOGD("build() MMAP not available because AAUDIO_PERFORMANCE_MODE_LOW_LATENCY not used.");
+ allowMMap = false;
+ }
+
+ // SessionID and Effects are only supported in Legacy mode.
+ if (getSessionId() != AAUDIO_SESSION_ID_NONE) {
+ ALOGD("build() MMAP not available because sessionId used.");
allowMMap = false;
}
diff --git a/media/libaaudio/src/legacy/AudioStreamLegacy.cpp b/media/libaaudio/src/legacy/AudioStreamLegacy.cpp
index c5dfb7c..3352b33 100644
--- a/media/libaaudio/src/legacy/AudioStreamLegacy.cpp
+++ b/media/libaaudio/src/legacy/AudioStreamLegacy.cpp
@@ -86,10 +86,15 @@
// AudioRecord::Buffer
// TODO define our own AudioBuffer and pass it from the subclasses.
AudioTrack::Buffer *audioBuffer = static_cast<AudioTrack::Buffer *>(info);
- if (getState() == AAUDIO_STREAM_STATE_DISCONNECTED || !mCallbackEnabled.load()) {
+ if (getState() == AAUDIO_STREAM_STATE_DISCONNECTED) {
+ ALOGW("processCallbackCommon() data, stream disconnected");
+ audioBuffer->size = SIZE_STOP_CALLBACKS;
+ } else if (!mCallbackEnabled.load()) {
+ ALOGW("processCallbackCommon() stopping because callback disabled");
audioBuffer->size = SIZE_STOP_CALLBACKS;
} else {
if (audioBuffer->frameCount == 0) {
+ ALOGW("processCallbackCommon() data, frameCount is zero");
return;
}
@@ -106,7 +111,7 @@
if (callbackResult == AAUDIO_CALLBACK_RESULT_CONTINUE) {
audioBuffer->size = audioBuffer->frameCount * getBytesPerFrame();
} else { // STOP or invalid result
- ALOGW("%s() stop stream by faking an error", __func__);
+ ALOGW("%s() callback requested stop, fake an error", __func__);
audioBuffer->size = SIZE_STOP_CALLBACKS;
// Disable the callback just in case AudioFlinger keeps trying to call us.
mCallbackEnabled.store(false);
diff --git a/media/libaaudio/src/legacy/AudioStreamLegacy.h b/media/libaaudio/src/legacy/AudioStreamLegacy.h
index 6a506b3..494edbc 100644
--- a/media/libaaudio/src/legacy/AudioStreamLegacy.h
+++ b/media/libaaudio/src/legacy/AudioStreamLegacy.h
@@ -121,9 +121,6 @@
void forceDisconnect(bool errorCallbackEnabled = true);
- void onStart() { mCallbackEnabled.store(true); }
- void onStop() { mCallbackEnabled.store(false); }
-
int64_t incrementFramesWritten(int32_t frames) {
return mFramesWritten.increment(frames);
}
diff --git a/media/libaaudio/src/legacy/AudioStreamRecord.cpp b/media/libaaudio/src/legacy/AudioStreamRecord.cpp
index 5f4ab9b..f7ae7d7 100644
--- a/media/libaaudio/src/legacy/AudioStreamRecord.cpp
+++ b/media/libaaudio/src/legacy/AudioStreamRecord.cpp
@@ -117,6 +117,9 @@
.tags = ""
};
+ aaudio_session_id_t requestedSessionId = builder.getSessionId();
+ audio_session_t sessionId = AAudioConvert_aaudioToAndroidSessionId(requestedSessionId);
+
mAudioRecord = new AudioRecord(
mOpPackageName // const String16& opPackageName TODO does not compile
);
@@ -130,7 +133,7 @@
callbackData,
notificationFrames,
false /*threadCanCallJava*/,
- AUDIO_SESSION_ALLOCATE,
+ sessionId,
streamTransferType,
flags,
AUDIO_UID_INVALID, // DEFAULT uid
@@ -189,6 +192,13 @@
setState(AAUDIO_STREAM_STATE_OPEN);
setDeviceId(mAudioRecord->getRoutedDeviceId());
+
+ aaudio_session_id_t actualSessionId =
+ (requestedSessionId == AAUDIO_SESSION_ID_NONE)
+ ? AAUDIO_SESSION_ID_NONE
+ : (aaudio_session_id_t) mAudioRecord->getSessionId();
+ setSessionId(actualSessionId);
+
mAudioRecord->addAudioDeviceCallback(mDeviceCallback);
return AAUDIO_OK;
@@ -234,11 +244,13 @@
return AAudioConvert_androidToAAudioResult(err);
}
+ // Enable callback before starting AudioTrack to avoid shutting
+ // down because of a race condition.
+ mCallbackEnabled.store(true);
err = mAudioRecord->start();
if (err != OK) {
return AAudioConvert_androidToAAudioResult(err);
} else {
- onStart();
setState(AAUDIO_STREAM_STATE_STARTING);
}
return AAUDIO_OK;
@@ -248,11 +260,11 @@
if (mAudioRecord.get() == nullptr) {
return AAUDIO_ERROR_INVALID_STATE;
}
- onStop();
setState(AAUDIO_STREAM_STATE_STOPPING);
incrementFramesWritten(getFramesRead() - getFramesWritten()); // TODO review
mTimestampPosition.set(getFramesRead());
mAudioRecord->stop();
+ mCallbackEnabled.store(false);
mFramesRead.reset32();
mTimestampPosition.reset32();
// Pass false to prevent errorCallback from being called after disconnect
diff --git a/media/libaaudio/src/legacy/AudioStreamTrack.cpp b/media/libaaudio/src/legacy/AudioStreamTrack.cpp
index 17a8d52..ee069ee 100644
--- a/media/libaaudio/src/legacy/AudioStreamTrack.cpp
+++ b/media/libaaudio/src/legacy/AudioStreamTrack.cpp
@@ -134,6 +134,11 @@
.tags = ""
};
+ static_assert(AAUDIO_UNSPECIFIED == AUDIO_SESSION_ALLOCATE, "Session IDs should match");
+
+ aaudio_session_id_t requestedSessionId = builder.getSessionId();
+ audio_session_t sessionId = AAudioConvert_aaudioToAndroidSessionId(requestedSessionId);
+
mAudioTrack = new AudioTrack();
mAudioTrack->set(
AUDIO_STREAM_DEFAULT, // ignored because we pass attributes below
@@ -147,7 +152,7 @@
notificationFrames,
0, // DEFAULT sharedBuffer*/,
false, // DEFAULT threadCanCallJava
- AUDIO_SESSION_ALLOCATE,
+ sessionId,
streamTransferType,
NULL, // DEFAULT audio_offload_info_t
AUDIO_UID_INVALID, // DEFAULT uid
@@ -193,6 +198,13 @@
setState(AAUDIO_STREAM_STATE_OPEN);
setDeviceId(mAudioTrack->getRoutedDeviceId());
+
+ aaudio_session_id_t actualSessionId =
+ (requestedSessionId == AAUDIO_SESSION_ID_NONE)
+ ? AAUDIO_SESSION_ID_NONE
+ : (aaudio_session_id_t) mAudioTrack->getSessionId();
+ setSessionId(actualSessionId);
+
mAudioTrack->addAudioDeviceCallback(mDeviceCallback);
// Update performance mode based on the actual stream flags.
@@ -259,11 +271,13 @@
return AAudioConvert_androidToAAudioResult(err);
}
+ // Enable callback before starting AudioTrack to avoid shutting
+ // down because of a race condition.
+ mCallbackEnabled.store(true);
err = mAudioTrack->start();
if (err != OK) {
return AAudioConvert_androidToAAudioResult(err);
} else {
- onStart();
setState(AAUDIO_STREAM_STATE_STARTING);
}
return AAUDIO_OK;
@@ -273,16 +287,11 @@
if (mAudioTrack.get() == nullptr) {
ALOGE("requestPause() no AudioTrack");
return AAUDIO_ERROR_INVALID_STATE;
- } else if (getState() != AAUDIO_STREAM_STATE_STARTING
- && getState() != AAUDIO_STREAM_STATE_STARTED) {
- // TODO What about DISCONNECTED?
- ALOGE("requestPause(), called when state is %s",
- AAudio_convertStreamStateToText(getState()));
- return AAUDIO_ERROR_INVALID_STATE;
}
- onStop();
+
setState(AAUDIO_STREAM_STATE_PAUSING);
mAudioTrack->pause();
+ mCallbackEnabled.store(false);
status_t err = mAudioTrack->getPosition(&mPositionWhenPausing);
if (err != OK) {
return AAudioConvert_androidToAAudioResult(err);
@@ -294,10 +303,8 @@
if (mAudioTrack.get() == nullptr) {
ALOGE("requestFlush() no AudioTrack");
return AAUDIO_ERROR_INVALID_STATE;
- } else if (getState() != AAUDIO_STREAM_STATE_PAUSED) {
- ALOGE("requestFlush() not paused");
- return AAUDIO_ERROR_INVALID_STATE;
}
+
setState(AAUDIO_STREAM_STATE_FLUSHING);
incrementFramesRead(getFramesWritten() - getFramesRead());
mAudioTrack->flush();
@@ -311,13 +318,14 @@
ALOGE("requestStop() no AudioTrack");
return AAUDIO_ERROR_INVALID_STATE;
}
- onStop();
+
setState(AAUDIO_STREAM_STATE_STOPPING);
incrementFramesRead(getFramesWritten() - getFramesRead()); // TODO review
mTimestampPosition.set(getFramesWritten());
mFramesWritten.reset32();
mTimestampPosition.reset32();
mAudioTrack->stop();
+ mCallbackEnabled.store(false);
return checkForDisconnectRequest(false);;
}
diff --git a/media/libaaudio/src/legacy/AudioStreamTrack.h b/media/libaaudio/src/legacy/AudioStreamTrack.h
index a871db4..68608de 100644
--- a/media/libaaudio/src/legacy/AudioStreamTrack.h
+++ b/media/libaaudio/src/legacy/AudioStreamTrack.h
@@ -48,6 +48,16 @@
aaudio_result_t requestFlush() override;
aaudio_result_t requestStop() override;
+ bool isFlushSupported() const override {
+ // Only implement FLUSH for OUTPUT streams.
+ return true;
+ }
+
+ bool isPauseSupported() const override {
+ // Only implement PAUSE for OUTPUT streams.
+ return true;
+ }
+
aaudio_result_t getTimestamp(clockid_t clockId,
int64_t *framePosition,
int64_t *timeNanoseconds) override;
diff --git a/media/libaaudio/src/utility/AAudioUtilities.cpp b/media/libaaudio/src/utility/AAudioUtilities.cpp
index c6adf33..adc4904 100644
--- a/media/libaaudio/src/utility/AAudioUtilities.cpp
+++ b/media/libaaudio/src/utility/AAudioUtilities.cpp
@@ -250,6 +250,13 @@
return result;
}
+audio_session_t AAudioConvert_aaudioToAndroidSessionId(aaudio_session_id_t sessionId) {
+ // If not a valid sessionId then convert to a safe value of AUDIO_SESSION_ALLOCATE.
+ return (sessionId < AAUDIO_SESSION_ID_MIN)
+ ? AUDIO_SESSION_ALLOCATE
+ : (audio_session_t) sessionId;
+}
+
audio_format_t AAudioConvert_aaudioToAndroidDataFormat(aaudio_format_t aaudioFormat) {
audio_format_t androidFormat;
switch (aaudioFormat) {
@@ -334,7 +341,7 @@
STATIC_ASSERT(AAUDIO_INPUT_PRESET_VOICE_COMMUNICATION == AUDIO_SOURCE_VOICE_COMMUNICATION);
STATIC_ASSERT(AAUDIO_INPUT_PRESET_UNPROCESSED == AUDIO_SOURCE_UNPROCESSED);
if (preset == AAUDIO_UNSPECIFIED) {
- preset = AAUDIO_INPUT_PRESET_GENERIC;
+ preset = AAUDIO_INPUT_PRESET_VOICE_RECOGNITION;
}
return (audio_source_t) preset; // same value
}
@@ -434,3 +441,31 @@
}
return prop;
}
+
+aaudio_result_t AAudio_isFlushAllowed(aaudio_stream_state_t state) {
+ aaudio_result_t result = AAUDIO_OK;
+ switch (state) {
+// Proceed with flushing.
+ case AAUDIO_STREAM_STATE_OPEN:
+ case AAUDIO_STREAM_STATE_PAUSED:
+ case AAUDIO_STREAM_STATE_STOPPED:
+ case AAUDIO_STREAM_STATE_FLUSHED:
+ break;
+
+// Transition from one inactive state to another.
+ case AAUDIO_STREAM_STATE_STARTING:
+ case AAUDIO_STREAM_STATE_STARTED:
+ case AAUDIO_STREAM_STATE_STOPPING:
+ case AAUDIO_STREAM_STATE_PAUSING:
+ case AAUDIO_STREAM_STATE_FLUSHING:
+ case AAUDIO_STREAM_STATE_CLOSING:
+ case AAUDIO_STREAM_STATE_CLOSED:
+ case AAUDIO_STREAM_STATE_DISCONNECTED:
+ default:
+ ALOGE("can only flush stream when PAUSED, OPEN or STOPPED, state = %s",
+ AAudio_convertStreamStateToText(state));
+ result = AAUDIO_ERROR_INVALID_STATE;
+ break;
+ }
+ return result;
+}
diff --git a/media/libaaudio/src/utility/AAudioUtilities.h b/media/libaaudio/src/utility/AAudioUtilities.h
index f2347f5..3673c34 100644
--- a/media/libaaudio/src/utility/AAudioUtilities.h
+++ b/media/libaaudio/src/utility/AAudioUtilities.h
@@ -23,10 +23,13 @@
#include <sys/types.h>
#include <utils/Errors.h>
-#include <hardware/audio.h>
+#include <system/audio.h>
#include "aaudio/AAudio.h"
+
+constexpr aaudio_session_id_t AAUDIO_SESSION_ID_MIN = 1; // must be positive
+
/**
* Convert an AAudio result into the closest matching Android status.
*/
@@ -38,6 +41,13 @@
aaudio_result_t AAudioConvert_androidToAAudioResult(android::status_t status);
/**
+ * Convert an aaudio_session_id_t to a value that is safe to pass to AudioFlinger.
+ * @param sessionId
+ * @return safe value
+ */
+audio_session_t AAudioConvert_aaudioToAndroidSessionId(aaudio_session_id_t sessionId);
+
+/**
* Convert an array of floats to an array of int16_t.
*
* @param source
@@ -258,6 +268,14 @@
*/
int32_t AAudioProperty_getHardwareBurstMinMicros();
+
+/**
+ * Is flush allowed for the given state?
+ * @param state
+ * @return AAUDIO_OK if allowed or an error
+ */
+aaudio_result_t AAudio_isFlushAllowed(aaudio_stream_state_t state);
+
/**
* Try a function f until it returns true.
*
diff --git a/media/libaaudio/tests/Android.bp b/media/libaaudio/tests/Android.bp
index 33718fc..45d417f 100644
--- a/media/libaaudio/tests/Android.bp
+++ b/media/libaaudio/tests/Android.bp
@@ -113,6 +113,18 @@
}
cc_test {
+ name: "test_session_id",
+ defaults: ["libaaudio_tests_defaults"],
+ srcs: ["test_session_id.cpp"],
+ shared_libs: [
+ "libaaudio",
+ "libbinder",
+ "libcutils",
+ "libutils",
+ ],
+}
+
+cc_test {
name: "test_aaudio_monkey",
defaults: ["libaaudio_tests_defaults"],
srcs: ["test_aaudio_monkey.cpp"],
diff --git a/media/libaaudio/tests/test_attributes.cpp b/media/libaaudio/tests/test_attributes.cpp
index 9cbf113..b01af25 100644
--- a/media/libaaudio/tests/test_attributes.cpp
+++ b/media/libaaudio/tests/test_attributes.cpp
@@ -76,7 +76,7 @@
aaudio_input_preset_t expectedPreset =
(preset == DONT_SET || preset == AAUDIO_UNSPECIFIED)
- ? AAUDIO_INPUT_PRESET_GENERIC // default
+ ? AAUDIO_INPUT_PRESET_VOICE_RECOGNITION // default
: preset;
EXPECT_EQ(expectedPreset, AAudioStream_getInputPreset(aaudioStream));
diff --git a/media/libaaudio/tests/test_session_id.cpp b/media/libaaudio/tests/test_session_id.cpp
new file mode 100644
index 0000000..d9072af
--- /dev/null
+++ b/media/libaaudio/tests/test_session_id.cpp
@@ -0,0 +1,163 @@
+/*
+ * 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.
+ */
+
+// Test AAudio SessionId, which is used to associate Effects with a stream
+
+#include <stdio.h>
+#include <unistd.h>
+
+#include <aaudio/AAudio.h>
+#include <gtest/gtest.h>
+
+constexpr int64_t kNanosPerSecond = 1000000000;
+constexpr int kNumFrames = 256;
+constexpr int kChannelCount = 2;
+
+// Test AAUDIO_SESSION_ID_NONE default
+static void checkSessionIdNone(aaudio_performance_mode_t perfMode) {
+
+ float *buffer = new float[kNumFrames * kChannelCount];
+
+ AAudioStreamBuilder *aaudioBuilder = nullptr;
+
+ AAudioStream *aaudioStream1 = nullptr;
+ int32_t sessionId1 = 0;
+
+ // Use an AAudioStreamBuilder to contain requested parameters.
+ ASSERT_EQ(AAUDIO_OK, AAudio_createStreamBuilder(&aaudioBuilder));
+
+ // Request stream properties.
+ AAudioStreamBuilder_setPerformanceMode(aaudioBuilder, perfMode);
+
+ // Create an AAudioStream using the Builder.
+ ASSERT_EQ(AAUDIO_OK, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream1));
+
+ // Since we did not request or specify a SessionID, we should get NONE
+ sessionId1 = AAudioStream_getSessionId(aaudioStream1);
+ ASSERT_EQ(AAUDIO_SESSION_ID_NONE, sessionId1);
+
+ ASSERT_EQ(AAUDIO_OK, AAudioStream_requestStart(aaudioStream1));
+
+ ASSERT_EQ(kNumFrames, AAudioStream_write(aaudioStream1, buffer, kNumFrames, kNanosPerSecond));
+
+ EXPECT_EQ(AAUDIO_OK, AAudioStream_requestStop(aaudioStream1));
+
+ EXPECT_EQ(AAUDIO_OK, AAudioStream_close(aaudioStream1));
+ delete[] buffer;
+ AAudioStreamBuilder_delete(aaudioBuilder);
+}
+
+TEST(test_session_id, aaudio_session_id_none_perfnone) {
+ checkSessionIdNone(AAUDIO_PERFORMANCE_MODE_NONE);
+}
+
+TEST(test_session_id, aaudio_session_id_none_lowlat) {
+ checkSessionIdNone(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
+}
+
+// Test AAUDIO_SESSION_ID_ALLOCATE
+static void checkSessionIdAllocate(aaudio_performance_mode_t perfMode,
+ aaudio_direction_t direction) {
+
+ float *buffer = new float[kNumFrames * kChannelCount];
+
+ AAudioStreamBuilder *aaudioBuilder = nullptr;
+
+ AAudioStream *aaudioStream1 = nullptr;
+ int32_t sessionId1 = 0;
+ AAudioStream *aaudioStream2 = nullptr;
+ int32_t sessionId2 = 0;
+
+ // Use an AAudioStreamBuilder to contain requested parameters.
+ ASSERT_EQ(AAUDIO_OK, AAudio_createStreamBuilder(&aaudioBuilder));
+
+ // Request stream properties.
+ AAudioStreamBuilder_setPerformanceMode(aaudioBuilder, perfMode);
+ // This stream could be input or output.
+ AAudioStreamBuilder_setDirection(aaudioBuilder, direction);
+
+ // Ask AAudio to allocate a Session ID.
+ AAudioStreamBuilder_setSessionId(aaudioBuilder, AAUDIO_SESSION_ID_ALLOCATE);
+
+ // Create an AAudioStream using the Builder.
+ ASSERT_EQ(AAUDIO_OK, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream1));
+
+ // Get the allocated ID from the stream.
+ sessionId1 = AAudioStream_getSessionId(aaudioStream1);
+ ASSERT_LT(0, sessionId1); // Must be positive.
+
+ ASSERT_EQ(AAUDIO_OK, AAudioStream_requestStart(aaudioStream1));
+
+ if (direction == AAUDIO_DIRECTION_INPUT) {
+ ASSERT_EQ(kNumFrames, AAudioStream_read(aaudioStream1,
+ buffer, kNumFrames, kNanosPerSecond));
+ } else {
+ ASSERT_EQ(kNumFrames, AAudioStream_write(aaudioStream1,
+ buffer, kNumFrames, kNanosPerSecond));
+ }
+
+ EXPECT_EQ(AAUDIO_OK, AAudioStream_requestStop(aaudioStream1));
+
+ // Now open a second stream using the same session ID. ==================
+ AAudioStreamBuilder_setSessionId(aaudioBuilder, sessionId1);
+
+ // Reverse direction for second stream.
+ aaudio_direction_t otherDirection = (direction == AAUDIO_DIRECTION_OUTPUT)
+ ? AAUDIO_DIRECTION_INPUT
+ : AAUDIO_DIRECTION_OUTPUT;
+ AAudioStreamBuilder_setDirection(aaudioBuilder, otherDirection);
+
+ // Create an AAudioStream using the Builder.
+ ASSERT_EQ(AAUDIO_OK, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream2));
+
+ // Get the allocated ID from the stream.
+ // It should match the ID that we set it to in the builder.
+ sessionId2 = AAudioStream_getSessionId(aaudioStream2);
+ ASSERT_EQ(sessionId1, sessionId2);
+
+ ASSERT_EQ(AAUDIO_OK, AAudioStream_requestStart(aaudioStream2));
+
+ if (otherDirection == AAUDIO_DIRECTION_INPUT) {
+ ASSERT_EQ(kNumFrames, AAudioStream_read(aaudioStream2,
+ buffer, kNumFrames, kNanosPerSecond));
+ } else {
+ ASSERT_EQ(kNumFrames, AAudioStream_write(aaudioStream2,
+ buffer, kNumFrames, kNanosPerSecond));
+ }
+
+ EXPECT_EQ(AAUDIO_OK, AAudioStream_requestStop(aaudioStream2));
+
+ EXPECT_EQ(AAUDIO_OK, AAudioStream_close(aaudioStream2));
+
+
+ EXPECT_EQ(AAUDIO_OK, AAudioStream_close(aaudioStream1));
+ delete[] buffer;
+ AAudioStreamBuilder_delete(aaudioBuilder);
+}
+
+TEST(test_session_id, aaudio_session_id_alloc_perfnone_in) {
+ checkSessionIdAllocate(AAUDIO_PERFORMANCE_MODE_NONE, AAUDIO_DIRECTION_INPUT);
+}
+TEST(test_session_id, aaudio_session_id_alloc_perfnone_out) {
+ checkSessionIdAllocate(AAUDIO_PERFORMANCE_MODE_NONE, AAUDIO_DIRECTION_OUTPUT);
+}
+
+TEST(test_session_id, aaudio_session_id_alloc_lowlat_in) {
+ checkSessionIdAllocate(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, AAUDIO_DIRECTION_INPUT);
+}
+TEST(test_session_id, aaudio_session_id_alloc_lowlat_out) {
+ checkSessionIdAllocate(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, AAUDIO_DIRECTION_OUTPUT);
+}
diff --git a/media/libaaudio/tests/test_various.cpp b/media/libaaudio/tests/test_various.cpp
index dc19985..4b065c9 100644
--- a/media/libaaudio/tests/test_various.cpp
+++ b/media/libaaudio/tests/test_various.cpp
@@ -26,7 +26,6 @@
#include <gtest/gtest.h>
#include <unistd.h>
-
// Callback function that does nothing.
aaudio_data_callback_result_t NoopDataCallbackProc(
AAudioStream *stream,
@@ -45,73 +44,376 @@
constexpr int64_t NANOS_PER_MILLISECOND = 1000 * 1000;
-//int foo() { // To fix Android Studio formatting when editing.
-TEST(test_various, aaudio_stop_when_open) {
+enum FunctionToCall {
+ CALL_START, CALL_STOP, CALL_PAUSE, CALL_FLUSH
+};
+
+void checkStateTransition(aaudio_performance_mode_t perfMode,
+ aaudio_stream_state_t originalState,
+ FunctionToCall functionToCall,
+ aaudio_result_t expectedResult,
+ aaudio_stream_state_t expectedState) {
AAudioStreamBuilder *aaudioBuilder = nullptr;
AAudioStream *aaudioStream = nullptr;
-// Use an AAudioStreamBuilder to contain requested parameters.
+ // Use an AAudioStreamBuilder to contain requested parameters.
ASSERT_EQ(AAUDIO_OK, AAudio_createStreamBuilder(&aaudioBuilder));
-// Request stream properties.
+ // Request stream properties.
AAudioStreamBuilder_setDataCallback(aaudioBuilder, NoopDataCallbackProc, nullptr);
- AAudioStreamBuilder_setPerformanceMode(aaudioBuilder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
+ AAudioStreamBuilder_setPerformanceMode(aaudioBuilder, perfMode);
-// Create an AAudioStream using the Builder.
- EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream));
+ // Create an AAudioStream using the Builder.
+ ASSERT_EQ(AAUDIO_OK, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream));
-
+ // Verify Open State
aaudio_stream_state_t state = AAUDIO_STREAM_STATE_UNKNOWN;
EXPECT_EQ(AAUDIO_OK, AAudioStream_waitForStateChange(aaudioStream,
AAUDIO_STREAM_STATE_UNKNOWN, &state,
1000 * NANOS_PER_MILLISECOND));
EXPECT_EQ(AAUDIO_STREAM_STATE_OPEN, state);
- EXPECT_EQ(AAUDIO_OK, AAudioStream_requestStop(aaudioStream));
+ // Put stream into desired state.
+ aaudio_stream_state_t inputState = AAUDIO_STREAM_STATE_UNINITIALIZED;
+ if (originalState != AAUDIO_STREAM_STATE_OPEN) {
- state = AAUDIO_STREAM_STATE_UNKNOWN;
+ ASSERT_EQ(AAUDIO_OK, AAudioStream_requestStart(aaudioStream));
+
+ if (originalState != AAUDIO_STREAM_STATE_STARTING) {
+
+ ASSERT_EQ(AAUDIO_OK, AAudioStream_waitForStateChange(aaudioStream,
+ AAUDIO_STREAM_STATE_STARTING,
+ &state,
+ 1000 * NANOS_PER_MILLISECOND));
+ ASSERT_EQ(AAUDIO_STREAM_STATE_STARTED, state);
+
+ if (originalState == AAUDIO_STREAM_STATE_STOPPING) {
+ ASSERT_EQ(AAUDIO_OK, AAudioStream_requestStop(aaudioStream));
+ } else if (originalState == AAUDIO_STREAM_STATE_STOPPED) {
+ ASSERT_EQ(AAUDIO_OK, AAudioStream_requestStop(aaudioStream));
+ inputState = AAUDIO_STREAM_STATE_STOPPING;
+ } else if (originalState == AAUDIO_STREAM_STATE_PAUSING) {
+ ASSERT_EQ(AAUDIO_OK, AAudioStream_requestPause(aaudioStream));
+ } else if (originalState == AAUDIO_STREAM_STATE_PAUSED) {
+ ASSERT_EQ(AAUDIO_OK, AAudioStream_requestPause(aaudioStream));
+ inputState = AAUDIO_STREAM_STATE_PAUSING;
+ }
+ }
+ }
+
+ // Wait until past transitional state.
+ if (inputState != AAUDIO_STREAM_STATE_UNINITIALIZED) {
+ ASSERT_EQ(AAUDIO_OK, AAudioStream_waitForStateChange(aaudioStream,
+ inputState,
+ &state,
+ 1000 * NANOS_PER_MILLISECOND));
+ ASSERT_EQ(originalState, state);
+ }
+
+ aaudio_stream_state_t transitionalState = originalState;
+ switch(functionToCall) {
+ case FunctionToCall::CALL_START:
+ EXPECT_EQ(expectedResult, AAudioStream_requestStart(aaudioStream));
+ transitionalState = AAUDIO_STREAM_STATE_STARTING;
+ break;
+ case FunctionToCall::CALL_STOP:
+ EXPECT_EQ(expectedResult, AAudioStream_requestStop(aaudioStream));
+ transitionalState = AAUDIO_STREAM_STATE_STOPPING;
+ break;
+ case FunctionToCall::CALL_PAUSE:
+ EXPECT_EQ(expectedResult, AAudioStream_requestPause(aaudioStream));
+ transitionalState = AAUDIO_STREAM_STATE_PAUSING;
+ break;
+ case FunctionToCall::CALL_FLUSH:
+ EXPECT_EQ(expectedResult, AAudioStream_requestFlush(aaudioStream));
+ transitionalState = AAUDIO_STREAM_STATE_FLUSHING;
+ break;
+ }
+
EXPECT_EQ(AAUDIO_OK, AAudioStream_waitForStateChange(aaudioStream,
- AAUDIO_STREAM_STATE_UNKNOWN, &state, 0));
- EXPECT_EQ(AAUDIO_STREAM_STATE_OPEN, state);
-
- AAudioStream_close(aaudioStream);
- AAudioStreamBuilder_delete(aaudioBuilder);
-}
-
-//int boo() { // To fix Android Studio formatting when editing.
-TEST(test_various, aaudio_flush_when_started) {
- AAudioStreamBuilder *aaudioBuilder = nullptr;
- AAudioStream *aaudioStream = nullptr;
-
-// Use an AAudioStreamBuilder to contain requested parameters.
- ASSERT_EQ(AAUDIO_OK, AAudio_createStreamBuilder(&aaudioBuilder));
-
-// Request stream properties.
- AAudioStreamBuilder_setDataCallback(aaudioBuilder, NoopDataCallbackProc, nullptr);
- AAudioStreamBuilder_setPerformanceMode(aaudioBuilder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
-
-// Create an AAudioStream using the Builder.
- EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream));
- EXPECT_EQ(AAUDIO_OK, AAudioStream_requestStart(aaudioStream));
-
- aaudio_stream_state_t state = AAUDIO_STREAM_STATE_UNKNOWN;
- EXPECT_EQ(AAUDIO_OK, AAudioStream_waitForStateChange(aaudioStream,
- AAUDIO_STREAM_STATE_STARTING, &state,
+ transitionalState,
+ &state,
1000 * NANOS_PER_MILLISECOND));
- EXPECT_EQ(AAUDIO_STREAM_STATE_STARTED, state);
-
- EXPECT_EQ(AAUDIO_ERROR_INVALID_STATE, AAudioStream_requestFlush(aaudioStream));
-
- state = AAUDIO_STREAM_STATE_UNKNOWN;
- EXPECT_EQ(AAUDIO_OK, AAudioStream_waitForStateChange(aaudioStream,
- AAUDIO_STREAM_STATE_UNKNOWN, &state, 0));
- EXPECT_EQ(AAUDIO_STREAM_STATE_STARTED, state);
+ // We should not change state when a function fails.
+ if (expectedResult != AAUDIO_OK) {
+ ASSERT_EQ(originalState, expectedState);
+ }
+ EXPECT_EQ(expectedState, state);
+ if (state != expectedState) {
+ printf("ERROR - expected %s, actual = %s\n",
+ AAudio_convertStreamStateToText(expectedState),
+ AAudio_convertStreamStateToText(state));
+ fflush(stdout);
+ }
AAudioStream_close(aaudioStream);
AAudioStreamBuilder_delete(aaudioBuilder);
}
-//int main() { // To fix Android Studio formatting when editing.
+// TODO Use parameterized tests instead of these individual specific tests.
+
+// OPEN =================================================================
+TEST(test_various, aaudio_state_lowlat_open_start) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+ AAUDIO_STREAM_STATE_OPEN,
+ FunctionToCall::CALL_START,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_STARTED);
+}
+
+TEST(test_various, aaudio_state_none_open_start) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_STREAM_STATE_OPEN,
+ FunctionToCall::CALL_START,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_STARTED);
+}
+
+TEST(test_various, aaudio_state_lowlat_open_stop) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+ AAUDIO_STREAM_STATE_OPEN,
+ FunctionToCall::CALL_STOP,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_STOPPED);
+}
+
+TEST(test_various, aaudio_state_none_open_stop) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_STREAM_STATE_OPEN,
+ FunctionToCall::CALL_STOP,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_STOPPED);
+}
+
+TEST(test_various, aaudio_state_lowlat_open_pause) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+ AAUDIO_STREAM_STATE_OPEN,
+ FunctionToCall::CALL_PAUSE,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_PAUSED);
+}
+
+TEST(test_various, aaudio_state_none_open_pause) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_STREAM_STATE_OPEN,
+ FunctionToCall::CALL_PAUSE,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_PAUSED);
+}
+
+TEST(test_various, aaudio_state_lowlat_open_flush) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+ AAUDIO_STREAM_STATE_OPEN,
+ FunctionToCall::CALL_FLUSH,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_FLUSHED);
+}
+
+TEST(test_various, aaudio_state_none_open_flush) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_STREAM_STATE_OPEN,
+ FunctionToCall::CALL_FLUSH,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_FLUSHED);
+}
+
+
+// STARTED =================================================================
+TEST(test_various, aaudio_state_lowlat_started_start) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+ AAUDIO_STREAM_STATE_STARTED,
+ FunctionToCall::CALL_START,
+ AAUDIO_ERROR_INVALID_STATE,
+ AAUDIO_STREAM_STATE_STARTED);
+}
+
+TEST(test_various, aaudio_state_none_started_start) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_STREAM_STATE_STARTED,
+ FunctionToCall::CALL_START,
+ AAUDIO_ERROR_INVALID_STATE,
+ AAUDIO_STREAM_STATE_STARTED);
+}
+
+TEST(test_various, aaudio_state_lowlat_started_stop) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+ AAUDIO_STREAM_STATE_STARTED,
+ FunctionToCall::CALL_STOP,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_STOPPED);
+}
+
+TEST(test_various, aaudio_state_none_started_stop) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_STREAM_STATE_STARTED,
+ FunctionToCall::CALL_STOP,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_STOPPED);
+}
+
+TEST(test_various, aaudio_state_lowlat_started_pause) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+ AAUDIO_STREAM_STATE_STARTED,
+ FunctionToCall::CALL_PAUSE,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_PAUSED);
+}
+
+TEST(test_various, aaudio_state_none_started_pause) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_STREAM_STATE_STARTED,
+ FunctionToCall::CALL_PAUSE,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_PAUSED);
+}
+
+TEST(test_various, aaudio_state_lowlat_started_flush) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+ AAUDIO_STREAM_STATE_STARTED,
+ FunctionToCall::CALL_FLUSH,
+ AAUDIO_ERROR_INVALID_STATE,
+ AAUDIO_STREAM_STATE_STARTED);
+}
+
+TEST(test_various, aaudio_state_none_started_flush) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_STREAM_STATE_STARTED,
+ FunctionToCall::CALL_FLUSH,
+ AAUDIO_ERROR_INVALID_STATE,
+ AAUDIO_STREAM_STATE_STARTED);
+}
+
+// STOPPED =================================================================
+TEST(test_various, aaudio_state_lowlat_stopped_start) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+ AAUDIO_STREAM_STATE_STOPPED,
+ FunctionToCall::CALL_START,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_STARTED);
+}
+
+TEST(test_various, aaudio_state_none_stopped_start) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_STREAM_STATE_STOPPED,
+ FunctionToCall::CALL_START,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_STARTED);
+}
+
+TEST(test_various, aaudio_state_lowlat_stopped_stop) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+ AAUDIO_STREAM_STATE_STOPPED,
+ FunctionToCall::CALL_STOP,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_STOPPED);
+}
+
+TEST(test_various, aaudio_state_none_stopped_stop) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_STREAM_STATE_STOPPED,
+ FunctionToCall::CALL_STOP,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_STOPPED);
+}
+
+TEST(test_various, aaudio_state_lowlat_stopped_pause) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+ AAUDIO_STREAM_STATE_STOPPED,
+ FunctionToCall::CALL_PAUSE,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_PAUSED);
+}
+
+TEST(test_various, aaudio_state_none_stopped_pause) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_STREAM_STATE_STOPPED,
+ FunctionToCall::CALL_PAUSE,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_PAUSED);
+}
+
+TEST(test_various, aaudio_state_lowlat_stopped_flush) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+ AAUDIO_STREAM_STATE_STOPPED,
+ FunctionToCall::CALL_FLUSH,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_FLUSHED);
+}
+
+TEST(test_various, aaudio_state_none_stopped_flush) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_STREAM_STATE_STOPPED,
+ FunctionToCall::CALL_FLUSH,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_FLUSHED);
+}
+
+// PAUSED =================================================================
+TEST(test_various, aaudio_state_lowlat_paused_start) {
+checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+ AAUDIO_STREAM_STATE_PAUSED,
+ FunctionToCall::CALL_START,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_STARTED);
+}
+
+TEST(test_various, aaudio_state_none_paused_start) {
+checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_STREAM_STATE_PAUSED,
+ FunctionToCall::CALL_START,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_STARTED);
+}
+
+TEST(test_various, aaudio_state_lowlat_paused_stop) {
+checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+ AAUDIO_STREAM_STATE_PAUSED,
+ FunctionToCall::CALL_STOP,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_STOPPED);
+}
+
+TEST(test_various, aaudio_state_none_paused_stop) {
+checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_STREAM_STATE_PAUSED,
+ FunctionToCall::CALL_STOP,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_STOPPED);
+}
+
+TEST(test_various, aaudio_state_lowlat_paused_pause) {
+checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+ AAUDIO_STREAM_STATE_PAUSED,
+ FunctionToCall::CALL_PAUSE,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_PAUSED);
+}
+
+TEST(test_various, aaudio_state_none_paused_pause) {
+checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_STREAM_STATE_PAUSED,
+ FunctionToCall::CALL_PAUSE,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_PAUSED);
+}
+
+TEST(test_various, aaudio_state_lowlat_paused_flush) {
+checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+ AAUDIO_STREAM_STATE_PAUSED,
+ FunctionToCall::CALL_FLUSH,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_FLUSHED);
+}
+
+TEST(test_various, aaudio_state_none_paused_flush) {
+checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_STREAM_STATE_PAUSED,
+ FunctionToCall::CALL_FLUSH,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_FLUSHED);
+}
+
+// ==========================================================================
TEST(test_various, aaudio_set_buffer_size) {
int32_t bufferCapacity;
@@ -174,7 +476,6 @@
AAudioStreamBuilder_delete(aaudioBuilder);
}
-
// ************************************************************
// Test to make sure that AAUDIO_CALLBACK_RESULT_STOP works.
diff --git a/media/libaudioclient/AudioRecord.cpp b/media/libaudioclient/AudioRecord.cpp
index bc294c5..3aebb8a 100644
--- a/media/libaudioclient/AudioRecord.cpp
+++ b/media/libaudioclient/AudioRecord.cpp
@@ -77,22 +77,47 @@
return rawbuffer;
}
+static std::string audioSourceString(audio_source_t value) {
+ std::string source;
+ if (SourceTypeConverter::toString(value, source)) {
+ return source;
+ }
+ char rawbuffer[16]; // room for "%d"
+ snprintf(rawbuffer, sizeof(rawbuffer), "%d", value);
+ return rawbuffer;
+}
+
void AudioRecord::MediaMetrics::gather(const AudioRecord *record)
{
// key for media statistics is defined in the header
// attrs for media statistics
static constexpr char kAudioRecordChannelCount[] = "android.media.audiorecord.channels";
- static constexpr char kAudioRecordFormat[] = "android.media.audiorecord.format";
+ static constexpr char kAudioRecordEncoding[] = "android.media.audiorecord.encoding";
static constexpr char kAudioRecordLatency[] = "android.media.audiorecord.latency";
static constexpr char kAudioRecordSampleRate[] = "android.media.audiorecord.samplerate";
+ static constexpr char kAudioRecordSource[] = "android.media.audiotrack.source";
// constructor guarantees mAnalyticsItem is valid
mAnalyticsItem->setInt32(kAudioRecordLatency, record->mLatency);
mAnalyticsItem->setInt32(kAudioRecordSampleRate, record->mSampleRate);
mAnalyticsItem->setInt32(kAudioRecordChannelCount, record->mChannelCount);
- mAnalyticsItem->setCString(kAudioRecordFormat,
+ mAnalyticsItem->setCString(kAudioRecordEncoding,
audioFormatTypeString(record->mFormat).c_str());
+ mAnalyticsItem->setCString(kAudioRecordSource,
+ audioSourceString(record->mAttributes.source).c_str());
+}
+
+// hand the user a snapshot of the metrics.
+status_t AudioRecord::getMetrics(MediaAnalyticsItem * &item)
+{
+ mMediaMetrics.gather(this);
+ MediaAnalyticsItem *tmp = mMediaMetrics.dup();
+ if (tmp == nullptr) {
+ return BAD_VALUE;
+ }
+ item = tmp;
+ return NO_ERROR;
}
AudioRecord::AudioRecord(const String16 &opPackageName)
diff --git a/media/libaudioclient/AudioSystem.cpp b/media/libaudioclient/AudioSystem.cpp
index 50fe385..7783ad3 100644
--- a/media/libaudioclient/AudioSystem.cpp
+++ b/media/libaudioclient/AudioSystem.cpp
@@ -39,8 +39,7 @@
sp<AudioSystem::AudioFlingerClient> AudioSystem::gAudioFlingerClient;
audio_error_callback AudioSystem::gAudioErrorCallback = NULL;
dynamic_policy_callback AudioSystem::gDynPolicyCallback = NULL;
-record_config_callback AudioSystem::gRecordConfigCallback = NULL;
-
+record_config_callback AudioSystem::gRecordConfigCallback = NULL;
// establish binder interface to AudioFlinger service
const sp<IAudioFlinger> AudioSystem::get_audio_flinger()
@@ -859,6 +858,7 @@
audio_io_handle_t *output,
audio_session_t session,
audio_stream_type_t *stream,
+ pid_t pid,
uid_t uid,
const audio_config_t *config,
audio_output_flags_t flags,
@@ -867,7 +867,7 @@
{
const sp<IAudioPolicyService>& aps = AudioSystem::get_audio_policy_service();
if (aps == 0) return NO_INIT;
- return aps->getOutputForAttr(attr, output, session, stream, uid,
+ return aps->getOutputForAttr(attr, output, session, stream, pid, uid,
config,
flags, selectedDeviceId, portId);
}
@@ -904,6 +904,7 @@
audio_session_t session,
pid_t pid,
uid_t uid,
+ const String16& opPackageName,
const audio_config_base_t *config,
audio_input_flags_t flags,
audio_port_handle_t *selectedDeviceId,
@@ -912,32 +913,29 @@
const sp<IAudioPolicyService>& aps = AudioSystem::get_audio_policy_service();
if (aps == 0) return NO_INIT;
return aps->getInputForAttr(
- attr, input, session, pid, uid,
+ attr, input, session, pid, uid, opPackageName,
config, flags, selectedDeviceId, portId);
}
-status_t AudioSystem::startInput(audio_io_handle_t input,
- audio_session_t session)
+status_t AudioSystem::startInput(audio_port_handle_t portId, bool *silenced)
{
const sp<IAudioPolicyService>& aps = AudioSystem::get_audio_policy_service();
if (aps == 0) return PERMISSION_DENIED;
- return aps->startInput(input, session);
+ return aps->startInput(portId, silenced);
}
-status_t AudioSystem::stopInput(audio_io_handle_t input,
- audio_session_t session)
+status_t AudioSystem::stopInput(audio_port_handle_t portId)
{
const sp<IAudioPolicyService>& aps = AudioSystem::get_audio_policy_service();
if (aps == 0) return PERMISSION_DENIED;
- return aps->stopInput(input, session);
+ return aps->stopInput(portId);
}
-void AudioSystem::releaseInput(audio_io_handle_t input,
- audio_session_t session)
+void AudioSystem::releaseInput(audio_port_handle_t portId)
{
const sp<IAudioPolicyService>& aps = AudioSystem::get_audio_policy_service();
if (aps == 0) return;
- aps->releaseInput(input, session);
+ aps->releaseInput(portId);
}
status_t AudioSystem::initStreamVolume(audio_stream_type_t stream,
@@ -1056,11 +1054,11 @@
return af->getPrimaryOutputFrameCount();
}
-status_t AudioSystem::setLowRamDevice(bool isLowRamDevice)
+status_t AudioSystem::setLowRamDevice(bool isLowRamDevice, int64_t totalMemory)
{
const sp<IAudioFlinger>& af = AudioSystem::get_audio_flinger();
if (af == 0) return PERMISSION_DENIED;
- return af->setLowRamDevice(isLowRamDevice);
+ return af->setLowRamDevice(isLowRamDevice, totalMemory);
}
void AudioSystem::clearAudioConfigCache()
diff --git a/media/libaudioclient/AudioTrack.cpp b/media/libaudioclient/AudioTrack.cpp
index a3c66fe..7dd3f29 100644
--- a/media/libaudioclient/AudioTrack.cpp
+++ b/media/libaudioclient/AudioTrack.cpp
@@ -189,22 +189,23 @@
static constexpr char kAudioTrackUsage[] = "android.media.audiotrack.usage";
static constexpr char kAudioTrackSampleRate[] = "android.media.audiotrack.samplerate";
static constexpr char kAudioTrackChannelMask[] = "android.media.audiotrack.channelmask";
-#if 0
- // XXX: disabled temporarily for b/72027185
static constexpr char kAudioTrackUnderrunFrames[] = "android.media.audiotrack.underrunframes";
-#endif
static constexpr char kAudioTrackStartupGlitch[] = "android.media.audiotrack.glitch.startup";
+ // only if we're in a good state...
+ // XXX: shall we gather alternative info if failing?
+ const status_t lstatus = track->initCheck();
+ if (lstatus != NO_ERROR) {
+ ALOGD("no metrics gathered, track status=%d", (int) lstatus);
+ return;
+ }
+
// constructor guarantees mAnalyticsItem is valid
-#if 0
- // XXX: disabled temporarily for b/72027185
- // must gather underrun info before cleaning mProxy information.
const int32_t underrunFrames = track->getUnderrunFrames();
if (underrunFrames != 0) {
mAnalyticsItem->setInt32(kAudioTrackUnderrunFrames, underrunFrames);
}
-#endif
if (track->mTimestampStartupGlitchReported) {
mAnalyticsItem->setInt32(kAudioTrackStartupGlitch, 1);
@@ -223,6 +224,17 @@
mAnalyticsItem->setInt64(kAudioTrackChannelMask, track->mChannelMask);
}
+// hand the user a snapshot of the metrics.
+status_t AudioTrack::getMetrics(MediaAnalyticsItem * &item)
+{
+ mMediaMetrics.gather(this);
+ MediaAnalyticsItem *tmp = mMediaMetrics.dup();
+ if (tmp == nullptr) {
+ return BAD_VALUE;
+ }
+ item = tmp;
+ return NO_ERROR;
+}
AudioTrack::AudioTrack()
: mStatus(NO_INIT),
diff --git a/media/libaudioclient/IAudioFlinger.cpp b/media/libaudioclient/IAudioFlinger.cpp
index 56ddd4f..ae9c96f 100644
--- a/media/libaudioclient/IAudioFlinger.cpp
+++ b/media/libaudioclient/IAudioFlinger.cpp
@@ -48,6 +48,7 @@
SET_MODE,
SET_MIC_MUTE,
GET_MIC_MUTE,
+ SET_RECORD_SILENCED,
SET_PARAMETERS,
GET_PARAMETERS,
REGISTER_CLIENT,
@@ -306,6 +307,15 @@
return reply.readInt32();
}
+ virtual void setRecordSilenced(uid_t uid, bool silenced)
+ {
+ Parcel data, reply;
+ data.writeInterfaceToken(IAudioFlinger::getInterfaceDescriptor());
+ data.writeInt32(uid);
+ data.writeInt32(silenced ? 1 : 0);
+ remote()->transact(SET_RECORD_SILENCED, data, &reply);
+ }
+
virtual status_t setParameters(audio_io_handle_t ioHandle, const String8& keyValuePairs)
{
Parcel data, reply;
@@ -697,14 +707,18 @@
return reply.readInt64();
}
- virtual status_t setLowRamDevice(bool isLowRamDevice)
+ virtual status_t setLowRamDevice(bool isLowRamDevice, int64_t totalMemory) override
{
Parcel data, reply;
- data.writeInterfaceToken(IAudioFlinger::getInterfaceDescriptor());
- data.writeInt32((int) isLowRamDevice);
- remote()->transact(SET_LOW_RAM_DEVICE, data, &reply);
- return reply.readInt32();
+
+ static_assert(NO_ERROR == 0, "NO_ERROR must be 0");
+ return data.writeInterfaceToken(IAudioFlinger::getInterfaceDescriptor())
+ ?: data.writeInt32((int) isLowRamDevice)
+ ?: data.writeInt64(totalMemory)
+ ?: remote()->transact(SET_LOW_RAM_DEVICE, data, &reply)
+ ?: reply.readInt32();
}
+
virtual status_t listAudioPorts(unsigned int *num_ports,
struct audio_port *ports)
{
@@ -859,6 +873,7 @@
case RELEASE_AUDIO_PATCH:
case LIST_AUDIO_PATCHES:
case SET_AUDIO_PORT_CONFIG:
+ case SET_RECORD_SILENCED:
ALOGW("%s: transaction %d received from PID %d",
__func__, code, IPCThreadState::self()->getCallingPid());
return INVALID_OPERATION;
@@ -1024,6 +1039,15 @@
reply->writeInt32( getMicMute() );
return NO_ERROR;
} break;
+ case SET_RECORD_SILENCED: {
+ CHECK_INTERFACE(IAudioFlinger, data, reply);
+ uid_t uid = data.readInt32();
+ audio_source_t source;
+ data.read(&source, sizeof(audio_source_t));
+ bool silenced = data.readInt32() == 1;
+ setRecordSilenced(uid, silenced);
+ return NO_ERROR;
+ } break;
case SET_PARAMETERS: {
CHECK_INTERFACE(IAudioFlinger, data, reply);
audio_io_handle_t ioHandle = (audio_io_handle_t) data.readInt32();
@@ -1261,8 +1285,13 @@
} break;
case SET_LOW_RAM_DEVICE: {
CHECK_INTERFACE(IAudioFlinger, data, reply);
- bool isLowRamDevice = data.readInt32() != 0;
- reply->writeInt32(setLowRamDevice(isLowRamDevice));
+ int32_t isLowRamDevice;
+ int64_t totalMemory;
+ const status_t status =
+ data.readInt32(&isLowRamDevice) ?:
+ data.readInt64(&totalMemory) ?:
+ setLowRamDevice(isLowRamDevice != 0, totalMemory);
+ (void)reply->writeInt32(status);
return NO_ERROR;
} break;
case LIST_AUDIO_PORTS: {
diff --git a/media/libaudioclient/IAudioPolicyService.cpp b/media/libaudioclient/IAudioPolicyService.cpp
index 53bc1b7..b91e4cf 100644
--- a/media/libaudioclient/IAudioPolicyService.cpp
+++ b/media/libaudioclient/IAudioPolicyService.cpp
@@ -174,6 +174,7 @@
audio_io_handle_t *output,
audio_session_t session,
audio_stream_type_t *stream,
+ pid_t pid,
uid_t uid,
const audio_config_t *config,
audio_output_flags_t flags,
@@ -217,6 +218,7 @@
data.writeInt32(1);
data.writeInt32(*stream);
}
+ data.writeInt32(pid);
data.writeInt32(uid);
data.write(config, sizeof(audio_config_t));
data.writeInt32(static_cast <uint32_t>(flags));
@@ -283,6 +285,7 @@
audio_session_t session,
pid_t pid,
uid_t uid,
+ const String16& opPackageName,
const audio_config_base_t *config,
audio_input_flags_t flags,
audio_port_handle_t *selectedDeviceId,
@@ -311,6 +314,7 @@
data.writeInt32(session);
data.writeInt32(pid);
data.writeInt32(uid);
+ data.writeString16(opPackageName);
data.write(config, sizeof(audio_config_base_t));
data.writeInt32(flags);
data.writeInt32(*selectedDeviceId);
@@ -329,35 +333,33 @@
return NO_ERROR;
}
- virtual status_t startInput(audio_io_handle_t input,
- audio_session_t session)
+ virtual status_t startInput(audio_port_handle_t portId,
+ bool *silenced)
{
Parcel data, reply;
data.writeInterfaceToken(IAudioPolicyService::getInterfaceDescriptor());
- data.writeInt32(input);
- data.writeInt32(session);
+ data.writeInt32(portId);
+ data.writeInt32(*silenced ? 1 : 0);
remote()->transact(START_INPUT, data, &reply);
- return static_cast <status_t> (reply.readInt32());
+ status_t status = static_cast <status_t> (reply.readInt32());
+ *silenced = reply.readInt32() == 1;
+ return status;
}
- virtual status_t stopInput(audio_io_handle_t input,
- audio_session_t session)
+ virtual status_t stopInput(audio_port_handle_t portId)
{
Parcel data, reply;
data.writeInterfaceToken(IAudioPolicyService::getInterfaceDescriptor());
- data.writeInt32(input);
- data.writeInt32(session);
+ data.writeInt32(portId);
remote()->transact(STOP_INPUT, data, &reply);
return static_cast <status_t> (reply.readInt32());
}
- virtual void releaseInput(audio_io_handle_t input,
- audio_session_t session)
+ virtual void releaseInput(audio_port_handle_t portId)
{
Parcel data, reply;
data.writeInterfaceToken(IAudioPolicyService::getInterfaceDescriptor());
- data.writeInt32(input);
- data.writeInt32(session);
+ data.writeInt32(portId);
remote()->transact(RELEASE_INPUT, data, &reply);
}
@@ -960,6 +962,7 @@
if (hasStream) {
stream = (audio_stream_type_t)data.readInt32();
}
+ pid_t pid = (pid_t)data.readInt32();
uid_t uid = (uid_t)data.readInt32();
audio_config_t config;
memset(&config, 0, sizeof(audio_config_t));
@@ -970,7 +973,7 @@
audio_port_handle_t portId = (audio_port_handle_t)data.readInt32();
audio_io_handle_t output = 0;
status_t status = getOutputForAttr(hasAttributes ? &attr : NULL,
- &output, session, &stream, uid,
+ &output, session, &stream, pid, uid,
&config,
flags, &selectedDeviceId, &portId);
reply->writeInt32(status);
@@ -1023,6 +1026,7 @@
audio_session_t session = (audio_session_t)data.readInt32();
pid_t pid = (pid_t)data.readInt32();
uid_t uid = (uid_t)data.readInt32();
+ const String16 opPackageName = data.readString16();
audio_config_base_t config;
memset(&config, 0, sizeof(audio_config_base_t));
data.read(&config, sizeof(audio_config_base_t));
@@ -1030,7 +1034,7 @@
audio_port_handle_t selectedDeviceId = (audio_port_handle_t) data.readInt32();
audio_port_handle_t portId = (audio_port_handle_t)data.readInt32();
status_t status = getInputForAttr(&attr, &input, session, pid, uid,
- &config,
+ opPackageName, &config,
flags, &selectedDeviceId, &portId);
reply->writeInt32(status);
if (status == NO_ERROR) {
@@ -1043,25 +1047,25 @@
case START_INPUT: {
CHECK_INTERFACE(IAudioPolicyService, data, reply);
- audio_io_handle_t input = static_cast <audio_io_handle_t>(data.readInt32());
- audio_session_t session = static_cast <audio_session_t>(data.readInt32());
- reply->writeInt32(static_cast <uint32_t>(startInput(input, session)));
+ audio_port_handle_t portId = static_cast <audio_port_handle_t>(data.readInt32());
+ bool silenced = data.readInt32() == 1;
+ status_t status = startInput(portId, &silenced);
+ reply->writeInt32(static_cast <uint32_t>(status));
+ reply->writeInt32(silenced ? 1 : 0);
return NO_ERROR;
} break;
case STOP_INPUT: {
CHECK_INTERFACE(IAudioPolicyService, data, reply);
- audio_io_handle_t input = static_cast <audio_io_handle_t>(data.readInt32());
- audio_session_t session = static_cast <audio_session_t>(data.readInt32());
- reply->writeInt32(static_cast <uint32_t>(stopInput(input, session)));
+ audio_port_handle_t portId = static_cast <audio_port_handle_t>(data.readInt32());
+ reply->writeInt32(static_cast <uint32_t>(stopInput(portId)));
return NO_ERROR;
} break;
case RELEASE_INPUT: {
CHECK_INTERFACE(IAudioPolicyService, data, reply);
- audio_io_handle_t input = static_cast <audio_io_handle_t>(data.readInt32());
- audio_session_t session = static_cast <audio_session_t>(data.readInt32());
- releaseInput(input, session);
+ audio_port_handle_t portId = static_cast <audio_port_handle_t>(data.readInt32());
+ releaseInput(portId);
return NO_ERROR;
} break;
diff --git a/media/libaudioclient/include/media/AudioMixer.h b/media/libaudioclient/include/media/AudioMixer.h
index d4ce417..2e29316 100644
--- a/media/libaudioclient/include/media/AudioMixer.h
+++ b/media/libaudioclient/include/media/AudioMixer.h
@@ -18,8 +18,11 @@
#ifndef ANDROID_AUDIO_MIXER_H
#define ANDROID_AUDIO_MIXER_H
+#include <pthread.h>
+#include <sstream>
#include <stdint.h>
#include <sys/types.h>
+#include <unordered_map>
#include <media/AudioBufferProvider.h>
#include <media/AudioResampler.h>
@@ -43,20 +46,14 @@
class AudioMixer
{
public:
- AudioMixer(size_t frameCount, uint32_t sampleRate,
- uint32_t maxNumTracks = MAX_NUM_TRACKS);
+ // This mixer has a hard-coded upper limit of active track inputs;
+ // the value is arbitrary but should be less than TRACK0 to avoid confusion.
+ static constexpr int32_t MAX_NUM_TRACKS = 256;
- /*virtual*/ ~AudioMixer(); // non-virtual saves a v-table, restore if sub-classed
-
-
- // This mixer has a hard-coded upper limit of 32 active track inputs.
- // Adding support for > 32 tracks would require more than simply changing this value.
- static const uint32_t MAX_NUM_TRACKS = 32;
- // maximum number of channels supported by the mixer
-
+ // Do not change these unless underlying code changes.
// This mixer has a hard-coded upper limit of 8 channels for output.
- static const uint32_t MAX_NUM_CHANNELS = 8;
- static const uint32_t MAX_NUM_VOLUMES = 2; // stereo volume only
+ static constexpr uint32_t MAX_NUM_CHANNELS = FCC_8;
+ static constexpr uint32_t MAX_NUM_VOLUMES = FCC_2; // stereo volume only
// maximum number of channels supported for the content
static const uint32_t MAX_NUM_CHANNELS_TO_DOWNMIX = AUDIO_CHANNEL_COUNT_MAX;
@@ -108,6 +105,12 @@
// parameter 'value' is a pointer to the new playback rate.
};
+ AudioMixer(size_t frameCount, uint32_t sampleRate, int32_t maxNumTracks = MAX_NUM_TRACKS)
+ : mMaxNumTracks(maxNumTracks)
+ , mSampleRate(sampleRate)
+ , mFrameCount(frameCount) {
+ pthread_once(&sOnceControl, &sInitRoutine);
+ }
// For all APIs with "name": TRACK0 <= name < TRACK0 + MAX_NUM_TRACKS
@@ -127,27 +130,38 @@
void setParameter(int name, int target, int param, void *value);
void setBufferProvider(int name, AudioBufferProvider* bufferProvider);
- void process();
- uint32_t trackNames() const { return mTrackNames; }
+ void process() {
+ (this->*mHook)();
+ }
size_t getUnreleasedFrames(int name) const;
- static inline bool isValidPcmTrackFormat(audio_format_t format) {
- switch (format) {
- case AUDIO_FORMAT_PCM_8_BIT:
- case AUDIO_FORMAT_PCM_16_BIT:
- case AUDIO_FORMAT_PCM_24_BIT_PACKED:
- case AUDIO_FORMAT_PCM_32_BIT:
- case AUDIO_FORMAT_PCM_FLOAT:
- return true;
- default:
- return false;
+ std::string trackNames() const {
+ std::stringstream ss;
+ for (const auto &pair : mTracks) {
+ ss << pair.first << " ";
}
+ return ss.str();
+ }
+
+ void setNBLogWriter(NBLog::Writer *logWriter) {
+ mNBLogWriter = logWriter;
}
private:
+ /* For multi-format functions (calls template functions
+ * in AudioMixerOps.h). The template parameters are as follows:
+ *
+ * MIXTYPE (see AudioMixerOps.h MIXTYPE_* enumeration)
+ * USEFLOATVOL (set to true if float volume is used)
+ * ADJUSTVOL (set to true if volume ramp parameters needs adjustment afterwards)
+ * TO: int32_t (Q4.27) or float
+ * TI: int32_t (Q4.27) or int16_t (Q0.15) or float
+ * TA: int32_t (Q4.27)
+ */
+
enum {
// FIXME this representation permits up to 8 channels
NEEDS_CHANNEL_COUNT__MASK = 0x00000007,
@@ -164,14 +178,67 @@
NEEDS_AUX = 0x00010000,
};
- struct state_t;
- struct track_t;
+ // hook types
+ enum {
+ PROCESSTYPE_NORESAMPLEONETRACK, // others set elsewhere
+ };
- typedef void (*hook_t)(track_t* t, int32_t* output, size_t numOutFrames, int32_t* temp,
- int32_t* aux);
- static const int BLOCKSIZE = 16; // 4 cache lines
+ enum {
+ TRACKTYPE_NOP,
+ TRACKTYPE_RESAMPLE,
+ TRACKTYPE_NORESAMPLE,
+ TRACKTYPE_NORESAMPLEMONO,
+ };
- struct track_t {
+ // process hook functionality
+ using process_hook_t = void(AudioMixer::*)();
+
+ struct Track;
+ using hook_t = void(Track::*)(int32_t* output, size_t numOutFrames, int32_t* temp, int32_t* aux);
+
+ struct Track {
+ Track()
+ : bufferProvider(nullptr)
+ {
+ // TODO: move additional initialization here.
+ }
+
+ ~Track()
+ {
+ // bufferProvider, mInputBufferProvider need not be deleted.
+ mResampler.reset(nullptr);
+ // Ensure the order of destruction of buffer providers as they
+ // release the upstream provider in the destructor.
+ mTimestretchBufferProvider.reset(nullptr);
+ mPostDownmixReformatBufferProvider.reset(nullptr);
+ mDownmixerBufferProvider.reset(nullptr);
+ mReformatBufferProvider.reset(nullptr);
+ }
+
+ bool needsRamp() { return (volumeInc[0] | volumeInc[1] | auxInc) != 0; }
+ bool setResampler(uint32_t trackSampleRate, uint32_t devSampleRate);
+ bool doesResample() const { return mResampler.get() != nullptr; }
+ void resetResampler() { if (mResampler.get() != nullptr) mResampler->reset(); }
+ void adjustVolumeRamp(bool aux, bool useFloat = false);
+ size_t getUnreleasedFrames() const { return mResampler.get() != nullptr ?
+ mResampler->getUnreleasedFrames() : 0; };
+
+ status_t prepareForDownmix();
+ void unprepareForDownmix();
+ status_t prepareForReformat();
+ void unprepareForReformat();
+ bool setPlaybackRate(const AudioPlaybackRate &playbackRate);
+ void reconfigureBufferProviders();
+
+ static hook_t getTrackHook(int trackType, uint32_t channelCount,
+ audio_format_t mixerInFormat, audio_format_t mixerOutFormat);
+
+ void track__nop(int32_t* out, size_t numFrames, int32_t* temp, int32_t* aux);
+
+ template <int MIXTYPE, bool USEFLOATVOL, bool ADJUSTVOL,
+ typename TO, typename TI, typename TA>
+ void volumeMix(TO *out, size_t outFrames, const TI *in, TA *aux, bool ramp);
+
uint32_t needs;
// TODO: Eventually remove legacy integer volume settings
@@ -181,16 +248,11 @@
};
int32_t prevVolume[MAX_NUM_VOLUMES];
-
- // 16-byte boundary
-
int32_t volumeInc[MAX_NUM_VOLUMES];
int32_t auxInc;
int32_t prevAuxLevel;
-
- // 16-byte boundary
-
int16_t auxLevel; // 0 <= auxLevel <= MAX_GAIN_INT, but signed for mul performance
+
uint16_t frameCount;
uint8_t channelCount; // 1 or 2, redundant with (needs & NEEDS_CHANNEL_COUNT__MASK)
@@ -202,22 +264,16 @@
// for how the Track buffer provider is wrapped by another one when dowmixing is required
AudioBufferProvider* bufferProvider;
- // 16-byte boundary
-
mutable AudioBufferProvider::Buffer buffer; // 8 bytes
hook_t hook;
- const void* in; // current location in buffer
+ const void *mIn; // current location in buffer
- // 16-byte boundary
-
- AudioResampler* resampler;
+ std::unique_ptr<AudioResampler> mResampler;
uint32_t sampleRate;
int32_t* mainBuffer;
int32_t* auxBuffer;
- // 16-byte boundary
-
/* Buffer providers are constructed to translate the track input data as needed.
*
* TODO: perhaps make a single PlaybackConverterProvider class to move
@@ -228,17 +284,17 @@
* match either mMixerInFormat or mDownmixRequiresFormat, if the downmixer
* requires reformat. For example, it may convert floating point input to
* PCM_16_bit if that's required by the downmixer.
- * 3) downmixerBufferProvider: If not NULL, performs the channel remixing to match
+ * 3) mDownmixerBufferProvider: If not NULL, performs the channel remixing to match
* the number of channels required by the mixer sink.
* 4) mPostDownmixReformatBufferProvider: If not NULL, performs reformatting from
* the downmixer requirements to the mixer engine input requirements.
* 5) mTimestretchBufferProvider: Adds timestretching for playback rate
*/
AudioBufferProvider* mInputBufferProvider; // externally provided buffer provider.
- PassthruBufferProvider* mReformatBufferProvider; // provider wrapper for reformatting.
- PassthruBufferProvider* downmixerBufferProvider; // wrapper for channel conversion.
- PassthruBufferProvider* mPostDownmixReformatBufferProvider;
- PassthruBufferProvider* mTimestretchBufferProvider;
+ std::unique_ptr<PassthruBufferProvider> mReformatBufferProvider;
+ std::unique_ptr<PassthruBufferProvider> mDownmixerBufferProvider;
+ std::unique_ptr<PassthruBufferProvider> mPostDownmixReformatBufferProvider;
+ std::unique_ptr<PassthruBufferProvider> mTimestretchBufferProvider;
int32_t sessionId;
@@ -263,129 +319,94 @@
AudioPlaybackRate mPlaybackRate;
- bool needsRamp() { return (volumeInc[0] | volumeInc[1] | auxInc) != 0; }
- bool setResampler(uint32_t trackSampleRate, uint32_t devSampleRate);
- bool doesResample() const { return resampler != NULL; }
- void resetResampler() { if (resampler != NULL) resampler->reset(); }
- void adjustVolumeRamp(bool aux, bool useFloat = false);
- size_t getUnreleasedFrames() const { return resampler != NULL ?
- resampler->getUnreleasedFrames() : 0; };
+ private:
+ // hooks
+ void track__genericResample(int32_t* out, size_t numFrames, int32_t* temp, int32_t* aux);
+ void track__16BitsStereo(int32_t* out, size_t numFrames, int32_t* temp, int32_t* aux);
+ void track__16BitsMono(int32_t* out, size_t numFrames, int32_t* temp, int32_t* aux);
- status_t prepareForDownmix();
- void unprepareForDownmix();
- status_t prepareForReformat();
- void unprepareForReformat();
- bool setPlaybackRate(const AudioPlaybackRate &playbackRate);
- void reconfigureBufferProviders();
+ void volumeRampStereo(int32_t* out, size_t frameCount, int32_t* temp, int32_t* aux);
+ void volumeStereo(int32_t* out, size_t frameCount, int32_t* temp, int32_t* aux);
+
+ // multi-format track hooks
+ template <int MIXTYPE, typename TO, typename TI, typename TA>
+ void track__Resample(TO* out, size_t frameCount, TO* temp __unused, TA* aux);
+ template <int MIXTYPE, typename TO, typename TI, typename TA>
+ void track__NoResample(TO* out, size_t frameCount, TO* temp __unused, TA* aux);
};
- typedef void (*process_hook_t)(state_t* state);
-
- // pad to 32-bytes to fill cache line
- struct state_t {
- uint32_t enabledTracks;
- uint32_t needsChanged;
- size_t frameCount;
- process_hook_t hook; // one of process__*, never NULL
- int32_t *outputTemp;
- int32_t *resampleTemp;
- NBLog::Writer* mNBLogWriter; // associated NBLog::Writer or &mDummyLog
- int32_t reserved[1];
- // FIXME allocate dynamically to save some memory when maxNumTracks < MAX_NUM_TRACKS
- track_t tracks[MAX_NUM_TRACKS] __attribute__((aligned(32)));
- };
-
- // bitmask of allocated track names, where bit 0 corresponds to TRACK0 etc.
- uint32_t mTrackNames;
-
- // bitmask of configured track names; ~0 if maxNumTracks == MAX_NUM_TRACKS,
- // but will have fewer bits set if maxNumTracks < MAX_NUM_TRACKS
- const uint32_t mConfiguredNames;
-
- const uint32_t mSampleRate;
-
- NBLog::Writer mDummyLogWriter;
-public:
- // Called by FastMixer to inform AudioMixer of it's associated NBLog::Writer.
- // FIXME It would be safer to use TLS for this, so we don't accidentally use wrong one.
- void setNBLogWriter(NBLog::Writer* log);
-private:
- state_t mState __attribute__((aligned(32)));
-
- // Call after changing either the enabled status of a track, or parameters of an enabled track.
- // OK to call more often than that, but unnecessary.
- void invalidateState(uint32_t mask);
+ // TODO: remove BLOCKSIZE unit of processing - it isn't needed anymore.
+ static constexpr int BLOCKSIZE = 16;
bool setChannelMasks(int name,
audio_channel_mask_t trackChannelMask, audio_channel_mask_t mixerChannelMask);
- static void track__genericResample(track_t* t, int32_t* out, size_t numFrames, int32_t* temp,
- int32_t* aux);
- static void track__nop(track_t* t, int32_t* out, size_t numFrames, int32_t* temp, int32_t* aux);
- static void track__16BitsStereo(track_t* t, int32_t* out, size_t numFrames, int32_t* temp,
- int32_t* aux);
- static void track__16BitsMono(track_t* t, int32_t* out, size_t numFrames, int32_t* temp,
- int32_t* aux);
- static void volumeRampStereo(track_t* t, int32_t* out, size_t frameCount, int32_t* temp,
- int32_t* aux);
- static void volumeStereo(track_t* t, int32_t* out, size_t frameCount, int32_t* temp,
- int32_t* aux);
+ // Called when track info changes and a new process hook should be determined.
+ void invalidate() {
+ mHook = &AudioMixer::process__validate;
+ }
- static void process__validate(state_t* state);
- static void process__nop(state_t* state);
- static void process__genericNoResampling(state_t* state);
- static void process__genericResampling(state_t* state);
- static void process__OneTrack16BitsStereoNoResampling(state_t* state);
+ void process__validate();
+ void process__nop();
+ void process__genericNoResampling();
+ void process__genericResampling();
+ void process__oneTrack16BitsStereoNoResampling();
- static pthread_once_t sOnceControl;
- static void sInitRoutine();
-
- /* multi-format volume mixing function (calls template functions
- * in AudioMixerOps.h). The template parameters are as follows:
- *
- * MIXTYPE (see AudioMixerOps.h MIXTYPE_* enumeration)
- * USEFLOATVOL (set to true if float volume is used)
- * ADJUSTVOL (set to true if volume ramp parameters needs adjustment afterwards)
- * TO: int32_t (Q4.27) or float
- * TI: int32_t (Q4.27) or int16_t (Q0.15) or float
- * TA: int32_t (Q4.27)
- */
- template <int MIXTYPE, bool USEFLOATVOL, bool ADJUSTVOL,
- typename TO, typename TI, typename TA>
- static void volumeMix(TO *out, size_t outFrames,
- const TI *in, TA *aux, bool ramp, AudioMixer::track_t *t);
-
- // multi-format process hooks
template <int MIXTYPE, typename TO, typename TI, typename TA>
- static void process_NoResampleOneTrack(state_t* state);
+ void process__noResampleOneTrack();
- // multi-format track hooks
- template <int MIXTYPE, typename TO, typename TI, typename TA>
- static void track__Resample(track_t* t, TO* out, size_t frameCount,
- TO* temp __unused, TA* aux);
- template <int MIXTYPE, typename TO, typename TI, typename TA>
- static void track__NoResample(track_t* t, TO* out, size_t frameCount,
- TO* temp __unused, TA* aux);
+ static process_hook_t getProcessHook(int processType, uint32_t channelCount,
+ audio_format_t mixerInFormat, audio_format_t mixerOutFormat);
static void convertMixerFormat(void *out, audio_format_t mixerOutFormat,
void *in, audio_format_t mixerInFormat, size_t sampleCount);
- // hook types
- enum {
- PROCESSTYPE_NORESAMPLEONETRACK,
- };
- enum {
- TRACKTYPE_NOP,
- TRACKTYPE_RESAMPLE,
- TRACKTYPE_NORESAMPLE,
- TRACKTYPE_NORESAMPLEMONO,
- };
+ static inline bool isValidPcmTrackFormat(audio_format_t format) {
+ switch (format) {
+ case AUDIO_FORMAT_PCM_8_BIT:
+ case AUDIO_FORMAT_PCM_16_BIT:
+ case AUDIO_FORMAT_PCM_24_BIT_PACKED:
+ case AUDIO_FORMAT_PCM_32_BIT:
+ case AUDIO_FORMAT_PCM_FLOAT:
+ return true;
+ default:
+ return false;
+ }
+ }
- // functions for determining the proper process and track hooks.
- static process_hook_t getProcessHook(int processType, uint32_t channelCount,
- audio_format_t mixerInFormat, audio_format_t mixerOutFormat);
- static hook_t getTrackHook(int trackType, uint32_t channelCount,
- audio_format_t mixerInFormat, audio_format_t mixerOutFormat);
+ static void sInitRoutine();
+
+ // initialization constants
+ const int mMaxNumTracks;
+ const uint32_t mSampleRate;
+ const size_t mFrameCount;
+
+ NBLog::Writer *mNBLogWriter = nullptr; // associated NBLog::Writer
+
+ process_hook_t mHook = &AudioMixer::process__nop; // one of process__*, never nullptr
+
+ // the size of the type (int32_t) should be the largest of all types supported
+ // by the mixer.
+ std::unique_ptr<int32_t[]> mOutputTemp;
+ std::unique_ptr<int32_t[]> mResampleTemp;
+
+ // fast lookup of previously deleted track names for reuse.
+ // the AudioMixer tries to return the smallest unused name -
+ // this is an arbitrary decision (actually any non-negative
+ // integer that isn't in mTracks could be used).
+ std::set<int /* name */> mUnusedNames; // set of unused track names (may be empty)
+
+ // track names grouped by main buffer, in no particular order of main buffer.
+ // however names for a particular main buffer are in order (by construction).
+ std::unordered_map<void * /* mainBuffer */, std::vector<int /* name */>> mGroups;
+
+ // track names that are enabled, in increasing order (by construction).
+ std::vector<int /* name */> mEnabled;
+
+ // track smart pointers, by name, in increasing order of name.
+ std::map<int /* name */, std::shared_ptr<Track>> mTracks;
+
+ static pthread_once_t sOnceControl; // initialized in constructor by first new
};
// ----------------------------------------------------------------------------
diff --git a/media/libaudioclient/include/media/AudioRecord.h b/media/libaudioclient/include/media/AudioRecord.h
index fea973a..caaefce 100644
--- a/media/libaudioclient/include/media/AudioRecord.h
+++ b/media/libaudioclient/include/media/AudioRecord.h
@@ -256,6 +256,11 @@
*/
uint32_t getNotificationPeriodInFrames() const { return mNotificationFramesAct; }
+ /*
+ * return metrics information for the current instance.
+ */
+ status_t getMetrics(MediaAnalyticsItem * &item);
+
/* After it's created the track is not active. Call start() to
* make it active. If set, the callback will start being called.
* If event is not AudioSystem::SYNC_EVENT_NONE, the capture start will be delayed until
@@ -703,6 +708,7 @@
}
}
void gather(const AudioRecord *record);
+ MediaAnalyticsItem *dup() { return mAnalyticsItem->dup(); }
private:
std::unique_ptr<MediaAnalyticsItem> mAnalyticsItem;
};
diff --git a/media/libaudioclient/include/media/AudioSystem.h b/media/libaudioclient/include/media/AudioSystem.h
index 24a6e22..52dcfaa 100644
--- a/media/libaudioclient/include/media/AudioSystem.h
+++ b/media/libaudioclient/include/media/AudioSystem.h
@@ -216,6 +216,7 @@
audio_io_handle_t *output,
audio_session_t session,
audio_stream_type_t *stream,
+ pid_t pid,
uid_t uid,
const audio_config_t *config,
audio_output_flags_t flags,
@@ -238,17 +239,16 @@
audio_session_t session,
pid_t pid,
uid_t uid,
+ const String16& opPackageName,
const audio_config_base_t *config,
audio_input_flags_t flags,
audio_port_handle_t *selectedDeviceId,
audio_port_handle_t *portId);
- static status_t startInput(audio_io_handle_t input,
- audio_session_t session);
- static status_t stopInput(audio_io_handle_t input,
- audio_session_t session);
- static void releaseInput(audio_io_handle_t input,
- audio_session_t session);
+ static status_t startInput(audio_port_handle_t portId,
+ bool *silenced);
+ static status_t stopInput(audio_port_handle_t portId);
+ static void releaseInput(audio_port_handle_t portId);
static status_t initStreamVolume(audio_stream_type_t stream,
int indexMin,
int indexMax);
@@ -281,7 +281,7 @@
static uint32_t getPrimaryOutputSamplingRate();
static size_t getPrimaryOutputFrameCount();
- static status_t setLowRamDevice(bool isLowRamDevice);
+ static status_t setLowRamDevice(bool isLowRamDevice, int64_t totalMemory);
// Check if hw offload is possible for given format, stream type, sample rate,
// bit rate, duration, video and streaming or offload property is enabled
diff --git a/media/libaudioclient/include/media/AudioTrack.h b/media/libaudioclient/include/media/AudioTrack.h
index c146db9..8fbe980 100644
--- a/media/libaudioclient/include/media/AudioTrack.h
+++ b/media/libaudioclient/include/media/AudioTrack.h
@@ -386,6 +386,11 @@
/* Return the static buffer specified in constructor or set(), or 0 for streaming mode */
sp<IMemory> sharedBuffer() const { return mSharedBuffer; }
+ /*
+ * return metrics information for the current track.
+ */
+ status_t getMetrics(MediaAnalyticsItem * &item);
+
/* After it's created the track is not active. Call start() to
* make it active. If set, the callback will start being called.
* If the track was previously paused, volume is ramped up over the first mix buffer.
@@ -1198,6 +1203,7 @@
}
}
void gather(const AudioTrack *track);
+ MediaAnalyticsItem *dup() { return mAnalyticsItem->dup(); }
private:
std::unique_ptr<MediaAnalyticsItem> mAnalyticsItem;
};
diff --git a/media/libaudioclient/include/media/IAudioFlinger.h b/media/libaudioclient/include/media/IAudioFlinger.h
index 57d9778..e8d405b 100644
--- a/media/libaudioclient/include/media/IAudioFlinger.h
+++ b/media/libaudioclient/include/media/IAudioFlinger.h
@@ -368,6 +368,7 @@
// mic mute/state
virtual status_t setMicMute(bool state) = 0;
virtual bool getMicMute() const = 0;
+ virtual void setRecordSilenced(uid_t uid, bool silenced) = 0;
virtual status_t setParameters(audio_io_handle_t ioHandle,
const String8& keyValuePairs) = 0;
@@ -453,8 +454,9 @@
// Intended for AudioService to inform AudioFlinger of device's low RAM attribute,
// and should be called at most once. For a definition of what "low RAM" means, see
- // android.app.ActivityManager.isLowRamDevice().
- virtual status_t setLowRamDevice(bool isLowRamDevice) = 0;
+ // android.app.ActivityManager.isLowRamDevice(). The totalMemory parameter
+ // is obtained from android.app.ActivityManager.MemoryInfo.totalMem.
+ virtual status_t setLowRamDevice(bool isLowRamDevice, int64_t totalMemory) = 0;
/* List available audio ports and their attributes */
virtual status_t listAudioPorts(unsigned int *num_ports,
diff --git a/media/libaudioclient/include/media/IAudioPolicyService.h b/media/libaudioclient/include/media/IAudioPolicyService.h
index 5558b77..949d593 100644
--- a/media/libaudioclient/include/media/IAudioPolicyService.h
+++ b/media/libaudioclient/include/media/IAudioPolicyService.h
@@ -60,6 +60,7 @@
audio_io_handle_t *output,
audio_session_t session,
audio_stream_type_t *stream,
+ pid_t pid,
uid_t uid,
const audio_config_t *config,
audio_output_flags_t flags,
@@ -79,16 +80,15 @@
audio_session_t session,
pid_t pid,
uid_t uid,
+ const String16& opPackageName,
const audio_config_base_t *config,
audio_input_flags_t flags,
audio_port_handle_t *selectedDeviceId,
audio_port_handle_t *portId) = 0;
- virtual status_t startInput(audio_io_handle_t input,
- audio_session_t session) = 0;
- virtual status_t stopInput(audio_io_handle_t input,
- audio_session_t session) = 0;
- virtual void releaseInput(audio_io_handle_t input,
- audio_session_t session) = 0;
+ virtual status_t startInput(audio_port_handle_t portId,
+ bool *silenced) = 0;
+ virtual status_t stopInput(audio_port_handle_t portId) = 0;
+ virtual void releaseInput(audio_port_handle_t portId) = 0;
virtual status_t initStreamVolume(audio_stream_type_t stream,
int indexMin,
int indexMax) = 0;
diff --git a/media/libaudiohal/2.0/Android.bp b/media/libaudiohal/2.0/Android.bp
new file mode 100644
index 0000000..574b435
--- /dev/null
+++ b/media/libaudiohal/2.0/Android.bp
@@ -0,0 +1,54 @@
+cc_library_shared {
+ name: "libaudiohal@2.0",
+
+ srcs: [
+ "DeviceHalLocal.cpp",
+ "DevicesFactoryHalHybrid.cpp",
+ "DevicesFactoryHalLocal.cpp",
+ "StreamHalLocal.cpp",
+
+ "ConversionHelperHidl.cpp",
+ "DeviceHalHidl.cpp",
+ "DevicesFactoryHalHidl.cpp",
+ "EffectBufferHalHidl.cpp",
+ "EffectHalHidl.cpp",
+ "EffectsFactoryHalHidl.cpp",
+ "StreamHalHidl.cpp",
+ ],
+
+ export_include_dirs: ["."],
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+ shared_libs: [
+ "libaudiohal_deathhandler",
+ "libaudioutils",
+ "libcutils",
+ "liblog",
+ "libutils",
+ "libhardware",
+ "libbase",
+ "libfmq",
+ "libhwbinder",
+ "libhidlbase",
+ "libhidlmemory",
+ "libhidltransport",
+ "android.hardware.audio@2.0",
+ "android.hardware.audio.common@2.0",
+ "android.hardware.audio.common@2.0-util",
+ "android.hardware.audio.effect@2.0",
+ "android.hidl.allocator@1.0",
+ "android.hidl.memory@1.0",
+ "libmedia_helper",
+ "libmediautils",
+ ],
+ header_libs: [
+ "libaudiohal_headers"
+ ],
+
+ export_shared_lib_headers: [
+ "libfmq",
+ ],
+}
diff --git a/media/libaudiohal/ConversionHelperHidl.cpp b/media/libaudiohal/2.0/ConversionHelperHidl.cpp
similarity index 100%
rename from media/libaudiohal/ConversionHelperHidl.cpp
rename to media/libaudiohal/2.0/ConversionHelperHidl.cpp
diff --git a/media/libaudiohal/ConversionHelperHidl.h b/media/libaudiohal/2.0/ConversionHelperHidl.h
similarity index 100%
rename from media/libaudiohal/ConversionHelperHidl.h
rename to media/libaudiohal/2.0/ConversionHelperHidl.h
diff --git a/media/libaudiohal/2.0/DeviceHalHidl.cpp b/media/libaudiohal/2.0/DeviceHalHidl.cpp
new file mode 100644
index 0000000..0d9c6c4
--- /dev/null
+++ b/media/libaudiohal/2.0/DeviceHalHidl.cpp
@@ -0,0 +1,358 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+
+#define LOG_TAG "DeviceHalHidl"
+//#define LOG_NDEBUG 0
+
+#include <android/hardware/audio/2.0/IPrimaryDevice.h>
+#include <cutils/native_handle.h>
+#include <hwbinder/IPCThreadState.h>
+#include <utils/Log.h>
+
+#include "DeviceHalHidl.h"
+#include "HidlUtils.h"
+#include "StreamHalHidl.h"
+
+using ::android::hardware::audio::common::V2_0::AudioConfig;
+using ::android::hardware::audio::common::V2_0::AudioDevice;
+using ::android::hardware::audio::common::V2_0::AudioInputFlag;
+using ::android::hardware::audio::common::V2_0::AudioOutputFlag;
+using ::android::hardware::audio::common::V2_0::AudioPatchHandle;
+using ::android::hardware::audio::common::V2_0::AudioPort;
+using ::android::hardware::audio::common::V2_0::AudioPortConfig;
+using ::android::hardware::audio::common::V2_0::AudioMode;
+using ::android::hardware::audio::common::V2_0::AudioSource;
+using ::android::hardware::audio::common::V2_0::HidlUtils;
+using ::android::hardware::audio::V2_0::DeviceAddress;
+using ::android::hardware::audio::V2_0::IPrimaryDevice;
+using ::android::hardware::audio::V2_0::ParameterValue;
+using ::android::hardware::audio::V2_0::Result;
+using ::android::hardware::hidl_string;
+using ::android::hardware::hidl_vec;
+
+namespace android {
+
+namespace {
+
+status_t deviceAddressFromHal(
+ audio_devices_t device, const char* halAddress, DeviceAddress* address) {
+ address->device = AudioDevice(device);
+
+ if (address == nullptr || strnlen(halAddress, AUDIO_DEVICE_MAX_ADDRESS_LEN) == 0) {
+ return OK;
+ }
+ const bool isInput = (device & AUDIO_DEVICE_BIT_IN) != 0;
+ if (isInput) device &= ~AUDIO_DEVICE_BIT_IN;
+ if ((!isInput && (device & AUDIO_DEVICE_OUT_ALL_A2DP) != 0)
+ || (isInput && (device & AUDIO_DEVICE_IN_BLUETOOTH_A2DP) != 0)) {
+ int status = sscanf(halAddress,
+ "%hhX:%hhX:%hhX:%hhX:%hhX:%hhX",
+ &address->address.mac[0], &address->address.mac[1], &address->address.mac[2],
+ &address->address.mac[3], &address->address.mac[4], &address->address.mac[5]);
+ return status == 6 ? OK : BAD_VALUE;
+ } else if ((!isInput && (device & AUDIO_DEVICE_OUT_IP) != 0)
+ || (isInput && (device & AUDIO_DEVICE_IN_IP) != 0)) {
+ int status = sscanf(halAddress,
+ "%hhu.%hhu.%hhu.%hhu",
+ &address->address.ipv4[0], &address->address.ipv4[1],
+ &address->address.ipv4[2], &address->address.ipv4[3]);
+ return status == 4 ? OK : BAD_VALUE;
+ } else if ((!isInput && (device & AUDIO_DEVICE_OUT_ALL_USB)) != 0
+ || (isInput && (device & AUDIO_DEVICE_IN_ALL_USB)) != 0) {
+ int status = sscanf(halAddress,
+ "card=%d;device=%d",
+ &address->address.alsa.card, &address->address.alsa.device);
+ return status == 2 ? OK : BAD_VALUE;
+ } else if ((!isInput && (device & AUDIO_DEVICE_OUT_BUS) != 0)
+ || (isInput && (device & AUDIO_DEVICE_IN_BUS) != 0)) {
+ if (halAddress != NULL) {
+ address->busAddress = halAddress;
+ return OK;
+ }
+ return BAD_VALUE;
+ } else if ((!isInput && (device & AUDIO_DEVICE_OUT_REMOTE_SUBMIX)) != 0
+ || (isInput && (device & AUDIO_DEVICE_IN_REMOTE_SUBMIX) != 0)) {
+ if (halAddress != NULL) {
+ address->rSubmixAddress = halAddress;
+ return OK;
+ }
+ return BAD_VALUE;
+ }
+ return OK;
+}
+
+} // namespace
+
+DeviceHalHidl::DeviceHalHidl(const sp<IDevice>& device)
+ : ConversionHelperHidl("Device"), mDevice(device),
+ mPrimaryDevice(IPrimaryDevice::castFrom(device)) {
+}
+
+DeviceHalHidl::~DeviceHalHidl() {
+ if (mDevice != 0) {
+ mDevice.clear();
+ hardware::IPCThreadState::self()->flushCommands();
+ }
+}
+
+status_t DeviceHalHidl::getSupportedDevices(uint32_t*) {
+ // Obsolete.
+ return INVALID_OPERATION;
+}
+
+status_t DeviceHalHidl::initCheck() {
+ if (mDevice == 0) return NO_INIT;
+ return processReturn("initCheck", mDevice->initCheck());
+}
+
+status_t DeviceHalHidl::setVoiceVolume(float volume) {
+ if (mDevice == 0) return NO_INIT;
+ if (mPrimaryDevice == 0) return INVALID_OPERATION;
+ return processReturn("setVoiceVolume", mPrimaryDevice->setVoiceVolume(volume));
+}
+
+status_t DeviceHalHidl::setMasterVolume(float volume) {
+ if (mDevice == 0) return NO_INIT;
+ if (mPrimaryDevice == 0) return INVALID_OPERATION;
+ return processReturn("setMasterVolume", mPrimaryDevice->setMasterVolume(volume));
+}
+
+status_t DeviceHalHidl::getMasterVolume(float *volume) {
+ if (mDevice == 0) return NO_INIT;
+ if (mPrimaryDevice == 0) return INVALID_OPERATION;
+ Result retval;
+ Return<void> ret = mPrimaryDevice->getMasterVolume(
+ [&](Result r, float v) {
+ retval = r;
+ if (retval == Result::OK) {
+ *volume = v;
+ }
+ });
+ return processReturn("getMasterVolume", ret, retval);
+}
+
+status_t DeviceHalHidl::setMode(audio_mode_t mode) {
+ if (mDevice == 0) return NO_INIT;
+ if (mPrimaryDevice == 0) return INVALID_OPERATION;
+ return processReturn("setMode", mPrimaryDevice->setMode(AudioMode(mode)));
+}
+
+status_t DeviceHalHidl::setMicMute(bool state) {
+ if (mDevice == 0) return NO_INIT;
+ return processReturn("setMicMute", mDevice->setMicMute(state));
+}
+
+status_t DeviceHalHidl::getMicMute(bool *state) {
+ if (mDevice == 0) return NO_INIT;
+ Result retval;
+ Return<void> ret = mDevice->getMicMute(
+ [&](Result r, bool mute) {
+ retval = r;
+ if (retval == Result::OK) {
+ *state = mute;
+ }
+ });
+ return processReturn("getMicMute", ret, retval);
+}
+
+status_t DeviceHalHidl::setMasterMute(bool state) {
+ if (mDevice == 0) return NO_INIT;
+ return processReturn("setMasterMute", mDevice->setMasterMute(state));
+}
+
+status_t DeviceHalHidl::getMasterMute(bool *state) {
+ if (mDevice == 0) return NO_INIT;
+ Result retval;
+ Return<void> ret = mDevice->getMasterMute(
+ [&](Result r, bool mute) {
+ retval = r;
+ if (retval == Result::OK) {
+ *state = mute;
+ }
+ });
+ return processReturn("getMasterMute", ret, retval);
+}
+
+status_t DeviceHalHidl::setParameters(const String8& kvPairs) {
+ if (mDevice == 0) return NO_INIT;
+ hidl_vec<ParameterValue> hidlParams;
+ status_t status = parametersFromHal(kvPairs, &hidlParams);
+ if (status != OK) return status;
+ return processReturn("setParameters", mDevice->setParameters(hidlParams));
+}
+
+status_t DeviceHalHidl::getParameters(const String8& keys, String8 *values) {
+ values->clear();
+ if (mDevice == 0) return NO_INIT;
+ hidl_vec<hidl_string> hidlKeys;
+ status_t status = keysFromHal(keys, &hidlKeys);
+ if (status != OK) return status;
+ Result retval;
+ Return<void> ret = mDevice->getParameters(
+ hidlKeys,
+ [&](Result r, const hidl_vec<ParameterValue>& parameters) {
+ retval = r;
+ if (retval == Result::OK) {
+ parametersToHal(parameters, values);
+ }
+ });
+ return processReturn("getParameters", ret, retval);
+}
+
+status_t DeviceHalHidl::getInputBufferSize(
+ const struct audio_config *config, size_t *size) {
+ if (mDevice == 0) return NO_INIT;
+ AudioConfig hidlConfig;
+ HidlUtils::audioConfigFromHal(*config, &hidlConfig);
+ Result retval;
+ Return<void> ret = mDevice->getInputBufferSize(
+ hidlConfig,
+ [&](Result r, uint64_t bufferSize) {
+ retval = r;
+ if (retval == Result::OK) {
+ *size = static_cast<size_t>(bufferSize);
+ }
+ });
+ return processReturn("getInputBufferSize", ret, retval);
+}
+
+status_t DeviceHalHidl::openOutputStream(
+ audio_io_handle_t handle,
+ audio_devices_t devices,
+ audio_output_flags_t flags,
+ struct audio_config *config,
+ const char *address,
+ sp<StreamOutHalInterface> *outStream) {
+ if (mDevice == 0) return NO_INIT;
+ DeviceAddress hidlDevice;
+ status_t status = deviceAddressFromHal(devices, address, &hidlDevice);
+ if (status != OK) return status;
+ AudioConfig hidlConfig;
+ HidlUtils::audioConfigFromHal(*config, &hidlConfig);
+ Result retval = Result::NOT_INITIALIZED;
+ Return<void> ret = mDevice->openOutputStream(
+ handle,
+ hidlDevice,
+ hidlConfig,
+ AudioOutputFlag(flags),
+ [&](Result r, const sp<IStreamOut>& result, const AudioConfig& suggestedConfig) {
+ retval = r;
+ if (retval == Result::OK) {
+ *outStream = new StreamOutHalHidl(result);
+ }
+ HidlUtils::audioConfigToHal(suggestedConfig, config);
+ });
+ return processReturn("openOutputStream", ret, retval);
+}
+
+status_t DeviceHalHidl::openInputStream(
+ audio_io_handle_t handle,
+ audio_devices_t devices,
+ struct audio_config *config,
+ audio_input_flags_t flags,
+ const char *address,
+ audio_source_t source,
+ sp<StreamInHalInterface> *inStream) {
+ if (mDevice == 0) return NO_INIT;
+ DeviceAddress hidlDevice;
+ status_t status = deviceAddressFromHal(devices, address, &hidlDevice);
+ if (status != OK) return status;
+ AudioConfig hidlConfig;
+ HidlUtils::audioConfigFromHal(*config, &hidlConfig);
+ Result retval = Result::NOT_INITIALIZED;
+ Return<void> ret = mDevice->openInputStream(
+ handle,
+ hidlDevice,
+ hidlConfig,
+ AudioInputFlag(flags),
+ AudioSource(source),
+ [&](Result r, const sp<IStreamIn>& result, const AudioConfig& suggestedConfig) {
+ retval = r;
+ if (retval == Result::OK) {
+ *inStream = new StreamInHalHidl(result);
+ }
+ HidlUtils::audioConfigToHal(suggestedConfig, config);
+ });
+ return processReturn("openInputStream", ret, retval);
+}
+
+status_t DeviceHalHidl::supportsAudioPatches(bool *supportsPatches) {
+ if (mDevice == 0) return NO_INIT;
+ return processReturn("supportsAudioPatches", mDevice->supportsAudioPatches(), supportsPatches);
+}
+
+status_t DeviceHalHidl::createAudioPatch(
+ unsigned int num_sources,
+ const struct audio_port_config *sources,
+ unsigned int num_sinks,
+ const struct audio_port_config *sinks,
+ audio_patch_handle_t *patch) {
+ if (mDevice == 0) return NO_INIT;
+ hidl_vec<AudioPortConfig> hidlSources, hidlSinks;
+ HidlUtils::audioPortConfigsFromHal(num_sources, sources, &hidlSources);
+ HidlUtils::audioPortConfigsFromHal(num_sinks, sinks, &hidlSinks);
+ Result retval;
+ Return<void> ret = mDevice->createAudioPatch(
+ hidlSources, hidlSinks,
+ [&](Result r, AudioPatchHandle hidlPatch) {
+ retval = r;
+ if (retval == Result::OK) {
+ *patch = static_cast<audio_patch_handle_t>(hidlPatch);
+ }
+ });
+ return processReturn("createAudioPatch", ret, retval);
+}
+
+status_t DeviceHalHidl::releaseAudioPatch(audio_patch_handle_t patch) {
+ if (mDevice == 0) return NO_INIT;
+ return processReturn("releaseAudioPatch", mDevice->releaseAudioPatch(patch));
+}
+
+status_t DeviceHalHidl::getAudioPort(struct audio_port *port) {
+ if (mDevice == 0) return NO_INIT;
+ AudioPort hidlPort;
+ HidlUtils::audioPortFromHal(*port, &hidlPort);
+ Result retval;
+ Return<void> ret = mDevice->getAudioPort(
+ hidlPort,
+ [&](Result r, const AudioPort& p) {
+ retval = r;
+ if (retval == Result::OK) {
+ HidlUtils::audioPortToHal(p, port);
+ }
+ });
+ return processReturn("getAudioPort", ret, retval);
+}
+
+status_t DeviceHalHidl::setAudioPortConfig(const struct audio_port_config *config) {
+ if (mDevice == 0) return NO_INIT;
+ AudioPortConfig hidlConfig;
+ HidlUtils::audioPortConfigFromHal(*config, &hidlConfig);
+ return processReturn("setAudioPortConfig", mDevice->setAudioPortConfig(hidlConfig));
+}
+
+status_t DeviceHalHidl::dump(int fd) {
+ if (mDevice == 0) return NO_INIT;
+ native_handle_t* hidlHandle = native_handle_create(1, 0);
+ hidlHandle->data[0] = fd;
+ Return<void> ret = mDevice->debugDump(hidlHandle);
+ native_handle_delete(hidlHandle);
+ return processReturn("dump", ret);
+}
+
+} // namespace android
diff --git a/media/libaudiohal/DeviceHalHidl.h b/media/libaudiohal/2.0/DeviceHalHidl.h
similarity index 100%
rename from media/libaudiohal/DeviceHalHidl.h
rename to media/libaudiohal/2.0/DeviceHalHidl.h
diff --git a/media/libaudiohal/DeviceHalLocal.cpp b/media/libaudiohal/2.0/DeviceHalLocal.cpp
similarity index 100%
rename from media/libaudiohal/DeviceHalLocal.cpp
rename to media/libaudiohal/2.0/DeviceHalLocal.cpp
diff --git a/media/libaudiohal/DeviceHalLocal.h b/media/libaudiohal/2.0/DeviceHalLocal.h
similarity index 100%
rename from media/libaudiohal/DeviceHalLocal.h
rename to media/libaudiohal/2.0/DeviceHalLocal.h
diff --git a/media/libaudiohal/DevicesFactoryHalHidl.cpp b/media/libaudiohal/2.0/DevicesFactoryHalHidl.cpp
similarity index 100%
rename from media/libaudiohal/DevicesFactoryHalHidl.cpp
rename to media/libaudiohal/2.0/DevicesFactoryHalHidl.cpp
diff --git a/media/libaudiohal/DevicesFactoryHalHidl.h b/media/libaudiohal/2.0/DevicesFactoryHalHidl.h
similarity index 100%
rename from media/libaudiohal/DevicesFactoryHalHidl.h
rename to media/libaudiohal/2.0/DevicesFactoryHalHidl.h
diff --git a/media/libaudiohal/2.0/DevicesFactoryHalHybrid.cpp b/media/libaudiohal/2.0/DevicesFactoryHalHybrid.cpp
new file mode 100644
index 0000000..77df6b5
--- /dev/null
+++ b/media/libaudiohal/2.0/DevicesFactoryHalHybrid.cpp
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "DevicesFactoryHalHybrid"
+//#define LOG_NDEBUG 0
+
+#include "DevicesFactoryHalHybrid.h"
+#include "DevicesFactoryHalLocal.h"
+#include "DevicesFactoryHalHidl.h"
+
+namespace android {
+
+DevicesFactoryHalHybrid::DevicesFactoryHalHybrid()
+ : mLocalFactory(new DevicesFactoryHalLocal()),
+ mHidlFactory(new DevicesFactoryHalHidl()) {
+}
+
+DevicesFactoryHalHybrid::~DevicesFactoryHalHybrid() {
+}
+
+status_t DevicesFactoryHalHybrid::openDevice(const char *name, sp<DeviceHalInterface> *device) {
+ if (mHidlFactory != 0 && strcmp(AUDIO_HARDWARE_MODULE_ID_A2DP, name) != 0) {
+ return mHidlFactory->openDevice(name, device);
+ }
+ return mLocalFactory->openDevice(name, device);
+}
+
+} // namespace android
diff --git a/media/libaudiohal/DevicesFactoryHalHybrid.h b/media/libaudiohal/2.0/DevicesFactoryHalHybrid.h
similarity index 100%
rename from media/libaudiohal/DevicesFactoryHalHybrid.h
rename to media/libaudiohal/2.0/DevicesFactoryHalHybrid.h
diff --git a/media/libaudiohal/DevicesFactoryHalLocal.cpp b/media/libaudiohal/2.0/DevicesFactoryHalLocal.cpp
similarity index 100%
rename from media/libaudiohal/DevicesFactoryHalLocal.cpp
rename to media/libaudiohal/2.0/DevicesFactoryHalLocal.cpp
diff --git a/media/libaudiohal/DevicesFactoryHalLocal.h b/media/libaudiohal/2.0/DevicesFactoryHalLocal.h
similarity index 100%
rename from media/libaudiohal/DevicesFactoryHalLocal.h
rename to media/libaudiohal/2.0/DevicesFactoryHalLocal.h
diff --git a/media/libaudiohal/2.0/EffectBufferHalHidl.cpp b/media/libaudiohal/2.0/EffectBufferHalHidl.cpp
new file mode 100644
index 0000000..226a500
--- /dev/null
+++ b/media/libaudiohal/2.0/EffectBufferHalHidl.cpp
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <atomic>
+
+#define LOG_TAG "EffectBufferHalHidl"
+//#define LOG_NDEBUG 0
+
+#include <android/hidl/allocator/1.0/IAllocator.h>
+#include <hidlmemory/mapping.h>
+#include <utils/Log.h>
+
+#include "ConversionHelperHidl.h"
+#include "EffectBufferHalHidl.h"
+
+using ::android::hardware::Return;
+using ::android::hidl::allocator::V1_0::IAllocator;
+
+namespace android {
+
+// static
+uint64_t EffectBufferHalHidl::makeUniqueId() {
+ static std::atomic<uint64_t> counter{1};
+ return counter++;
+}
+
+status_t EffectBufferHalHidl::allocate(
+ size_t size, sp<EffectBufferHalInterface>* buffer) {
+ return mirror(nullptr, size, buffer);
+}
+
+status_t EffectBufferHalHidl::mirror(
+ void* external, size_t size, sp<EffectBufferHalInterface>* buffer) {
+ sp<EffectBufferHalInterface> tempBuffer = new EffectBufferHalHidl(size);
+ status_t result = static_cast<EffectBufferHalHidl*>(tempBuffer.get())->init();
+ if (result == OK) {
+ tempBuffer->setExternalData(external);
+ *buffer = tempBuffer;
+ }
+ return result;
+}
+
+EffectBufferHalHidl::EffectBufferHalHidl(size_t size)
+ : mBufferSize(size), mFrameCountChanged(false),
+ mExternalData(nullptr), mAudioBuffer{0, {nullptr}} {
+ mHidlBuffer.id = makeUniqueId();
+ mHidlBuffer.frameCount = 0;
+}
+
+EffectBufferHalHidl::~EffectBufferHalHidl() {
+}
+
+status_t EffectBufferHalHidl::init() {
+ sp<IAllocator> ashmem = IAllocator::getService("ashmem");
+ if (ashmem == 0) {
+ ALOGE("Failed to retrieve ashmem allocator service");
+ return NO_INIT;
+ }
+ status_t retval = NO_MEMORY;
+ Return<void> result = ashmem->allocate(
+ mBufferSize,
+ [&](bool success, const hidl_memory& memory) {
+ if (success) {
+ mHidlBuffer.data = memory;
+ retval = OK;
+ }
+ });
+ if (result.isOk() && retval == OK) {
+ mMemory = hardware::mapMemory(mHidlBuffer.data);
+ if (mMemory != 0) {
+ mMemory->update();
+ mAudioBuffer.raw = static_cast<void*>(mMemory->getPointer());
+ memset(mAudioBuffer.raw, 0, mMemory->getSize());
+ mMemory->commit();
+ } else {
+ ALOGE("Failed to map allocated ashmem");
+ retval = NO_MEMORY;
+ }
+ } else {
+ ALOGE("Failed to allocate %d bytes from ashmem", (int)mBufferSize);
+ }
+ return result.isOk() ? retval : FAILED_TRANSACTION;
+}
+
+audio_buffer_t* EffectBufferHalHidl::audioBuffer() {
+ return &mAudioBuffer;
+}
+
+void* EffectBufferHalHidl::externalData() const {
+ return mExternalData;
+}
+
+void EffectBufferHalHidl::setFrameCount(size_t frameCount) {
+ mHidlBuffer.frameCount = frameCount;
+ mAudioBuffer.frameCount = frameCount;
+ mFrameCountChanged = true;
+}
+
+bool EffectBufferHalHidl::checkFrameCountChange() {
+ bool result = mFrameCountChanged;
+ mFrameCountChanged = false;
+ return result;
+}
+
+void EffectBufferHalHidl::setExternalData(void* external) {
+ mExternalData = external;
+}
+
+void EffectBufferHalHidl::update() {
+ update(mBufferSize);
+}
+
+void EffectBufferHalHidl::commit() {
+ commit(mBufferSize);
+}
+
+void EffectBufferHalHidl::update(size_t size) {
+ if (mExternalData == nullptr) return;
+ mMemory->update();
+ if (size > mBufferSize) size = mBufferSize;
+ memcpy(mAudioBuffer.raw, mExternalData, size);
+ mMemory->commit();
+}
+
+void EffectBufferHalHidl::commit(size_t size) {
+ if (mExternalData == nullptr) return;
+ if (size > mBufferSize) size = mBufferSize;
+ memcpy(mExternalData, mAudioBuffer.raw, size);
+}
+
+} // namespace android
diff --git a/media/libaudiohal/2.0/EffectBufferHalHidl.h b/media/libaudiohal/2.0/EffectBufferHalHidl.h
new file mode 100644
index 0000000..31e0087
--- /dev/null
+++ b/media/libaudiohal/2.0/EffectBufferHalHidl.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_EFFECT_BUFFER_HAL_HIDL_H
+#define ANDROID_HARDWARE_EFFECT_BUFFER_HAL_HIDL_H
+
+#include <android/hardware/audio/effect/2.0/types.h>
+#include <android/hidl/memory/1.0/IMemory.h>
+#include <hidl/HidlSupport.h>
+#include <media/audiohal/EffectBufferHalInterface.h>
+#include <system/audio_effect.h>
+
+using android::hardware::audio::effect::V2_0::AudioBuffer;
+using android::hardware::hidl_memory;
+using android::hidl::memory::V1_0::IMemory;
+
+namespace android {
+
+class EffectBufferHalHidl : public EffectBufferHalInterface
+{
+ public:
+ static status_t allocate(size_t size, sp<EffectBufferHalInterface>* buffer);
+ static status_t mirror(void* external, size_t size, sp<EffectBufferHalInterface>* buffer);
+
+ virtual audio_buffer_t* audioBuffer();
+ virtual void* externalData() const;
+
+ virtual size_t getSize() const override { return mBufferSize; }
+
+ virtual void setExternalData(void* external);
+ virtual void setFrameCount(size_t frameCount);
+ virtual bool checkFrameCountChange();
+
+ virtual void update();
+ virtual void commit();
+ virtual void update(size_t size);
+ virtual void commit(size_t size);
+
+ const AudioBuffer& hidlBuffer() const { return mHidlBuffer; }
+
+ private:
+ friend class EffectBufferHalInterface;
+
+ static uint64_t makeUniqueId();
+
+ const size_t mBufferSize;
+ bool mFrameCountChanged;
+ void* mExternalData;
+ AudioBuffer mHidlBuffer;
+ sp<IMemory> mMemory;
+ audio_buffer_t mAudioBuffer;
+
+ // Can not be constructed directly by clients.
+ explicit EffectBufferHalHidl(size_t size);
+
+ virtual ~EffectBufferHalHidl();
+
+ status_t init();
+};
+
+} // namespace android
+
+#endif // ANDROID_HARDWARE_EFFECT_BUFFER_HAL_HIDL_H
diff --git a/media/libaudiohal/2.0/EffectHalHidl.cpp b/media/libaudiohal/2.0/EffectHalHidl.cpp
new file mode 100644
index 0000000..4fb032c
--- /dev/null
+++ b/media/libaudiohal/2.0/EffectHalHidl.cpp
@@ -0,0 +1,338 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "EffectHalHidl"
+//#define LOG_NDEBUG 0
+
+#include <hwbinder/IPCThreadState.h>
+#include <media/EffectsFactoryApi.h>
+#include <utils/Log.h>
+
+#include "ConversionHelperHidl.h"
+#include "EffectBufferHalHidl.h"
+#include "EffectHalHidl.h"
+#include "HidlUtils.h"
+
+using ::android::hardware::audio::effect::V2_0::AudioBuffer;
+using ::android::hardware::audio::effect::V2_0::EffectBufferAccess;
+using ::android::hardware::audio::effect::V2_0::EffectConfigParameters;
+using ::android::hardware::audio::effect::V2_0::MessageQueueFlagBits;
+using ::android::hardware::audio::effect::V2_0::Result;
+using ::android::hardware::audio::common::V2_0::HidlUtils;
+using ::android::hardware::audio::common::V2_0::AudioChannelMask;
+using ::android::hardware::audio::common::V2_0::AudioFormat;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::MQDescriptorSync;
+using ::android::hardware::Return;
+
+namespace android {
+
+EffectHalHidl::EffectHalHidl(const sp<IEffect>& effect, uint64_t effectId)
+ : mEffect(effect), mEffectId(effectId), mBuffersChanged(true), mEfGroup(nullptr) {
+}
+
+EffectHalHidl::~EffectHalHidl() {
+ if (mEffect != 0) {
+ close();
+ mEffect.clear();
+ hardware::IPCThreadState::self()->flushCommands();
+ }
+ if (mEfGroup) {
+ EventFlag::deleteEventFlag(&mEfGroup);
+ }
+}
+
+// static
+void EffectHalHidl::effectDescriptorToHal(
+ const EffectDescriptor& descriptor, effect_descriptor_t* halDescriptor) {
+ HidlUtils::uuidToHal(descriptor.type, &halDescriptor->type);
+ HidlUtils::uuidToHal(descriptor.uuid, &halDescriptor->uuid);
+ halDescriptor->flags = static_cast<uint32_t>(descriptor.flags);
+ halDescriptor->cpuLoad = descriptor.cpuLoad;
+ halDescriptor->memoryUsage = descriptor.memoryUsage;
+ memcpy(halDescriptor->name, descriptor.name.data(), descriptor.name.size());
+ memcpy(halDescriptor->implementor,
+ descriptor.implementor.data(), descriptor.implementor.size());
+}
+
+// TODO(mnaganov): These buffer conversion functions should be shared with Effect wrapper
+// via HidlUtils. Move them there when hardware/interfaces will get un-frozen again.
+
+// static
+void EffectHalHidl::effectBufferConfigFromHal(
+ const buffer_config_t& halConfig, EffectBufferConfig* config) {
+ config->samplingRateHz = halConfig.samplingRate;
+ config->channels = AudioChannelMask(halConfig.channels);
+ config->format = AudioFormat(halConfig.format);
+ config->accessMode = EffectBufferAccess(halConfig.accessMode);
+ config->mask = EffectConfigParameters(halConfig.mask);
+}
+
+// static
+void EffectHalHidl::effectBufferConfigToHal(
+ const EffectBufferConfig& config, buffer_config_t* halConfig) {
+ halConfig->buffer.frameCount = 0;
+ halConfig->buffer.raw = NULL;
+ halConfig->samplingRate = config.samplingRateHz;
+ halConfig->channels = static_cast<uint32_t>(config.channels);
+ halConfig->bufferProvider.cookie = NULL;
+ halConfig->bufferProvider.getBuffer = NULL;
+ halConfig->bufferProvider.releaseBuffer = NULL;
+ halConfig->format = static_cast<uint8_t>(config.format);
+ halConfig->accessMode = static_cast<uint8_t>(config.accessMode);
+ halConfig->mask = static_cast<uint8_t>(config.mask);
+}
+
+// static
+void EffectHalHidl::effectConfigFromHal(const effect_config_t& halConfig, EffectConfig* config) {
+ effectBufferConfigFromHal(halConfig.inputCfg, &config->inputCfg);
+ effectBufferConfigFromHal(halConfig.outputCfg, &config->outputCfg);
+}
+
+// static
+void EffectHalHidl::effectConfigToHal(const EffectConfig& config, effect_config_t* halConfig) {
+ effectBufferConfigToHal(config.inputCfg, &halConfig->inputCfg);
+ effectBufferConfigToHal(config.outputCfg, &halConfig->outputCfg);
+}
+
+// static
+status_t EffectHalHidl::analyzeResult(const Result& result) {
+ switch (result) {
+ case Result::OK: return OK;
+ case Result::INVALID_ARGUMENTS: return BAD_VALUE;
+ case Result::INVALID_STATE: return NOT_ENOUGH_DATA;
+ case Result::NOT_INITIALIZED: return NO_INIT;
+ case Result::NOT_SUPPORTED: return INVALID_OPERATION;
+ case Result::RESULT_TOO_BIG: return NO_MEMORY;
+ default: return NO_INIT;
+ }
+}
+
+status_t EffectHalHidl::setInBuffer(const sp<EffectBufferHalInterface>& buffer) {
+ if (!mBuffersChanged) {
+ if (buffer.get() == nullptr || mInBuffer.get() == nullptr) {
+ mBuffersChanged = buffer.get() != mInBuffer.get();
+ } else {
+ mBuffersChanged = buffer->audioBuffer() != mInBuffer->audioBuffer();
+ }
+ }
+ mInBuffer = buffer;
+ return OK;
+}
+
+status_t EffectHalHidl::setOutBuffer(const sp<EffectBufferHalInterface>& buffer) {
+ if (!mBuffersChanged) {
+ if (buffer.get() == nullptr || mOutBuffer.get() == nullptr) {
+ mBuffersChanged = buffer.get() != mOutBuffer.get();
+ } else {
+ mBuffersChanged = buffer->audioBuffer() != mOutBuffer->audioBuffer();
+ }
+ }
+ mOutBuffer = buffer;
+ return OK;
+}
+
+status_t EffectHalHidl::process() {
+ return processImpl(static_cast<uint32_t>(MessageQueueFlagBits::REQUEST_PROCESS));
+}
+
+status_t EffectHalHidl::processReverse() {
+ return processImpl(static_cast<uint32_t>(MessageQueueFlagBits::REQUEST_PROCESS_REVERSE));
+}
+
+status_t EffectHalHidl::prepareForProcessing() {
+ std::unique_ptr<StatusMQ> tempStatusMQ;
+ Result retval;
+ Return<void> ret = mEffect->prepareForProcessing(
+ [&](Result r, const MQDescriptorSync<Result>& statusMQ) {
+ retval = r;
+ if (retval == Result::OK) {
+ tempStatusMQ.reset(new StatusMQ(statusMQ));
+ if (tempStatusMQ->isValid() && tempStatusMQ->getEventFlagWord()) {
+ EventFlag::createEventFlag(tempStatusMQ->getEventFlagWord(), &mEfGroup);
+ }
+ }
+ });
+ if (!ret.isOk() || retval != Result::OK) {
+ return ret.isOk() ? analyzeResult(retval) : FAILED_TRANSACTION;
+ }
+ if (!tempStatusMQ || !tempStatusMQ->isValid() || !mEfGroup) {
+ ALOGE_IF(!tempStatusMQ, "Failed to obtain status message queue for effects");
+ ALOGE_IF(tempStatusMQ && !tempStatusMQ->isValid(),
+ "Status message queue for effects is invalid");
+ ALOGE_IF(!mEfGroup, "Event flag creation for effects failed");
+ return NO_INIT;
+ }
+ mStatusMQ = std::move(tempStatusMQ);
+ return OK;
+}
+
+bool EffectHalHidl::needToResetBuffers() {
+ if (mBuffersChanged) return true;
+ bool inBufferFrameCountUpdated = mInBuffer->checkFrameCountChange();
+ bool outBufferFrameCountUpdated = mOutBuffer->checkFrameCountChange();
+ return inBufferFrameCountUpdated || outBufferFrameCountUpdated;
+}
+
+status_t EffectHalHidl::processImpl(uint32_t mqFlag) {
+ if (mEffect == 0 || mInBuffer == 0 || mOutBuffer == 0) return NO_INIT;
+ status_t status;
+ if (!mStatusMQ && (status = prepareForProcessing()) != OK) {
+ return status;
+ }
+ if (needToResetBuffers() && (status = setProcessBuffers()) != OK) {
+ return status;
+ }
+ // The data is already in the buffers, just need to flush it and wake up the server side.
+ std::atomic_thread_fence(std::memory_order_release);
+ mEfGroup->wake(mqFlag);
+ uint32_t efState = 0;
+retry:
+ status_t ret = mEfGroup->wait(
+ static_cast<uint32_t>(MessageQueueFlagBits::DONE_PROCESSING), &efState);
+ if (efState & static_cast<uint32_t>(MessageQueueFlagBits::DONE_PROCESSING)) {
+ Result retval = Result::NOT_INITIALIZED;
+ mStatusMQ->read(&retval);
+ if (retval == Result::OK || retval == Result::INVALID_STATE) {
+ // Sync back the changed contents of the buffer.
+ std::atomic_thread_fence(std::memory_order_acquire);
+ }
+ return analyzeResult(retval);
+ }
+ if (ret == -EAGAIN || ret == -EINTR) {
+ // Spurious wakeup. This normally retries no more than once.
+ goto retry;
+ }
+ return ret;
+}
+
+status_t EffectHalHidl::setProcessBuffers() {
+ Return<Result> ret = mEffect->setProcessBuffers(
+ static_cast<EffectBufferHalHidl*>(mInBuffer.get())->hidlBuffer(),
+ static_cast<EffectBufferHalHidl*>(mOutBuffer.get())->hidlBuffer());
+ if (ret.isOk() && ret == Result::OK) {
+ mBuffersChanged = false;
+ return OK;
+ }
+ return ret.isOk() ? analyzeResult(ret) : FAILED_TRANSACTION;
+}
+
+status_t EffectHalHidl::command(uint32_t cmdCode, uint32_t cmdSize, void *pCmdData,
+ uint32_t *replySize, void *pReplyData) {
+ if (mEffect == 0) return NO_INIT;
+
+ // Special cases.
+ if (cmdCode == EFFECT_CMD_SET_CONFIG || cmdCode == EFFECT_CMD_SET_CONFIG_REVERSE) {
+ return setConfigImpl(cmdCode, cmdSize, pCmdData, replySize, pReplyData);
+ } else if (cmdCode == EFFECT_CMD_GET_CONFIG || cmdCode == EFFECT_CMD_GET_CONFIG_REVERSE) {
+ return getConfigImpl(cmdCode, replySize, pReplyData);
+ }
+
+ // Common case.
+ hidl_vec<uint8_t> hidlData;
+ if (pCmdData != nullptr && cmdSize > 0) {
+ hidlData.setToExternal(reinterpret_cast<uint8_t*>(pCmdData), cmdSize);
+ }
+ status_t status;
+ uint32_t replySizeStub = 0;
+ if (replySize == nullptr || pReplyData == nullptr) replySize = &replySizeStub;
+ Return<void> ret = mEffect->command(cmdCode, hidlData, *replySize,
+ [&](int32_t s, const hidl_vec<uint8_t>& result) {
+ status = s;
+ if (status == 0) {
+ if (*replySize > result.size()) *replySize = result.size();
+ if (pReplyData != nullptr && *replySize > 0) {
+ memcpy(pReplyData, &result[0], *replySize);
+ }
+ }
+ });
+ return ret.isOk() ? status : FAILED_TRANSACTION;
+}
+
+status_t EffectHalHidl::getDescriptor(effect_descriptor_t *pDescriptor) {
+ if (mEffect == 0) return NO_INIT;
+ Result retval = Result::NOT_INITIALIZED;
+ Return<void> ret = mEffect->getDescriptor(
+ [&](Result r, const EffectDescriptor& result) {
+ retval = r;
+ if (retval == Result::OK) {
+ effectDescriptorToHal(result, pDescriptor);
+ }
+ });
+ return ret.isOk() ? analyzeResult(retval) : FAILED_TRANSACTION;
+}
+
+status_t EffectHalHidl::close() {
+ if (mEffect == 0) return NO_INIT;
+ Return<Result> ret = mEffect->close();
+ return ret.isOk() ? analyzeResult(ret) : FAILED_TRANSACTION;
+}
+
+status_t EffectHalHidl::getConfigImpl(
+ uint32_t cmdCode, uint32_t *replySize, void *pReplyData) {
+ if (replySize == NULL || *replySize != sizeof(effect_config_t) || pReplyData == NULL) {
+ return BAD_VALUE;
+ }
+ status_t result = FAILED_TRANSACTION;
+ Return<void> ret;
+ if (cmdCode == EFFECT_CMD_GET_CONFIG) {
+ ret = mEffect->getConfig([&] (Result r, const EffectConfig &hidlConfig) {
+ result = analyzeResult(r);
+ if (r == Result::OK) {
+ effectConfigToHal(hidlConfig, static_cast<effect_config_t*>(pReplyData));
+ }
+ });
+ } else {
+ ret = mEffect->getConfigReverse([&] (Result r, const EffectConfig &hidlConfig) {
+ result = analyzeResult(r);
+ if (r == Result::OK) {
+ effectConfigToHal(hidlConfig, static_cast<effect_config_t*>(pReplyData));
+ }
+ });
+ }
+ if (!ret.isOk()) {
+ result = FAILED_TRANSACTION;
+ }
+ return result;
+}
+
+status_t EffectHalHidl::setConfigImpl(
+ uint32_t cmdCode, uint32_t cmdSize, void *pCmdData, uint32_t *replySize, void *pReplyData) {
+ if (pCmdData == NULL || cmdSize != sizeof(effect_config_t) ||
+ replySize == NULL || *replySize != sizeof(int32_t) || pReplyData == NULL) {
+ return BAD_VALUE;
+ }
+ const effect_config_t *halConfig = static_cast<effect_config_t*>(pCmdData);
+ if (halConfig->inputCfg.bufferProvider.getBuffer != NULL ||
+ halConfig->inputCfg.bufferProvider.releaseBuffer != NULL ||
+ halConfig->outputCfg.bufferProvider.getBuffer != NULL ||
+ halConfig->outputCfg.bufferProvider.releaseBuffer != NULL) {
+ ALOGE("Buffer provider callbacks are not supported");
+ }
+ EffectConfig hidlConfig;
+ effectConfigFromHal(*halConfig, &hidlConfig);
+ Return<Result> ret = cmdCode == EFFECT_CMD_SET_CONFIG ?
+ mEffect->setConfig(hidlConfig, nullptr, nullptr) :
+ mEffect->setConfigReverse(hidlConfig, nullptr, nullptr);
+ status_t result = FAILED_TRANSACTION;
+ if (ret.isOk()) {
+ result = analyzeResult(ret);
+ *static_cast<int32_t*>(pReplyData) = result;
+ }
+ return result;
+}
+
+} // namespace android
diff --git a/media/libaudiohal/EffectHalHidl.h b/media/libaudiohal/2.0/EffectHalHidl.h
similarity index 100%
rename from media/libaudiohal/EffectHalHidl.h
rename to media/libaudiohal/2.0/EffectHalHidl.h
diff --git a/media/libaudiohal/2.0/EffectsFactoryHalHidl.cpp b/media/libaudiohal/2.0/EffectsFactoryHalHidl.cpp
new file mode 100644
index 0000000..0d40e6d
--- /dev/null
+++ b/media/libaudiohal/2.0/EffectsFactoryHalHidl.cpp
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "EffectsFactoryHalHidl"
+//#define LOG_NDEBUG 0
+
+#include <cutils/native_handle.h>
+
+#include "ConversionHelperHidl.h"
+#include "EffectBufferHalHidl.h"
+#include "EffectHalHidl.h"
+#include "EffectsFactoryHalHidl.h"
+#include "HidlUtils.h"
+
+using ::android::hardware::audio::common::V2_0::HidlUtils;
+using ::android::hardware::audio::common::V2_0::Uuid;
+using ::android::hardware::audio::effect::V2_0::IEffect;
+using ::android::hardware::audio::effect::V2_0::Result;
+using ::android::hardware::Return;
+
+namespace android {
+
+EffectsFactoryHalHidl::EffectsFactoryHalHidl() : ConversionHelperHidl("EffectsFactory") {
+ mEffectsFactory = IEffectsFactory::getService();
+ if (mEffectsFactory == 0) {
+ ALOGE("Failed to obtain IEffectsFactory service, terminating process.");
+ exit(1);
+ }
+}
+
+EffectsFactoryHalHidl::~EffectsFactoryHalHidl() {
+}
+
+status_t EffectsFactoryHalHidl::queryAllDescriptors() {
+ if (mEffectsFactory == 0) return NO_INIT;
+ Result retval = Result::NOT_INITIALIZED;
+ Return<void> ret = mEffectsFactory->getAllDescriptors(
+ [&](Result r, const hidl_vec<EffectDescriptor>& result) {
+ retval = r;
+ if (retval == Result::OK) {
+ mLastDescriptors = result;
+ }
+ });
+ if (ret.isOk()) {
+ return retval == Result::OK ? OK : NO_INIT;
+ }
+ mLastDescriptors.resize(0);
+ return processReturn(__FUNCTION__, ret);
+}
+
+status_t EffectsFactoryHalHidl::queryNumberEffects(uint32_t *pNumEffects) {
+ status_t queryResult = queryAllDescriptors();
+ if (queryResult == OK) {
+ *pNumEffects = mLastDescriptors.size();
+ }
+ return queryResult;
+}
+
+status_t EffectsFactoryHalHidl::getDescriptor(
+ uint32_t index, effect_descriptor_t *pDescriptor) {
+ // TODO: We need somehow to track the changes on the server side
+ // or figure out how to convert everybody to query all the descriptors at once.
+ // TODO: check for nullptr
+ if (mLastDescriptors.size() == 0) {
+ status_t queryResult = queryAllDescriptors();
+ if (queryResult != OK) return queryResult;
+ }
+ if (index >= mLastDescriptors.size()) return NAME_NOT_FOUND;
+ EffectHalHidl::effectDescriptorToHal(mLastDescriptors[index], pDescriptor);
+ return OK;
+}
+
+status_t EffectsFactoryHalHidl::getDescriptor(
+ const effect_uuid_t *pEffectUuid, effect_descriptor_t *pDescriptor) {
+ // TODO: check for nullptr
+ if (mEffectsFactory == 0) return NO_INIT;
+ Uuid hidlUuid;
+ HidlUtils::uuidFromHal(*pEffectUuid, &hidlUuid);
+ Result retval = Result::NOT_INITIALIZED;
+ Return<void> ret = mEffectsFactory->getDescriptor(hidlUuid,
+ [&](Result r, const EffectDescriptor& result) {
+ retval = r;
+ if (retval == Result::OK) {
+ EffectHalHidl::effectDescriptorToHal(result, pDescriptor);
+ }
+ });
+ if (ret.isOk()) {
+ if (retval == Result::OK) return OK;
+ else if (retval == Result::INVALID_ARGUMENTS) return NAME_NOT_FOUND;
+ else return NO_INIT;
+ }
+ return processReturn(__FUNCTION__, ret);
+}
+
+status_t EffectsFactoryHalHidl::createEffect(
+ const effect_uuid_t *pEffectUuid, int32_t sessionId, int32_t ioId,
+ sp<EffectHalInterface> *effect) {
+ if (mEffectsFactory == 0) return NO_INIT;
+ Uuid hidlUuid;
+ HidlUtils::uuidFromHal(*pEffectUuid, &hidlUuid);
+ Result retval = Result::NOT_INITIALIZED;
+ Return<void> ret = mEffectsFactory->createEffect(
+ hidlUuid, sessionId, ioId,
+ [&](Result r, const sp<IEffect>& result, uint64_t effectId) {
+ retval = r;
+ if (retval == Result::OK) {
+ *effect = new EffectHalHidl(result, effectId);
+ }
+ });
+ if (ret.isOk()) {
+ if (retval == Result::OK) return OK;
+ else if (retval == Result::INVALID_ARGUMENTS) return NAME_NOT_FOUND;
+ else return NO_INIT;
+ }
+ return processReturn(__FUNCTION__, ret);
+}
+
+status_t EffectsFactoryHalHidl::dumpEffects(int fd) {
+ if (mEffectsFactory == 0) return NO_INIT;
+ native_handle_t* hidlHandle = native_handle_create(1, 0);
+ hidlHandle->data[0] = fd;
+ Return<void> ret = mEffectsFactory->debugDump(hidlHandle);
+ native_handle_delete(hidlHandle);
+ return processReturn(__FUNCTION__, ret);
+}
+
+status_t EffectsFactoryHalHidl::allocateBuffer(size_t size, sp<EffectBufferHalInterface>* buffer) {
+ return EffectBufferHalHidl::allocate(size, buffer);
+}
+
+status_t EffectsFactoryHalHidl::mirrorBuffer(void* external, size_t size,
+ sp<EffectBufferHalInterface>* buffer) {
+ return EffectBufferHalHidl::mirror(external, size, buffer);
+}
+
+
+} // namespace android
diff --git a/media/libaudiohal/2.0/EffectsFactoryHalHidl.h b/media/libaudiohal/2.0/EffectsFactoryHalHidl.h
new file mode 100644
index 0000000..82b5481
--- /dev/null
+++ b/media/libaudiohal/2.0/EffectsFactoryHalHidl.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_EFFECTS_FACTORY_HAL_HIDL_H
+#define ANDROID_HARDWARE_EFFECTS_FACTORY_HAL_HIDL_H
+
+#include <android/hardware/audio/effect/2.0/IEffectsFactory.h>
+#include <android/hardware/audio/effect/2.0/types.h>
+#include <media/audiohal/EffectsFactoryHalInterface.h>
+
+#include "ConversionHelperHidl.h"
+
+namespace android {
+
+using ::android::hardware::audio::effect::V2_0::EffectDescriptor;
+using ::android::hardware::audio::effect::V2_0::IEffectsFactory;
+using ::android::hardware::hidl_vec;
+
+class EffectsFactoryHalHidl : public EffectsFactoryHalInterface, public ConversionHelperHidl
+{
+ public:
+ // Returns the number of different effects in all loaded libraries.
+ virtual status_t queryNumberEffects(uint32_t *pNumEffects);
+
+ // Returns a descriptor of the next available effect.
+ virtual status_t getDescriptor(uint32_t index,
+ effect_descriptor_t *pDescriptor);
+
+ virtual status_t getDescriptor(const effect_uuid_t *pEffectUuid,
+ effect_descriptor_t *pDescriptor);
+
+ // Creates an effect engine of the specified type.
+ // To release the effect engine, it is necessary to release references
+ // to the returned effect object.
+ virtual status_t createEffect(const effect_uuid_t *pEffectUuid,
+ int32_t sessionId, int32_t ioId,
+ sp<EffectHalInterface> *effect);
+
+ virtual status_t dumpEffects(int fd);
+
+ status_t allocateBuffer(size_t size, sp<EffectBufferHalInterface>* buffer) override;
+ status_t mirrorBuffer(void* external, size_t size,
+ sp<EffectBufferHalInterface>* buffer) override;
+
+ private:
+ friend class EffectsFactoryHalInterface;
+
+ sp<IEffectsFactory> mEffectsFactory;
+ hidl_vec<EffectDescriptor> mLastDescriptors;
+
+ // Can not be constructed directly by clients.
+ EffectsFactoryHalHidl();
+ virtual ~EffectsFactoryHalHidl();
+
+ status_t queryAllDescriptors();
+};
+
+} // namespace android
+
+#endif // ANDROID_HARDWARE_EFFECTS_FACTORY_HAL_HIDL_H
diff --git a/media/libaudiohal/StreamHalHidl.cpp b/media/libaudiohal/2.0/StreamHalHidl.cpp
similarity index 100%
rename from media/libaudiohal/StreamHalHidl.cpp
rename to media/libaudiohal/2.0/StreamHalHidl.cpp
diff --git a/media/libaudiohal/StreamHalHidl.h b/media/libaudiohal/2.0/StreamHalHidl.h
similarity index 100%
rename from media/libaudiohal/StreamHalHidl.h
rename to media/libaudiohal/2.0/StreamHalHidl.h
diff --git a/media/libaudiohal/StreamHalLocal.cpp b/media/libaudiohal/2.0/StreamHalLocal.cpp
similarity index 100%
rename from media/libaudiohal/StreamHalLocal.cpp
rename to media/libaudiohal/2.0/StreamHalLocal.cpp
diff --git a/media/libaudiohal/StreamHalLocal.h b/media/libaudiohal/2.0/StreamHalLocal.h
similarity index 100%
rename from media/libaudiohal/StreamHalLocal.h
rename to media/libaudiohal/2.0/StreamHalLocal.h
diff --git a/media/libaudiohal/StreamPowerLog.h b/media/libaudiohal/2.0/StreamPowerLog.h
similarity index 100%
rename from media/libaudiohal/StreamPowerLog.h
rename to media/libaudiohal/2.0/StreamPowerLog.h
diff --git a/media/libaudiohal/Android.bp b/media/libaudiohal/Android.bp
index 700de8e..7ecb9d8 100644
--- a/media/libaudiohal/Android.bp
+++ b/media/libaudiohal/Android.bp
@@ -2,46 +2,52 @@
name: "libaudiohal",
srcs: [
- "DeviceHalLocal.cpp",
- "DevicesFactoryHalHybrid.cpp",
- "DevicesFactoryHalLocal.cpp",
- "StreamHalLocal.cpp",
-
- "ConversionHelperHidl.cpp",
- "HalDeathHandlerHidl.cpp",
- "DeviceHalHidl.cpp",
- "DevicesFactoryHalHidl.cpp",
- "EffectBufferHalHidl.cpp",
- "EffectHalHidl.cpp",
- "EffectsFactoryHalHidl.cpp",
- "StreamHalHidl.cpp",
+ "DevicesFactoryHalInterface.cpp",
+ "EffectsFactoryHalInterface.cpp",
],
cflags: [
"-Wall",
"-Werror",
],
- export_include_dirs: ["include"],
shared_libs: [
- "libaudioutils",
- "libcutils",
- "liblog",
- "libutils",
- "libhardware",
- "libbase",
- "libfmq",
- "libhwbinder",
- "libhidlbase",
- "libhidlmemory",
- "libhidltransport",
"android.hardware.audio@2.0",
- "android.hardware.audio.common@2.0",
- "android.hardware.audio.common@2.0-util",
"android.hardware.audio.effect@2.0",
- "android.hidl.allocator@1.0",
- "android.hidl.memory@1.0",
- "libmedia_helper",
- "libmediautils",
+ "libaudiohal@2.0",
+ "libutils",
],
+
+ header_libs: [
+ "libaudiohal_headers"
+ ]
+}
+
+cc_library_shared {
+ name: "libaudiohal_deathhandler",
+
+ srcs: [
+ "HalDeathHandlerHidl.cpp",
+ ],
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+
+ shared_libs: [
+ "libhidlbase",
+ "libutils",
+ "liblog",
+ ],
+
+ header_libs: [
+ "libaudiohal_headers"
+ ]
+}
+
+cc_library_headers {
+ name: "libaudiohal_headers",
+
+ export_include_dirs: ["include"],
}
diff --git a/media/libaudiohal/DeviceHalHidl.cpp b/media/libaudiohal/DeviceHalHidl.cpp
deleted file mode 100644
index 49ef991..0000000
--- a/media/libaudiohal/DeviceHalHidl.cpp
+++ /dev/null
@@ -1,357 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <stdio.h>
-
-#define LOG_TAG "DeviceHalHidl"
-//#define LOG_NDEBUG 0
-
-#include <android/hardware/audio/2.0/IPrimaryDevice.h>
-#include <cutils/native_handle.h>
-#include <hwbinder/IPCThreadState.h>
-#include <utils/Log.h>
-
-#include "DeviceHalHidl.h"
-#include "HidlUtils.h"
-#include "StreamHalHidl.h"
-
-using ::android::hardware::audio::common::V2_0::AudioConfig;
-using ::android::hardware::audio::common::V2_0::AudioDevice;
-using ::android::hardware::audio::common::V2_0::AudioInputFlag;
-using ::android::hardware::audio::common::V2_0::AudioOutputFlag;
-using ::android::hardware::audio::common::V2_0::AudioPatchHandle;
-using ::android::hardware::audio::common::V2_0::AudioPort;
-using ::android::hardware::audio::common::V2_0::AudioPortConfig;
-using ::android::hardware::audio::common::V2_0::AudioMode;
-using ::android::hardware::audio::common::V2_0::AudioSource;
-using ::android::hardware::audio::V2_0::DeviceAddress;
-using ::android::hardware::audio::V2_0::IPrimaryDevice;
-using ::android::hardware::audio::V2_0::ParameterValue;
-using ::android::hardware::audio::V2_0::Result;
-using ::android::hardware::hidl_string;
-using ::android::hardware::hidl_vec;
-
-namespace android {
-
-namespace {
-
-status_t deviceAddressFromHal(
- audio_devices_t device, const char* halAddress, DeviceAddress* address) {
- address->device = AudioDevice(device);
-
- if (address == nullptr || strnlen(halAddress, AUDIO_DEVICE_MAX_ADDRESS_LEN) == 0) {
- return OK;
- }
- const bool isInput = (device & AUDIO_DEVICE_BIT_IN) != 0;
- if (isInput) device &= ~AUDIO_DEVICE_BIT_IN;
- if ((!isInput && (device & AUDIO_DEVICE_OUT_ALL_A2DP) != 0)
- || (isInput && (device & AUDIO_DEVICE_IN_BLUETOOTH_A2DP) != 0)) {
- int status = sscanf(halAddress,
- "%hhX:%hhX:%hhX:%hhX:%hhX:%hhX",
- &address->address.mac[0], &address->address.mac[1], &address->address.mac[2],
- &address->address.mac[3], &address->address.mac[4], &address->address.mac[5]);
- return status == 6 ? OK : BAD_VALUE;
- } else if ((!isInput && (device & AUDIO_DEVICE_OUT_IP) != 0)
- || (isInput && (device & AUDIO_DEVICE_IN_IP) != 0)) {
- int status = sscanf(halAddress,
- "%hhu.%hhu.%hhu.%hhu",
- &address->address.ipv4[0], &address->address.ipv4[1],
- &address->address.ipv4[2], &address->address.ipv4[3]);
- return status == 4 ? OK : BAD_VALUE;
- } else if ((!isInput && (device & AUDIO_DEVICE_OUT_ALL_USB)) != 0
- || (isInput && (device & AUDIO_DEVICE_IN_ALL_USB)) != 0) {
- int status = sscanf(halAddress,
- "card=%d;device=%d",
- &address->address.alsa.card, &address->address.alsa.device);
- return status == 2 ? OK : BAD_VALUE;
- } else if ((!isInput && (device & AUDIO_DEVICE_OUT_BUS) != 0)
- || (isInput && (device & AUDIO_DEVICE_IN_BUS) != 0)) {
- if (halAddress != NULL) {
- address->busAddress = halAddress;
- return OK;
- }
- return BAD_VALUE;
- } else if ((!isInput && (device & AUDIO_DEVICE_OUT_REMOTE_SUBMIX)) != 0
- || (isInput && (device & AUDIO_DEVICE_IN_REMOTE_SUBMIX) != 0)) {
- if (halAddress != NULL) {
- address->rSubmixAddress = halAddress;
- return OK;
- }
- return BAD_VALUE;
- }
- return OK;
-}
-
-} // namespace
-
-DeviceHalHidl::DeviceHalHidl(const sp<IDevice>& device)
- : ConversionHelperHidl("Device"), mDevice(device),
- mPrimaryDevice(IPrimaryDevice::castFrom(device)) {
-}
-
-DeviceHalHidl::~DeviceHalHidl() {
- if (mDevice != 0) {
- mDevice.clear();
- hardware::IPCThreadState::self()->flushCommands();
- }
-}
-
-status_t DeviceHalHidl::getSupportedDevices(uint32_t*) {
- // Obsolete.
- return INVALID_OPERATION;
-}
-
-status_t DeviceHalHidl::initCheck() {
- if (mDevice == 0) return NO_INIT;
- return processReturn("initCheck", mDevice->initCheck());
-}
-
-status_t DeviceHalHidl::setVoiceVolume(float volume) {
- if (mDevice == 0) return NO_INIT;
- if (mPrimaryDevice == 0) return INVALID_OPERATION;
- return processReturn("setVoiceVolume", mPrimaryDevice->setVoiceVolume(volume));
-}
-
-status_t DeviceHalHidl::setMasterVolume(float volume) {
- if (mDevice == 0) return NO_INIT;
- if (mPrimaryDevice == 0) return INVALID_OPERATION;
- return processReturn("setMasterVolume", mPrimaryDevice->setMasterVolume(volume));
-}
-
-status_t DeviceHalHidl::getMasterVolume(float *volume) {
- if (mDevice == 0) return NO_INIT;
- if (mPrimaryDevice == 0) return INVALID_OPERATION;
- Result retval;
- Return<void> ret = mPrimaryDevice->getMasterVolume(
- [&](Result r, float v) {
- retval = r;
- if (retval == Result::OK) {
- *volume = v;
- }
- });
- return processReturn("getMasterVolume", ret, retval);
-}
-
-status_t DeviceHalHidl::setMode(audio_mode_t mode) {
- if (mDevice == 0) return NO_INIT;
- if (mPrimaryDevice == 0) return INVALID_OPERATION;
- return processReturn("setMode", mPrimaryDevice->setMode(AudioMode(mode)));
-}
-
-status_t DeviceHalHidl::setMicMute(bool state) {
- if (mDevice == 0) return NO_INIT;
- return processReturn("setMicMute", mDevice->setMicMute(state));
-}
-
-status_t DeviceHalHidl::getMicMute(bool *state) {
- if (mDevice == 0) return NO_INIT;
- Result retval;
- Return<void> ret = mDevice->getMicMute(
- [&](Result r, bool mute) {
- retval = r;
- if (retval == Result::OK) {
- *state = mute;
- }
- });
- return processReturn("getMicMute", ret, retval);
-}
-
-status_t DeviceHalHidl::setMasterMute(bool state) {
- if (mDevice == 0) return NO_INIT;
- return processReturn("setMasterMute", mDevice->setMasterMute(state));
-}
-
-status_t DeviceHalHidl::getMasterMute(bool *state) {
- if (mDevice == 0) return NO_INIT;
- Result retval;
- Return<void> ret = mDevice->getMasterMute(
- [&](Result r, bool mute) {
- retval = r;
- if (retval == Result::OK) {
- *state = mute;
- }
- });
- return processReturn("getMasterMute", ret, retval);
-}
-
-status_t DeviceHalHidl::setParameters(const String8& kvPairs) {
- if (mDevice == 0) return NO_INIT;
- hidl_vec<ParameterValue> hidlParams;
- status_t status = parametersFromHal(kvPairs, &hidlParams);
- if (status != OK) return status;
- return processReturn("setParameters", mDevice->setParameters(hidlParams));
-}
-
-status_t DeviceHalHidl::getParameters(const String8& keys, String8 *values) {
- values->clear();
- if (mDevice == 0) return NO_INIT;
- hidl_vec<hidl_string> hidlKeys;
- status_t status = keysFromHal(keys, &hidlKeys);
- if (status != OK) return status;
- Result retval;
- Return<void> ret = mDevice->getParameters(
- hidlKeys,
- [&](Result r, const hidl_vec<ParameterValue>& parameters) {
- retval = r;
- if (retval == Result::OK) {
- parametersToHal(parameters, values);
- }
- });
- return processReturn("getParameters", ret, retval);
-}
-
-status_t DeviceHalHidl::getInputBufferSize(
- const struct audio_config *config, size_t *size) {
- if (mDevice == 0) return NO_INIT;
- AudioConfig hidlConfig;
- HidlUtils::audioConfigFromHal(*config, &hidlConfig);
- Result retval;
- Return<void> ret = mDevice->getInputBufferSize(
- hidlConfig,
- [&](Result r, uint64_t bufferSize) {
- retval = r;
- if (retval == Result::OK) {
- *size = static_cast<size_t>(bufferSize);
- }
- });
- return processReturn("getInputBufferSize", ret, retval);
-}
-
-status_t DeviceHalHidl::openOutputStream(
- audio_io_handle_t handle,
- audio_devices_t devices,
- audio_output_flags_t flags,
- struct audio_config *config,
- const char *address,
- sp<StreamOutHalInterface> *outStream) {
- if (mDevice == 0) return NO_INIT;
- DeviceAddress hidlDevice;
- status_t status = deviceAddressFromHal(devices, address, &hidlDevice);
- if (status != OK) return status;
- AudioConfig hidlConfig;
- HidlUtils::audioConfigFromHal(*config, &hidlConfig);
- Result retval = Result::NOT_INITIALIZED;
- Return<void> ret = mDevice->openOutputStream(
- handle,
- hidlDevice,
- hidlConfig,
- AudioOutputFlag(flags),
- [&](Result r, const sp<IStreamOut>& result, const AudioConfig& suggestedConfig) {
- retval = r;
- if (retval == Result::OK) {
- *outStream = new StreamOutHalHidl(result);
- }
- HidlUtils::audioConfigToHal(suggestedConfig, config);
- });
- return processReturn("openOutputStream", ret, retval);
-}
-
-status_t DeviceHalHidl::openInputStream(
- audio_io_handle_t handle,
- audio_devices_t devices,
- struct audio_config *config,
- audio_input_flags_t flags,
- const char *address,
- audio_source_t source,
- sp<StreamInHalInterface> *inStream) {
- if (mDevice == 0) return NO_INIT;
- DeviceAddress hidlDevice;
- status_t status = deviceAddressFromHal(devices, address, &hidlDevice);
- if (status != OK) return status;
- AudioConfig hidlConfig;
- HidlUtils::audioConfigFromHal(*config, &hidlConfig);
- Result retval = Result::NOT_INITIALIZED;
- Return<void> ret = mDevice->openInputStream(
- handle,
- hidlDevice,
- hidlConfig,
- AudioInputFlag(flags),
- AudioSource(source),
- [&](Result r, const sp<IStreamIn>& result, const AudioConfig& suggestedConfig) {
- retval = r;
- if (retval == Result::OK) {
- *inStream = new StreamInHalHidl(result);
- }
- HidlUtils::audioConfigToHal(suggestedConfig, config);
- });
- return processReturn("openInputStream", ret, retval);
-}
-
-status_t DeviceHalHidl::supportsAudioPatches(bool *supportsPatches) {
- if (mDevice == 0) return NO_INIT;
- return processReturn("supportsAudioPatches", mDevice->supportsAudioPatches(), supportsPatches);
-}
-
-status_t DeviceHalHidl::createAudioPatch(
- unsigned int num_sources,
- const struct audio_port_config *sources,
- unsigned int num_sinks,
- const struct audio_port_config *sinks,
- audio_patch_handle_t *patch) {
- if (mDevice == 0) return NO_INIT;
- hidl_vec<AudioPortConfig> hidlSources, hidlSinks;
- HidlUtils::audioPortConfigsFromHal(num_sources, sources, &hidlSources);
- HidlUtils::audioPortConfigsFromHal(num_sinks, sinks, &hidlSinks);
- Result retval;
- Return<void> ret = mDevice->createAudioPatch(
- hidlSources, hidlSinks,
- [&](Result r, AudioPatchHandle hidlPatch) {
- retval = r;
- if (retval == Result::OK) {
- *patch = static_cast<audio_patch_handle_t>(hidlPatch);
- }
- });
- return processReturn("createAudioPatch", ret, retval);
-}
-
-status_t DeviceHalHidl::releaseAudioPatch(audio_patch_handle_t patch) {
- if (mDevice == 0) return NO_INIT;
- return processReturn("releaseAudioPatch", mDevice->releaseAudioPatch(patch));
-}
-
-status_t DeviceHalHidl::getAudioPort(struct audio_port *port) {
- if (mDevice == 0) return NO_INIT;
- AudioPort hidlPort;
- HidlUtils::audioPortFromHal(*port, &hidlPort);
- Result retval;
- Return<void> ret = mDevice->getAudioPort(
- hidlPort,
- [&](Result r, const AudioPort& p) {
- retval = r;
- if (retval == Result::OK) {
- HidlUtils::audioPortToHal(p, port);
- }
- });
- return processReturn("getAudioPort", ret, retval);
-}
-
-status_t DeviceHalHidl::setAudioPortConfig(const struct audio_port_config *config) {
- if (mDevice == 0) return NO_INIT;
- AudioPortConfig hidlConfig;
- HidlUtils::audioPortConfigFromHal(*config, &hidlConfig);
- return processReturn("setAudioPortConfig", mDevice->setAudioPortConfig(hidlConfig));
-}
-
-status_t DeviceHalHidl::dump(int fd) {
- if (mDevice == 0) return NO_INIT;
- native_handle_t* hidlHandle = native_handle_create(1, 0);
- hidlHandle->data[0] = fd;
- Return<void> ret = mDevice->debugDump(hidlHandle);
- native_handle_delete(hidlHandle);
- return processReturn("dump", ret);
-}
-
-} // namespace android
diff --git a/media/libaudiohal/DevicesFactoryHalHybrid.cpp b/media/libaudiohal/DevicesFactoryHalHybrid.cpp
deleted file mode 100644
index 8dc1434..0000000
--- a/media/libaudiohal/DevicesFactoryHalHybrid.cpp
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "DevicesFactoryHalHybrid"
-//#define LOG_NDEBUG 0
-
-#include "DevicesFactoryHalHybrid.h"
-#include "DevicesFactoryHalLocal.h"
-#include "DevicesFactoryHalHidl.h"
-
-namespace android {
-
-// static
-sp<DevicesFactoryHalInterface> DevicesFactoryHalInterface::create() {
- return new DevicesFactoryHalHybrid();
-}
-
-DevicesFactoryHalHybrid::DevicesFactoryHalHybrid()
- : mLocalFactory(new DevicesFactoryHalLocal()),
- mHidlFactory(new DevicesFactoryHalHidl()) {
-}
-
-DevicesFactoryHalHybrid::~DevicesFactoryHalHybrid() {
-}
-
-status_t DevicesFactoryHalHybrid::openDevice(const char *name, sp<DeviceHalInterface> *device) {
- if (mHidlFactory != 0 && strcmp(AUDIO_HARDWARE_MODULE_ID_A2DP, name) != 0) {
- return mHidlFactory->openDevice(name, device);
- }
- return mLocalFactory->openDevice(name, device);
-}
-
-} // namespace android
diff --git a/media/libaudiohal/DevicesFactoryHalInterface.cpp b/media/libaudiohal/DevicesFactoryHalInterface.cpp
new file mode 100644
index 0000000..cfec3d6
--- /dev/null
+++ b/media/libaudiohal/DevicesFactoryHalInterface.cpp
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "DevicesFactoryHalHybrid.h"
+#include <android/hardware/audio/2.0/IDevicesFactory.h>
+
+using namespace ::android::hardware::audio;
+
+namespace android {
+
+// static
+sp<DevicesFactoryHalInterface> DevicesFactoryHalInterface::create() {
+ if (V2_0::IDevicesFactory::getService() != nullptr) {
+ return new DevicesFactoryHalHybrid();
+ }
+ return nullptr;
+}
+
+} // namespace android
diff --git a/media/libaudiohal/EffectBufferHalHidl.cpp b/media/libaudiohal/EffectBufferHalHidl.cpp
deleted file mode 100644
index 8b5201b..0000000
--- a/media/libaudiohal/EffectBufferHalHidl.cpp
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <atomic>
-
-#define LOG_TAG "EffectBufferHalHidl"
-//#define LOG_NDEBUG 0
-
-#include <android/hidl/allocator/1.0/IAllocator.h>
-#include <hidlmemory/mapping.h>
-#include <utils/Log.h>
-
-#include "ConversionHelperHidl.h"
-#include "EffectBufferHalHidl.h"
-
-using ::android::hardware::Return;
-using ::android::hidl::allocator::V1_0::IAllocator;
-
-namespace android {
-
-// static
-uint64_t EffectBufferHalHidl::makeUniqueId() {
- static std::atomic<uint64_t> counter{1};
- return counter++;
-}
-
-// static
-status_t EffectBufferHalInterface::allocate(
- size_t size, sp<EffectBufferHalInterface>* buffer) {
- return mirror(nullptr, size, buffer);
-}
-
-// static
-status_t EffectBufferHalInterface::mirror(
- void* external, size_t size, sp<EffectBufferHalInterface>* buffer) {
- sp<EffectBufferHalInterface> tempBuffer = new EffectBufferHalHidl(size);
- status_t result = static_cast<EffectBufferHalHidl*>(tempBuffer.get())->init();
- if (result == OK) {
- tempBuffer->setExternalData(external);
- *buffer = tempBuffer;
- }
- return result;
-}
-
-EffectBufferHalHidl::EffectBufferHalHidl(size_t size)
- : mBufferSize(size), mFrameCountChanged(false),
- mExternalData(nullptr), mAudioBuffer{0, {nullptr}} {
- mHidlBuffer.id = makeUniqueId();
- mHidlBuffer.frameCount = 0;
-}
-
-EffectBufferHalHidl::~EffectBufferHalHidl() {
-}
-
-status_t EffectBufferHalHidl::init() {
- sp<IAllocator> ashmem = IAllocator::getService("ashmem");
- if (ashmem == 0) {
- ALOGE("Failed to retrieve ashmem allocator service");
- return NO_INIT;
- }
- status_t retval = NO_MEMORY;
- Return<void> result = ashmem->allocate(
- mBufferSize,
- [&](bool success, const hidl_memory& memory) {
- if (success) {
- mHidlBuffer.data = memory;
- retval = OK;
- }
- });
- if (result.isOk() && retval == OK) {
- mMemory = hardware::mapMemory(mHidlBuffer.data);
- if (mMemory != 0) {
- mMemory->update();
- mAudioBuffer.raw = static_cast<void*>(mMemory->getPointer());
- memset(mAudioBuffer.raw, 0, mMemory->getSize());
- mMemory->commit();
- } else {
- ALOGE("Failed to map allocated ashmem");
- retval = NO_MEMORY;
- }
- } else {
- ALOGE("Failed to allocate %d bytes from ashmem", (int)mBufferSize);
- }
- return result.isOk() ? retval : FAILED_TRANSACTION;
-}
-
-audio_buffer_t* EffectBufferHalHidl::audioBuffer() {
- return &mAudioBuffer;
-}
-
-void* EffectBufferHalHidl::externalData() const {
- return mExternalData;
-}
-
-void EffectBufferHalHidl::setFrameCount(size_t frameCount) {
- mHidlBuffer.frameCount = frameCount;
- mAudioBuffer.frameCount = frameCount;
- mFrameCountChanged = true;
-}
-
-bool EffectBufferHalHidl::checkFrameCountChange() {
- bool result = mFrameCountChanged;
- mFrameCountChanged = false;
- return result;
-}
-
-void EffectBufferHalHidl::setExternalData(void* external) {
- mExternalData = external;
-}
-
-void EffectBufferHalHidl::update() {
- update(mBufferSize);
-}
-
-void EffectBufferHalHidl::commit() {
- commit(mBufferSize);
-}
-
-void EffectBufferHalHidl::update(size_t size) {
- if (mExternalData == nullptr) return;
- mMemory->update();
- if (size > mBufferSize) size = mBufferSize;
- memcpy(mAudioBuffer.raw, mExternalData, size);
- mMemory->commit();
-}
-
-void EffectBufferHalHidl::commit(size_t size) {
- if (mExternalData == nullptr) return;
- if (size > mBufferSize) size = mBufferSize;
- memcpy(mExternalData, mAudioBuffer.raw, size);
-}
-
-} // namespace android
diff --git a/media/libaudiohal/EffectBufferHalHidl.h b/media/libaudiohal/EffectBufferHalHidl.h
deleted file mode 100644
index d7a43ae..0000000
--- a/media/libaudiohal/EffectBufferHalHidl.h
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef ANDROID_HARDWARE_EFFECT_BUFFER_HAL_HIDL_H
-#define ANDROID_HARDWARE_EFFECT_BUFFER_HAL_HIDL_H
-
-#include <android/hardware/audio/effect/2.0/types.h>
-#include <android/hidl/memory/1.0/IMemory.h>
-#include <hidl/HidlSupport.h>
-#include <media/audiohal/EffectBufferHalInterface.h>
-#include <system/audio_effect.h>
-
-using android::hardware::audio::effect::V2_0::AudioBuffer;
-using android::hardware::hidl_memory;
-using android::hidl::memory::V1_0::IMemory;
-
-namespace android {
-
-class EffectBufferHalHidl : public EffectBufferHalInterface
-{
- public:
- virtual audio_buffer_t* audioBuffer();
- virtual void* externalData() const;
-
- virtual size_t getSize() const override { return mBufferSize; }
-
- virtual void setExternalData(void* external);
- virtual void setFrameCount(size_t frameCount);
- virtual bool checkFrameCountChange();
-
- virtual void update();
- virtual void commit();
- virtual void update(size_t size);
- virtual void commit(size_t size);
-
- const AudioBuffer& hidlBuffer() const { return mHidlBuffer; }
-
- private:
- friend class EffectBufferHalInterface;
-
- static uint64_t makeUniqueId();
-
- const size_t mBufferSize;
- bool mFrameCountChanged;
- void* mExternalData;
- AudioBuffer mHidlBuffer;
- sp<IMemory> mMemory;
- audio_buffer_t mAudioBuffer;
-
- // Can not be constructed directly by clients.
- explicit EffectBufferHalHidl(size_t size);
-
- virtual ~EffectBufferHalHidl();
-
- status_t init();
-};
-
-} // namespace android
-
-#endif // ANDROID_HARDWARE_EFFECT_BUFFER_HAL_HIDL_H
diff --git a/media/libaudiohal/EffectHalHidl.cpp b/media/libaudiohal/EffectHalHidl.cpp
deleted file mode 100644
index f4d1958..0000000
--- a/media/libaudiohal/EffectHalHidl.cpp
+++ /dev/null
@@ -1,337 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "EffectHalHidl"
-//#define LOG_NDEBUG 0
-
-#include <hwbinder/IPCThreadState.h>
-#include <media/EffectsFactoryApi.h>
-#include <utils/Log.h>
-
-#include "ConversionHelperHidl.h"
-#include "EffectBufferHalHidl.h"
-#include "EffectHalHidl.h"
-#include "HidlUtils.h"
-
-using ::android::hardware::audio::effect::V2_0::AudioBuffer;
-using ::android::hardware::audio::effect::V2_0::EffectBufferAccess;
-using ::android::hardware::audio::effect::V2_0::EffectConfigParameters;
-using ::android::hardware::audio::effect::V2_0::MessageQueueFlagBits;
-using ::android::hardware::audio::effect::V2_0::Result;
-using ::android::hardware::audio::common::V2_0::AudioChannelMask;
-using ::android::hardware::audio::common::V2_0::AudioFormat;
-using ::android::hardware::hidl_vec;
-using ::android::hardware::MQDescriptorSync;
-using ::android::hardware::Return;
-
-namespace android {
-
-EffectHalHidl::EffectHalHidl(const sp<IEffect>& effect, uint64_t effectId)
- : mEffect(effect), mEffectId(effectId), mBuffersChanged(true), mEfGroup(nullptr) {
-}
-
-EffectHalHidl::~EffectHalHidl() {
- if (mEffect != 0) {
- close();
- mEffect.clear();
- hardware::IPCThreadState::self()->flushCommands();
- }
- if (mEfGroup) {
- EventFlag::deleteEventFlag(&mEfGroup);
- }
-}
-
-// static
-void EffectHalHidl::effectDescriptorToHal(
- const EffectDescriptor& descriptor, effect_descriptor_t* halDescriptor) {
- HidlUtils::uuidToHal(descriptor.type, &halDescriptor->type);
- HidlUtils::uuidToHal(descriptor.uuid, &halDescriptor->uuid);
- halDescriptor->flags = static_cast<uint32_t>(descriptor.flags);
- halDescriptor->cpuLoad = descriptor.cpuLoad;
- halDescriptor->memoryUsage = descriptor.memoryUsage;
- memcpy(halDescriptor->name, descriptor.name.data(), descriptor.name.size());
- memcpy(halDescriptor->implementor,
- descriptor.implementor.data(), descriptor.implementor.size());
-}
-
-// TODO(mnaganov): These buffer conversion functions should be shared with Effect wrapper
-// via HidlUtils. Move them there when hardware/interfaces will get un-frozen again.
-
-// static
-void EffectHalHidl::effectBufferConfigFromHal(
- const buffer_config_t& halConfig, EffectBufferConfig* config) {
- config->samplingRateHz = halConfig.samplingRate;
- config->channels = AudioChannelMask(halConfig.channels);
- config->format = AudioFormat(halConfig.format);
- config->accessMode = EffectBufferAccess(halConfig.accessMode);
- config->mask = EffectConfigParameters(halConfig.mask);
-}
-
-// static
-void EffectHalHidl::effectBufferConfigToHal(
- const EffectBufferConfig& config, buffer_config_t* halConfig) {
- halConfig->buffer.frameCount = 0;
- halConfig->buffer.raw = NULL;
- halConfig->samplingRate = config.samplingRateHz;
- halConfig->channels = static_cast<uint32_t>(config.channels);
- halConfig->bufferProvider.cookie = NULL;
- halConfig->bufferProvider.getBuffer = NULL;
- halConfig->bufferProvider.releaseBuffer = NULL;
- halConfig->format = static_cast<uint8_t>(config.format);
- halConfig->accessMode = static_cast<uint8_t>(config.accessMode);
- halConfig->mask = static_cast<uint8_t>(config.mask);
-}
-
-// static
-void EffectHalHidl::effectConfigFromHal(const effect_config_t& halConfig, EffectConfig* config) {
- effectBufferConfigFromHal(halConfig.inputCfg, &config->inputCfg);
- effectBufferConfigFromHal(halConfig.outputCfg, &config->outputCfg);
-}
-
-// static
-void EffectHalHidl::effectConfigToHal(const EffectConfig& config, effect_config_t* halConfig) {
- effectBufferConfigToHal(config.inputCfg, &halConfig->inputCfg);
- effectBufferConfigToHal(config.outputCfg, &halConfig->outputCfg);
-}
-
-// static
-status_t EffectHalHidl::analyzeResult(const Result& result) {
- switch (result) {
- case Result::OK: return OK;
- case Result::INVALID_ARGUMENTS: return BAD_VALUE;
- case Result::INVALID_STATE: return NOT_ENOUGH_DATA;
- case Result::NOT_INITIALIZED: return NO_INIT;
- case Result::NOT_SUPPORTED: return INVALID_OPERATION;
- case Result::RESULT_TOO_BIG: return NO_MEMORY;
- default: return NO_INIT;
- }
-}
-
-status_t EffectHalHidl::setInBuffer(const sp<EffectBufferHalInterface>& buffer) {
- if (!mBuffersChanged) {
- if (buffer.get() == nullptr || mInBuffer.get() == nullptr) {
- mBuffersChanged = buffer.get() != mInBuffer.get();
- } else {
- mBuffersChanged = buffer->audioBuffer() != mInBuffer->audioBuffer();
- }
- }
- mInBuffer = buffer;
- return OK;
-}
-
-status_t EffectHalHidl::setOutBuffer(const sp<EffectBufferHalInterface>& buffer) {
- if (!mBuffersChanged) {
- if (buffer.get() == nullptr || mOutBuffer.get() == nullptr) {
- mBuffersChanged = buffer.get() != mOutBuffer.get();
- } else {
- mBuffersChanged = buffer->audioBuffer() != mOutBuffer->audioBuffer();
- }
- }
- mOutBuffer = buffer;
- return OK;
-}
-
-status_t EffectHalHidl::process() {
- return processImpl(static_cast<uint32_t>(MessageQueueFlagBits::REQUEST_PROCESS));
-}
-
-status_t EffectHalHidl::processReverse() {
- return processImpl(static_cast<uint32_t>(MessageQueueFlagBits::REQUEST_PROCESS_REVERSE));
-}
-
-status_t EffectHalHidl::prepareForProcessing() {
- std::unique_ptr<StatusMQ> tempStatusMQ;
- Result retval;
- Return<void> ret = mEffect->prepareForProcessing(
- [&](Result r, const MQDescriptorSync<Result>& statusMQ) {
- retval = r;
- if (retval == Result::OK) {
- tempStatusMQ.reset(new StatusMQ(statusMQ));
- if (tempStatusMQ->isValid() && tempStatusMQ->getEventFlagWord()) {
- EventFlag::createEventFlag(tempStatusMQ->getEventFlagWord(), &mEfGroup);
- }
- }
- });
- if (!ret.isOk() || retval != Result::OK) {
- return ret.isOk() ? analyzeResult(retval) : FAILED_TRANSACTION;
- }
- if (!tempStatusMQ || !tempStatusMQ->isValid() || !mEfGroup) {
- ALOGE_IF(!tempStatusMQ, "Failed to obtain status message queue for effects");
- ALOGE_IF(tempStatusMQ && !tempStatusMQ->isValid(),
- "Status message queue for effects is invalid");
- ALOGE_IF(!mEfGroup, "Event flag creation for effects failed");
- return NO_INIT;
- }
- mStatusMQ = std::move(tempStatusMQ);
- return OK;
-}
-
-bool EffectHalHidl::needToResetBuffers() {
- if (mBuffersChanged) return true;
- bool inBufferFrameCountUpdated = mInBuffer->checkFrameCountChange();
- bool outBufferFrameCountUpdated = mOutBuffer->checkFrameCountChange();
- return inBufferFrameCountUpdated || outBufferFrameCountUpdated;
-}
-
-status_t EffectHalHidl::processImpl(uint32_t mqFlag) {
- if (mEffect == 0 || mInBuffer == 0 || mOutBuffer == 0) return NO_INIT;
- status_t status;
- if (!mStatusMQ && (status = prepareForProcessing()) != OK) {
- return status;
- }
- if (needToResetBuffers() && (status = setProcessBuffers()) != OK) {
- return status;
- }
- // The data is already in the buffers, just need to flush it and wake up the server side.
- std::atomic_thread_fence(std::memory_order_release);
- mEfGroup->wake(mqFlag);
- uint32_t efState = 0;
-retry:
- status_t ret = mEfGroup->wait(
- static_cast<uint32_t>(MessageQueueFlagBits::DONE_PROCESSING), &efState);
- if (efState & static_cast<uint32_t>(MessageQueueFlagBits::DONE_PROCESSING)) {
- Result retval = Result::NOT_INITIALIZED;
- mStatusMQ->read(&retval);
- if (retval == Result::OK || retval == Result::INVALID_STATE) {
- // Sync back the changed contents of the buffer.
- std::atomic_thread_fence(std::memory_order_acquire);
- }
- return analyzeResult(retval);
- }
- if (ret == -EAGAIN || ret == -EINTR) {
- // Spurious wakeup. This normally retries no more than once.
- goto retry;
- }
- return ret;
-}
-
-status_t EffectHalHidl::setProcessBuffers() {
- Return<Result> ret = mEffect->setProcessBuffers(
- static_cast<EffectBufferHalHidl*>(mInBuffer.get())->hidlBuffer(),
- static_cast<EffectBufferHalHidl*>(mOutBuffer.get())->hidlBuffer());
- if (ret.isOk() && ret == Result::OK) {
- mBuffersChanged = false;
- return OK;
- }
- return ret.isOk() ? analyzeResult(ret) : FAILED_TRANSACTION;
-}
-
-status_t EffectHalHidl::command(uint32_t cmdCode, uint32_t cmdSize, void *pCmdData,
- uint32_t *replySize, void *pReplyData) {
- if (mEffect == 0) return NO_INIT;
-
- // Special cases.
- if (cmdCode == EFFECT_CMD_SET_CONFIG || cmdCode == EFFECT_CMD_SET_CONFIG_REVERSE) {
- return setConfigImpl(cmdCode, cmdSize, pCmdData, replySize, pReplyData);
- } else if (cmdCode == EFFECT_CMD_GET_CONFIG || cmdCode == EFFECT_CMD_GET_CONFIG_REVERSE) {
- return getConfigImpl(cmdCode, replySize, pReplyData);
- }
-
- // Common case.
- hidl_vec<uint8_t> hidlData;
- if (pCmdData != nullptr && cmdSize > 0) {
- hidlData.setToExternal(reinterpret_cast<uint8_t*>(pCmdData), cmdSize);
- }
- status_t status;
- uint32_t replySizeStub = 0;
- if (replySize == nullptr || pReplyData == nullptr) replySize = &replySizeStub;
- Return<void> ret = mEffect->command(cmdCode, hidlData, *replySize,
- [&](int32_t s, const hidl_vec<uint8_t>& result) {
- status = s;
- if (status == 0) {
- if (*replySize > result.size()) *replySize = result.size();
- if (pReplyData != nullptr && *replySize > 0) {
- memcpy(pReplyData, &result[0], *replySize);
- }
- }
- });
- return ret.isOk() ? status : FAILED_TRANSACTION;
-}
-
-status_t EffectHalHidl::getDescriptor(effect_descriptor_t *pDescriptor) {
- if (mEffect == 0) return NO_INIT;
- Result retval = Result::NOT_INITIALIZED;
- Return<void> ret = mEffect->getDescriptor(
- [&](Result r, const EffectDescriptor& result) {
- retval = r;
- if (retval == Result::OK) {
- effectDescriptorToHal(result, pDescriptor);
- }
- });
- return ret.isOk() ? analyzeResult(retval) : FAILED_TRANSACTION;
-}
-
-status_t EffectHalHidl::close() {
- if (mEffect == 0) return NO_INIT;
- Return<Result> ret = mEffect->close();
- return ret.isOk() ? analyzeResult(ret) : FAILED_TRANSACTION;
-}
-
-status_t EffectHalHidl::getConfigImpl(
- uint32_t cmdCode, uint32_t *replySize, void *pReplyData) {
- if (replySize == NULL || *replySize != sizeof(effect_config_t) || pReplyData == NULL) {
- return BAD_VALUE;
- }
- status_t result = FAILED_TRANSACTION;
- Return<void> ret;
- if (cmdCode == EFFECT_CMD_GET_CONFIG) {
- ret = mEffect->getConfig([&] (Result r, const EffectConfig &hidlConfig) {
- result = analyzeResult(r);
- if (r == Result::OK) {
- effectConfigToHal(hidlConfig, static_cast<effect_config_t*>(pReplyData));
- }
- });
- } else {
- ret = mEffect->getConfigReverse([&] (Result r, const EffectConfig &hidlConfig) {
- result = analyzeResult(r);
- if (r == Result::OK) {
- effectConfigToHal(hidlConfig, static_cast<effect_config_t*>(pReplyData));
- }
- });
- }
- if (!ret.isOk()) {
- result = FAILED_TRANSACTION;
- }
- return result;
-}
-
-status_t EffectHalHidl::setConfigImpl(
- uint32_t cmdCode, uint32_t cmdSize, void *pCmdData, uint32_t *replySize, void *pReplyData) {
- if (pCmdData == NULL || cmdSize != sizeof(effect_config_t) ||
- replySize == NULL || *replySize != sizeof(int32_t) || pReplyData == NULL) {
- return BAD_VALUE;
- }
- const effect_config_t *halConfig = static_cast<effect_config_t*>(pCmdData);
- if (halConfig->inputCfg.bufferProvider.getBuffer != NULL ||
- halConfig->inputCfg.bufferProvider.releaseBuffer != NULL ||
- halConfig->outputCfg.bufferProvider.getBuffer != NULL ||
- halConfig->outputCfg.bufferProvider.releaseBuffer != NULL) {
- ALOGE("Buffer provider callbacks are not supported");
- }
- EffectConfig hidlConfig;
- effectConfigFromHal(*halConfig, &hidlConfig);
- Return<Result> ret = cmdCode == EFFECT_CMD_SET_CONFIG ?
- mEffect->setConfig(hidlConfig, nullptr, nullptr) :
- mEffect->setConfigReverse(hidlConfig, nullptr, nullptr);
- status_t result = FAILED_TRANSACTION;
- if (ret.isOk()) {
- result = analyzeResult(ret);
- *static_cast<int32_t*>(pReplyData) = result;
- }
- return result;
-}
-
-} // namespace android
diff --git a/media/libaudiohal/EffectsFactoryHalHidl.cpp b/media/libaudiohal/EffectsFactoryHalHidl.cpp
deleted file mode 100644
index a8081b7..0000000
--- a/media/libaudiohal/EffectsFactoryHalHidl.cpp
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "EffectsFactoryHalHidl"
-//#define LOG_NDEBUG 0
-
-#include <cutils/native_handle.h>
-
-#include "ConversionHelperHidl.h"
-#include "EffectHalHidl.h"
-#include "EffectsFactoryHalHidl.h"
-#include "HidlUtils.h"
-
-using ::android::hardware::audio::common::V2_0::Uuid;
-using ::android::hardware::audio::effect::V2_0::IEffect;
-using ::android::hardware::audio::effect::V2_0::Result;
-using ::android::hardware::Return;
-
-namespace android {
-
-// static
-sp<EffectsFactoryHalInterface> EffectsFactoryHalInterface::create() {
- return new EffectsFactoryHalHidl();
-}
-
-// static
-bool EffectsFactoryHalInterface::isNullUuid(const effect_uuid_t *pEffectUuid) {
- return memcmp(pEffectUuid, EFFECT_UUID_NULL, sizeof(effect_uuid_t)) == 0;
-}
-
-EffectsFactoryHalHidl::EffectsFactoryHalHidl() : ConversionHelperHidl("EffectsFactory") {
- mEffectsFactory = IEffectsFactory::getService();
- if (mEffectsFactory == 0) {
- ALOGE("Failed to obtain IEffectsFactory service, terminating process.");
- exit(1);
- }
-}
-
-EffectsFactoryHalHidl::~EffectsFactoryHalHidl() {
-}
-
-status_t EffectsFactoryHalHidl::queryAllDescriptors() {
- if (mEffectsFactory == 0) return NO_INIT;
- Result retval = Result::NOT_INITIALIZED;
- Return<void> ret = mEffectsFactory->getAllDescriptors(
- [&](Result r, const hidl_vec<EffectDescriptor>& result) {
- retval = r;
- if (retval == Result::OK) {
- mLastDescriptors = result;
- }
- });
- if (ret.isOk()) {
- return retval == Result::OK ? OK : NO_INIT;
- }
- mLastDescriptors.resize(0);
- return processReturn(__FUNCTION__, ret);
-}
-
-status_t EffectsFactoryHalHidl::queryNumberEffects(uint32_t *pNumEffects) {
- status_t queryResult = queryAllDescriptors();
- if (queryResult == OK) {
- *pNumEffects = mLastDescriptors.size();
- }
- return queryResult;
-}
-
-status_t EffectsFactoryHalHidl::getDescriptor(
- uint32_t index, effect_descriptor_t *pDescriptor) {
- // TODO: We need somehow to track the changes on the server side
- // or figure out how to convert everybody to query all the descriptors at once.
- // TODO: check for nullptr
- if (mLastDescriptors.size() == 0) {
- status_t queryResult = queryAllDescriptors();
- if (queryResult != OK) return queryResult;
- }
- if (index >= mLastDescriptors.size()) return NAME_NOT_FOUND;
- EffectHalHidl::effectDescriptorToHal(mLastDescriptors[index], pDescriptor);
- return OK;
-}
-
-status_t EffectsFactoryHalHidl::getDescriptor(
- const effect_uuid_t *pEffectUuid, effect_descriptor_t *pDescriptor) {
- // TODO: check for nullptr
- if (mEffectsFactory == 0) return NO_INIT;
- Uuid hidlUuid;
- HidlUtils::uuidFromHal(*pEffectUuid, &hidlUuid);
- Result retval = Result::NOT_INITIALIZED;
- Return<void> ret = mEffectsFactory->getDescriptor(hidlUuid,
- [&](Result r, const EffectDescriptor& result) {
- retval = r;
- if (retval == Result::OK) {
- EffectHalHidl::effectDescriptorToHal(result, pDescriptor);
- }
- });
- if (ret.isOk()) {
- if (retval == Result::OK) return OK;
- else if (retval == Result::INVALID_ARGUMENTS) return NAME_NOT_FOUND;
- else return NO_INIT;
- }
- return processReturn(__FUNCTION__, ret);
-}
-
-status_t EffectsFactoryHalHidl::createEffect(
- const effect_uuid_t *pEffectUuid, int32_t sessionId, int32_t ioId,
- sp<EffectHalInterface> *effect) {
- if (mEffectsFactory == 0) return NO_INIT;
- Uuid hidlUuid;
- HidlUtils::uuidFromHal(*pEffectUuid, &hidlUuid);
- Result retval = Result::NOT_INITIALIZED;
- Return<void> ret = mEffectsFactory->createEffect(
- hidlUuid, sessionId, ioId,
- [&](Result r, const sp<IEffect>& result, uint64_t effectId) {
- retval = r;
- if (retval == Result::OK) {
- *effect = new EffectHalHidl(result, effectId);
- }
- });
- if (ret.isOk()) {
- if (retval == Result::OK) return OK;
- else if (retval == Result::INVALID_ARGUMENTS) return NAME_NOT_FOUND;
- else return NO_INIT;
- }
- return processReturn(__FUNCTION__, ret);
-}
-
-status_t EffectsFactoryHalHidl::dumpEffects(int fd) {
- if (mEffectsFactory == 0) return NO_INIT;
- native_handle_t* hidlHandle = native_handle_create(1, 0);
- hidlHandle->data[0] = fd;
- Return<void> ret = mEffectsFactory->debugDump(hidlHandle);
- native_handle_delete(hidlHandle);
- return processReturn(__FUNCTION__, ret);
-}
-
-} // namespace android
diff --git a/media/libaudiohal/EffectsFactoryHalHidl.h b/media/libaudiohal/EffectsFactoryHalHidl.h
deleted file mode 100644
index e89f042..0000000
--- a/media/libaudiohal/EffectsFactoryHalHidl.h
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef ANDROID_HARDWARE_EFFECTS_FACTORY_HAL_HIDL_H
-#define ANDROID_HARDWARE_EFFECTS_FACTORY_HAL_HIDL_H
-
-#include <android/hardware/audio/effect/2.0/IEffectsFactory.h>
-#include <android/hardware/audio/effect/2.0/types.h>
-#include <media/audiohal/EffectsFactoryHalInterface.h>
-
-namespace android {
-
-using ::android::hardware::audio::effect::V2_0::EffectDescriptor;
-using ::android::hardware::audio::effect::V2_0::IEffectsFactory;
-using ::android::hardware::hidl_vec;
-
-class EffectsFactoryHalHidl : public EffectsFactoryHalInterface, public ConversionHelperHidl
-{
- public:
- // Returns the number of different effects in all loaded libraries.
- virtual status_t queryNumberEffects(uint32_t *pNumEffects);
-
- // Returns a descriptor of the next available effect.
- virtual status_t getDescriptor(uint32_t index,
- effect_descriptor_t *pDescriptor);
-
- virtual status_t getDescriptor(const effect_uuid_t *pEffectUuid,
- effect_descriptor_t *pDescriptor);
-
- // Creates an effect engine of the specified type.
- // To release the effect engine, it is necessary to release references
- // to the returned effect object.
- virtual status_t createEffect(const effect_uuid_t *pEffectUuid,
- int32_t sessionId, int32_t ioId,
- sp<EffectHalInterface> *effect);
-
- virtual status_t dumpEffects(int fd);
-
- private:
- friend class EffectsFactoryHalInterface;
-
- sp<IEffectsFactory> mEffectsFactory;
- hidl_vec<EffectDescriptor> mLastDescriptors;
-
- // Can not be constructed directly by clients.
- EffectsFactoryHalHidl();
- virtual ~EffectsFactoryHalHidl();
-
- status_t queryAllDescriptors();
-};
-
-} // namespace android
-
-#endif // ANDROID_HARDWARE_EFFECTS_FACTORY_HAL_HIDL_H
diff --git a/media/libaudiohal/EffectsFactoryHalInterface.cpp b/media/libaudiohal/EffectsFactoryHalInterface.cpp
new file mode 100644
index 0000000..01a171e
--- /dev/null
+++ b/media/libaudiohal/EffectsFactoryHalInterface.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "EffectsFactoryHalHidl"
+//#define LOG_NDEBUG 0
+
+#include "EffectHalHidl.h"
+#include "EffectsFactoryHalHidl.h"
+
+using ::android::hardware::Return;
+
+using namespace ::android::hardware::audio::effect;
+
+namespace android {
+
+// static
+sp<EffectsFactoryHalInterface> EffectsFactoryHalInterface::create() {
+ if (V2_0::IEffectsFactory::getService() != nullptr) {
+ return new EffectsFactoryHalHidl();
+ }
+ return nullptr;
+}
+
+// static
+bool EffectsFactoryHalInterface::isNullUuid(const effect_uuid_t *pEffectUuid) {
+ return memcmp(pEffectUuid, EFFECT_UUID_NULL, sizeof(effect_uuid_t)) == 0;
+}
+
+} // namespace android
diff --git a/media/libaudiohal/include/media/audiohal/EffectBufferHalInterface.h b/media/libaudiohal/include/media/audiohal/EffectBufferHalInterface.h
index 1cae662..d0603cd 100644
--- a/media/libaudiohal/include/media/audiohal/EffectBufferHalInterface.h
+++ b/media/libaudiohal/include/media/audiohal/EffectBufferHalInterface.h
@@ -26,6 +26,7 @@
// Abstraction for an audio buffer. It may be a "mirror" for
// a buffer that the effect chain doesn't own, or a buffer owned by
// the effect chain.
+// Buffers are created from EffectsFactoryHalInterface
class EffectBufferHalInterface : public RefBase
{
public:
@@ -49,9 +50,6 @@
virtual void update(size_t size) = 0; // copies partial data from external buffer
virtual void commit(size_t size) = 0; // copies partial data to external buffer
- static status_t allocate(size_t size, sp<EffectBufferHalInterface>* buffer);
- static status_t mirror(void* external, size_t size, sp<EffectBufferHalInterface>* buffer);
-
protected:
// Subclasses can not be constructed directly by clients.
EffectBufferHalInterface() {}
diff --git a/media/libaudiohal/include/media/audiohal/EffectsFactoryHalInterface.h b/media/libaudiohal/include/media/audiohal/EffectsFactoryHalInterface.h
index a616e86..316a46c 100644
--- a/media/libaudiohal/include/media/audiohal/EffectsFactoryHalInterface.h
+++ b/media/libaudiohal/include/media/audiohal/EffectsFactoryHalInterface.h
@@ -48,6 +48,10 @@
static sp<EffectsFactoryHalInterface> create();
+ virtual status_t allocateBuffer(size_t size, sp<EffectBufferHalInterface>* buffer) = 0;
+ virtual status_t mirrorBuffer(void* external, size_t size,
+ sp<EffectBufferHalInterface>* buffer) = 0;
+
// Helper function to compare effect uuid to EFFECT_UUID_NULL.
static bool isNullUuid(const effect_uuid_t *pEffectUuid);
diff --git a/media/libaudioprocessing/AudioMixer.cpp b/media/libaudioprocessing/AudioMixer.cpp
index f8e05e7..f1daeb4 100644
--- a/media/libaudioprocessing/AudioMixer.cpp
+++ b/media/libaudioprocessing/AudioMixer.cpp
@@ -62,13 +62,6 @@
#define ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0]))
#endif
-// TODO: Move these macro/inlines to a header file.
-template <typename T>
-static inline
-T max(const T& x, const T& y) {
- return x > y ? x : y;
-}
-
// Set kUseNewMixer to true to use the new mixer engine always. Otherwise the
// original code will be used for stereo sinks, the new mixer for multichannel.
static constexpr bool kUseNewMixer = true;
@@ -93,88 +86,41 @@
// ----------------------------------------------------------------------------
-template <typename T>
-T min(const T& a, const T& b)
-{
- return a < b ? a : b;
-}
-
-// ----------------------------------------------------------------------------
-
-// Ensure mConfiguredNames bitmask is initialized properly on all architectures.
-// The value of 1 << x is undefined in C when x >= 32.
-
-AudioMixer::AudioMixer(size_t frameCount, uint32_t sampleRate, uint32_t maxNumTracks)
- : mTrackNames(0), mConfiguredNames((maxNumTracks >= 32 ? 0 : 1 << maxNumTracks) - 1),
- mSampleRate(sampleRate)
-{
- ALOG_ASSERT(maxNumTracks <= MAX_NUM_TRACKS, "maxNumTracks %u > MAX_NUM_TRACKS %u",
- maxNumTracks, MAX_NUM_TRACKS);
-
- // AudioMixer is not yet capable of more than 32 active track inputs
- ALOG_ASSERT(32 >= MAX_NUM_TRACKS, "bad MAX_NUM_TRACKS %d", MAX_NUM_TRACKS);
-
- pthread_once(&sOnceControl, &sInitRoutine);
-
- mState.enabledTracks= 0;
- mState.needsChanged = 0;
- mState.frameCount = frameCount;
- mState.hook = process__nop;
- mState.outputTemp = NULL;
- mState.resampleTemp = NULL;
- mState.mNBLogWriter = &mDummyLogWriter;
- // mState.reserved
-
- // FIXME Most of the following initialization is probably redundant since
- // tracks[i] should only be referenced if (mTrackNames & (1 << i)) != 0
- // and mTrackNames is initially 0. However, leave it here until that's verified.
- track_t* t = mState.tracks;
- for (unsigned i=0 ; i < MAX_NUM_TRACKS ; i++) {
- t->resampler = NULL;
- t->downmixerBufferProvider = NULL;
- t->mReformatBufferProvider = NULL;
- t->mTimestretchBufferProvider = NULL;
- t++;
- }
-
-}
-
-AudioMixer::~AudioMixer()
-{
- track_t* t = mState.tracks;
- for (unsigned i=0 ; i < MAX_NUM_TRACKS ; i++) {
- delete t->resampler;
- delete t->downmixerBufferProvider;
- delete t->mReformatBufferProvider;
- delete t->mTimestretchBufferProvider;
- t++;
- }
- delete [] mState.outputTemp;
- delete [] mState.resampleTemp;
-}
-
-void AudioMixer::setNBLogWriter(NBLog::Writer *logWriter)
-{
- mState.mNBLogWriter = logWriter;
-}
-
static inline audio_format_t selectMixerInFormat(audio_format_t inputFormat __unused) {
return kUseFloat && kUseNewMixer ? AUDIO_FORMAT_PCM_FLOAT : AUDIO_FORMAT_PCM_16_BIT;
}
-int AudioMixer::getTrackName(audio_channel_mask_t channelMask,
- audio_format_t format, int sessionId)
+int AudioMixer::getTrackName(
+ audio_channel_mask_t channelMask, audio_format_t format, int sessionId)
{
if (!isValidPcmTrackFormat(format)) {
ALOGE("AudioMixer::getTrackName invalid format (%#x)", format);
return -1;
}
- uint32_t names = (~mTrackNames) & mConfiguredNames;
- if (names != 0) {
- int n = __builtin_ctz(names);
- ALOGV("add track (%d)", n);
+ if (mTracks.size() >= (size_t)mMaxNumTracks) {
+ ALOGE("%s: out of track names (max = %d)", __func__, mMaxNumTracks);
+ return -1;
+ }
+
+ // get a new name for the track.
+ int name;
+ if (mUnusedNames.size() != 0) {
+ // reuse first name for deleted track.
+ auto it = mUnusedNames.begin();
+ name = *it;
+ (void)mUnusedNames.erase(it);
+ } else {
+ // we're fully populated, so create a new name.
+ name = mTracks.size();
+ }
+ ALOGV("add track (%d)", name);
+
+ auto t = std::make_shared<Track>();
+ mTracks[name] = t;
+
+ {
+ // TODO: move initialization to the Track constructor.
// assume default parameters for the track, except where noted below
- track_t* t = &mState.tracks[n];
t->needs = 0;
// Integer volume.
@@ -215,17 +161,12 @@
// no initialization needed
// t->buffer.frameCount
t->hook = NULL;
- t->in = NULL;
- t->resampler = NULL;
+ t->mIn = NULL;
t->sampleRate = mSampleRate;
// setParameter(name, TRACK, MAIN_BUFFER, mixBuffer) is required before enable(name)
t->mainBuffer = NULL;
t->auxBuffer = NULL;
t->mInputBufferProvider = NULL;
- t->mReformatBufferProvider = NULL;
- t->downmixerBufferProvider = NULL;
- t->mPostDownmixReformatBufferProvider = NULL;
- t->mTimestretchBufferProvider = NULL;
t->mMixerFormat = AUDIO_FORMAT_PCM_16_BIT;
t->mFormat = format;
t->mMixerInFormat = selectMixerInFormat(format);
@@ -243,91 +184,78 @@
// prepareForDownmix() may change mDownmixRequiresFormat
ALOGVV("mMixerFormat:%#x mMixerInFormat:%#x\n", t->mMixerFormat, t->mMixerInFormat);
t->prepareForReformat();
- mTrackNames |= 1 << n;
- return TRACK0 + n;
+ return TRACK0 + name;
}
- ALOGE("AudioMixer::getTrackName out of available tracks");
- return -1;
}
-void AudioMixer::invalidateState(uint32_t mask)
-{
- if (mask != 0) {
- mState.needsChanged |= mask;
- mState.hook = process__validate;
- }
- }
-
// Called when channel masks have changed for a track name
// TODO: Fix DownmixerBufferProvider not to (possibly) change mixer input format,
// which will simplify this logic.
bool AudioMixer::setChannelMasks(int name,
audio_channel_mask_t trackChannelMask, audio_channel_mask_t mixerChannelMask) {
- track_t &track = mState.tracks[name];
+ LOG_ALWAYS_FATAL_IF(mTracks.find(name) == mTracks.end(), "invalid name: %d", name);
+ const std::shared_ptr<Track> &track = mTracks[name];
- if (trackChannelMask == track.channelMask
- && mixerChannelMask == track.mMixerChannelMask) {
+ if (trackChannelMask == track->channelMask
+ && mixerChannelMask == track->mMixerChannelMask) {
return false; // no need to change
}
// always recompute for both channel masks even if only one has changed.
const uint32_t trackChannelCount = audio_channel_count_from_out_mask(trackChannelMask);
const uint32_t mixerChannelCount = audio_channel_count_from_out_mask(mixerChannelMask);
- const bool mixerChannelCountChanged = track.mMixerChannelCount != mixerChannelCount;
+ const bool mixerChannelCountChanged = track->mMixerChannelCount != mixerChannelCount;
ALOG_ASSERT((trackChannelCount <= MAX_NUM_CHANNELS_TO_DOWNMIX)
&& trackChannelCount
&& mixerChannelCount);
- track.channelMask = trackChannelMask;
- track.channelCount = trackChannelCount;
- track.mMixerChannelMask = mixerChannelMask;
- track.mMixerChannelCount = mixerChannelCount;
+ track->channelMask = trackChannelMask;
+ track->channelCount = trackChannelCount;
+ track->mMixerChannelMask = mixerChannelMask;
+ track->mMixerChannelCount = mixerChannelCount;
// channel masks have changed, does this track need a downmixer?
// update to try using our desired format (if we aren't already using it)
- const audio_format_t prevDownmixerFormat = track.mDownmixRequiresFormat;
- const status_t status = mState.tracks[name].prepareForDownmix();
+ const audio_format_t prevDownmixerFormat = track->mDownmixRequiresFormat;
+ const status_t status = track->prepareForDownmix();
ALOGE_IF(status != OK,
"prepareForDownmix error %d, track channel mask %#x, mixer channel mask %#x",
- status, track.channelMask, track.mMixerChannelMask);
+ status, track->channelMask, track->mMixerChannelMask);
- if (prevDownmixerFormat != track.mDownmixRequiresFormat) {
- track.prepareForReformat(); // because of downmixer, track format may change!
+ if (prevDownmixerFormat != track->mDownmixRequiresFormat) {
+ track->prepareForReformat(); // because of downmixer, track format may change!
}
- if (track.resampler && mixerChannelCountChanged) {
+ if (track->mResampler.get() != nullptr && mixerChannelCountChanged) {
// resampler channels may have changed.
- const uint32_t resetToSampleRate = track.sampleRate;
- delete track.resampler;
- track.resampler = NULL;
- track.sampleRate = mSampleRate; // without resampler, track rate is device sample rate.
+ const uint32_t resetToSampleRate = track->sampleRate;
+ track->mResampler.reset(nullptr);
+ track->sampleRate = mSampleRate; // without resampler, track rate is device sample rate.
// recreate the resampler with updated format, channels, saved sampleRate.
- track.setResampler(resetToSampleRate /*trackSampleRate*/, mSampleRate /*devSampleRate*/);
+ track->setResampler(resetToSampleRate /*trackSampleRate*/, mSampleRate /*devSampleRate*/);
}
return true;
}
-void AudioMixer::track_t::unprepareForDownmix() {
+void AudioMixer::Track::unprepareForDownmix() {
ALOGV("AudioMixer::unprepareForDownmix(%p)", this);
- if (mPostDownmixReformatBufferProvider != nullptr) {
+ if (mPostDownmixReformatBufferProvider.get() != nullptr) {
// release any buffers held by the mPostDownmixReformatBufferProvider
- // before deallocating the downmixerBufferProvider.
+ // before deallocating the mDownmixerBufferProvider.
mPostDownmixReformatBufferProvider->reset();
}
mDownmixRequiresFormat = AUDIO_FORMAT_INVALID;
- if (downmixerBufferProvider != NULL) {
+ if (mDownmixerBufferProvider.get() != nullptr) {
// this track had previously been configured with a downmixer, delete it
- ALOGV(" deleting old downmixer");
- delete downmixerBufferProvider;
- downmixerBufferProvider = NULL;
+ mDownmixerBufferProvider.reset(nullptr);
reconfigureBufferProviders();
} else {
ALOGV(" nothing to do, no downmixer to delete");
}
}
-status_t AudioMixer::track_t::prepareForDownmix()
+status_t AudioMixer::Track::prepareForDownmix()
{
ALOGV("AudioMixer::prepareForDownmix(%p) with mask 0x%x",
this, channelMask);
@@ -345,40 +273,35 @@
if (audio_channel_mask_get_representation(channelMask)
== AUDIO_CHANNEL_REPRESENTATION_POSITION
&& DownmixerBufferProvider::isMultichannelCapable()) {
- DownmixerBufferProvider* pDbp = new DownmixerBufferProvider(channelMask,
+ mDownmixerBufferProvider.reset(new DownmixerBufferProvider(channelMask,
mMixerChannelMask,
AUDIO_FORMAT_PCM_16_BIT /* TODO: use mMixerInFormat, now only PCM 16 */,
- sampleRate, sessionId, kCopyBufferFrameCount);
-
- if (pDbp->isValid()) { // if constructor completed properly
+ sampleRate, sessionId, kCopyBufferFrameCount));
+ if (static_cast<DownmixerBufferProvider *>(mDownmixerBufferProvider.get())->isValid()) {
mDownmixRequiresFormat = AUDIO_FORMAT_PCM_16_BIT; // PCM 16 bit required for downmix
- downmixerBufferProvider = pDbp;
reconfigureBufferProviders();
return NO_ERROR;
}
- delete pDbp;
+ // mDownmixerBufferProvider reset below.
}
// Effect downmixer does not accept the channel conversion. Let's use our remixer.
- RemixBufferProvider* pRbp = new RemixBufferProvider(channelMask,
- mMixerChannelMask, mMixerInFormat, kCopyBufferFrameCount);
+ mDownmixerBufferProvider.reset(new RemixBufferProvider(channelMask,
+ mMixerChannelMask, mMixerInFormat, kCopyBufferFrameCount));
// Remix always finds a conversion whereas Downmixer effect above may fail.
- downmixerBufferProvider = pRbp;
reconfigureBufferProviders();
return NO_ERROR;
}
-void AudioMixer::track_t::unprepareForReformat() {
+void AudioMixer::Track::unprepareForReformat() {
ALOGV("AudioMixer::unprepareForReformat(%p)", this);
bool requiresReconfigure = false;
- if (mReformatBufferProvider != NULL) {
- delete mReformatBufferProvider;
- mReformatBufferProvider = NULL;
+ if (mReformatBufferProvider.get() != nullptr) {
+ mReformatBufferProvider.reset(nullptr);
requiresReconfigure = true;
}
- if (mPostDownmixReformatBufferProvider != NULL) {
- delete mPostDownmixReformatBufferProvider;
- mPostDownmixReformatBufferProvider = NULL;
+ if (mPostDownmixReformatBufferProvider.get() != nullptr) {
+ mPostDownmixReformatBufferProvider.reset(nullptr);
requiresReconfigure = true;
}
if (requiresReconfigure) {
@@ -386,7 +309,7 @@
}
}
-status_t AudioMixer::track_t::prepareForReformat()
+status_t AudioMixer::Track::prepareForReformat()
{
ALOGV("AudioMixer::prepareForReformat(%p) with format %#x", this, mFormat);
// discard previous reformatters
@@ -396,19 +319,19 @@
? mDownmixRequiresFormat : mMixerInFormat;
bool requiresReconfigure = false;
if (mFormat != targetFormat) {
- mReformatBufferProvider = new ReformatBufferProvider(
+ mReformatBufferProvider.reset(new ReformatBufferProvider(
audio_channel_count_from_out_mask(channelMask),
mFormat,
targetFormat,
- kCopyBufferFrameCount);
+ kCopyBufferFrameCount));
requiresReconfigure = true;
}
if (targetFormat != mMixerInFormat) {
- mPostDownmixReformatBufferProvider = new ReformatBufferProvider(
+ mPostDownmixReformatBufferProvider.reset(new ReformatBufferProvider(
audio_channel_count_from_out_mask(mMixerChannelMask),
targetFormat,
mMixerInFormat,
- kCopyBufferFrameCount);
+ kCopyBufferFrameCount));
requiresReconfigure = true;
}
if (requiresReconfigure) {
@@ -417,74 +340,63 @@
return NO_ERROR;
}
-void AudioMixer::track_t::reconfigureBufferProviders()
+void AudioMixer::Track::reconfigureBufferProviders()
{
bufferProvider = mInputBufferProvider;
- if (mReformatBufferProvider) {
+ if (mReformatBufferProvider.get() != nullptr) {
mReformatBufferProvider->setBufferProvider(bufferProvider);
- bufferProvider = mReformatBufferProvider;
+ bufferProvider = mReformatBufferProvider.get();
}
- if (downmixerBufferProvider) {
- downmixerBufferProvider->setBufferProvider(bufferProvider);
- bufferProvider = downmixerBufferProvider;
+ if (mDownmixerBufferProvider.get() != nullptr) {
+ mDownmixerBufferProvider->setBufferProvider(bufferProvider);
+ bufferProvider = mDownmixerBufferProvider.get();
}
- if (mPostDownmixReformatBufferProvider) {
+ if (mPostDownmixReformatBufferProvider.get() != nullptr) {
mPostDownmixReformatBufferProvider->setBufferProvider(bufferProvider);
- bufferProvider = mPostDownmixReformatBufferProvider;
+ bufferProvider = mPostDownmixReformatBufferProvider.get();
}
- if (mTimestretchBufferProvider) {
+ if (mTimestretchBufferProvider.get() != nullptr) {
mTimestretchBufferProvider->setBufferProvider(bufferProvider);
- bufferProvider = mTimestretchBufferProvider;
+ bufferProvider = mTimestretchBufferProvider.get();
}
}
void AudioMixer::deleteTrackName(int name)
{
- ALOGV("AudioMixer::deleteTrackName(%d)", name);
name -= TRACK0;
- LOG_ALWAYS_FATAL_IF(name < 0 || name >= (int)MAX_NUM_TRACKS, "bad track name %d", name);
+ LOG_ALWAYS_FATAL_IF(mTracks.find(name) == mTracks.end(), "invalid name: %d", name);
ALOGV("deleteTrackName(%d)", name);
- track_t& track(mState.tracks[ name ]);
- if (track.enabled) {
- track.enabled = false;
- invalidateState(1<<name);
+
+ if (mTracks[name]->enabled) {
+ invalidate();
}
- // delete the resampler
- delete track.resampler;
- track.resampler = NULL;
- // delete the downmixer
- mState.tracks[name].unprepareForDownmix();
- // delete the reformatter
- mState.tracks[name].unprepareForReformat();
- // delete the timestretch provider
- delete track.mTimestretchBufferProvider;
- track.mTimestretchBufferProvider = NULL;
- mTrackNames &= ~(1<<name);
+ mTracks.erase(name); // deallocate track
+ mUnusedNames.emplace(name); // recycle name
}
void AudioMixer::enable(int name)
{
name -= TRACK0;
- ALOG_ASSERT(uint32_t(name) < MAX_NUM_TRACKS, "bad track name %d", name);
- track_t& track = mState.tracks[name];
+ LOG_ALWAYS_FATAL_IF(mTracks.find(name) == mTracks.end(), "invalid name: %d", name);
+ const std::shared_ptr<Track> &track = mTracks[name];
- if (!track.enabled) {
- track.enabled = true;
+ if (!track->enabled) {
+ track->enabled = true;
ALOGV("enable(%d)", name);
- invalidateState(1 << name);
+ invalidate();
}
}
void AudioMixer::disable(int name)
{
name -= TRACK0;
- ALOG_ASSERT(uint32_t(name) < MAX_NUM_TRACKS, "bad track name %d", name);
- track_t& track = mState.tracks[name];
+ LOG_ALWAYS_FATAL_IF(mTracks.find(name) == mTracks.end(), "invalid name: %d", name);
+ const std::shared_ptr<Track> &track = mTracks[name];
- if (track.enabled) {
- track.enabled = false;
+ if (track->enabled) {
+ track->enabled = false;
ALOGV("disable(%d)", name);
- invalidateState(1 << name);
+ invalidate();
}
}
@@ -562,7 +474,8 @@
ALOGD_IF(*pPrevVolume != *pSetVolume, "previous float ramp hasn't finished,"
" prev:%f set_to:%f", *pPrevVolume, *pSetVolume);
const float inc = (newVolume - *pPrevVolume) / ramp; // could be inf, nan, subnormal
- const float maxv = max(newVolume, *pPrevVolume); // could be inf, cannot be nan, subnormal
+ // could be inf, cannot be nan, subnormal
+ const float maxv = std::max(newVolume, *pPrevVolume);
if (isnormal(inc) // inc must be a normal number (no subnormals, infinite, nan)
&& maxv + inc != maxv) { // inc must make forward progress
@@ -616,8 +529,8 @@
void AudioMixer::setParameter(int name, int target, int param, void *value)
{
name -= TRACK0;
- ALOG_ASSERT(uint32_t(name) < MAX_NUM_TRACKS, "bad track name %d", name);
- track_t& track = mState.tracks[name];
+ LOG_ALWAYS_FATAL_IF(mTracks.find(name) == mTracks.end(), "invalid name: %d", name);
+ const std::shared_ptr<Track> &track = mTracks[name];
int valueInt = static_cast<int>(reinterpret_cast<uintptr_t>(value));
int32_t *valueBuf = reinterpret_cast<int32_t*>(value);
@@ -629,33 +542,33 @@
case CHANNEL_MASK: {
const audio_channel_mask_t trackChannelMask =
static_cast<audio_channel_mask_t>(valueInt);
- if (setChannelMasks(name, trackChannelMask, track.mMixerChannelMask)) {
+ if (setChannelMasks(name, trackChannelMask, track->mMixerChannelMask)) {
ALOGV("setParameter(TRACK, CHANNEL_MASK, %x)", trackChannelMask);
- invalidateState(1 << name);
+ invalidate();
}
} break;
case MAIN_BUFFER:
- if (track.mainBuffer != valueBuf) {
- track.mainBuffer = valueBuf;
+ if (track->mainBuffer != valueBuf) {
+ track->mainBuffer = valueBuf;
ALOGV("setParameter(TRACK, MAIN_BUFFER, %p)", valueBuf);
- invalidateState(1 << name);
+ invalidate();
}
break;
case AUX_BUFFER:
- if (track.auxBuffer != valueBuf) {
- track.auxBuffer = valueBuf;
+ if (track->auxBuffer != valueBuf) {
+ track->auxBuffer = valueBuf;
ALOGV("setParameter(TRACK, AUX_BUFFER, %p)", valueBuf);
- invalidateState(1 << name);
+ invalidate();
}
break;
case FORMAT: {
audio_format_t format = static_cast<audio_format_t>(valueInt);
- if (track.mFormat != format) {
+ if (track->mFormat != format) {
ALOG_ASSERT(audio_is_linear_pcm(format), "Invalid format %#x", format);
- track.mFormat = format;
+ track->mFormat = format;
ALOGV("setParameter(TRACK, FORMAT, %#x)", format);
- track.prepareForReformat();
- invalidateState(1 << name);
+ track->prepareForReformat();
+ invalidate();
}
} break;
// FIXME do we want to support setting the downmix type from AudioFlinger?
@@ -664,17 +577,17 @@
break */
case MIXER_FORMAT: {
audio_format_t format = static_cast<audio_format_t>(valueInt);
- if (track.mMixerFormat != format) {
- track.mMixerFormat = format;
+ if (track->mMixerFormat != format) {
+ track->mMixerFormat = format;
ALOGV("setParameter(TRACK, MIXER_FORMAT, %#x)", format);
}
} break;
case MIXER_CHANNEL_MASK: {
const audio_channel_mask_t mixerChannelMask =
static_cast<audio_channel_mask_t>(valueInt);
- if (setChannelMasks(name, track.channelMask, mixerChannelMask)) {
+ if (setChannelMasks(name, track->channelMask, mixerChannelMask)) {
ALOGV("setParameter(TRACK, MIXER_CHANNEL_MASK, %#x)", mixerChannelMask);
- invalidateState(1 << name);
+ invalidate();
}
} break;
default:
@@ -686,21 +599,20 @@
switch (param) {
case SAMPLE_RATE:
ALOG_ASSERT(valueInt > 0, "bad sample rate %d", valueInt);
- if (track.setResampler(uint32_t(valueInt), mSampleRate)) {
+ if (track->setResampler(uint32_t(valueInt), mSampleRate)) {
ALOGV("setParameter(RESAMPLE, SAMPLE_RATE, %u)",
uint32_t(valueInt));
- invalidateState(1 << name);
+ invalidate();
}
break;
case RESET:
- track.resetResampler();
- invalidateState(1 << name);
+ track->resetResampler();
+ invalidate();
break;
case REMOVE:
- delete track.resampler;
- track.resampler = NULL;
- track.sampleRate = mSampleRate;
- invalidateState(1 << name);
+ track->mResampler.reset(nullptr);
+ track->sampleRate = mSampleRate;
+ invalidate();
break;
default:
LOG_ALWAYS_FATAL("setParameter resample: bad param %d", param);
@@ -712,26 +624,28 @@
switch (param) {
case AUXLEVEL:
if (setVolumeRampVariables(*reinterpret_cast<float*>(value),
- target == RAMP_VOLUME ? mState.frameCount : 0,
- &track.auxLevel, &track.prevAuxLevel, &track.auxInc,
- &track.mAuxLevel, &track.mPrevAuxLevel, &track.mAuxInc)) {
+ target == RAMP_VOLUME ? mFrameCount : 0,
+ &track->auxLevel, &track->prevAuxLevel, &track->auxInc,
+ &track->mAuxLevel, &track->mPrevAuxLevel, &track->mAuxInc)) {
ALOGV("setParameter(%s, AUXLEVEL: %04x)",
- target == VOLUME ? "VOLUME" : "RAMP_VOLUME", track.auxLevel);
- invalidateState(1 << name);
+ target == VOLUME ? "VOLUME" : "RAMP_VOLUME", track->auxLevel);
+ invalidate();
}
break;
default:
if ((unsigned)param >= VOLUME0 && (unsigned)param < VOLUME0 + MAX_NUM_VOLUMES) {
if (setVolumeRampVariables(*reinterpret_cast<float*>(value),
- target == RAMP_VOLUME ? mState.frameCount : 0,
- &track.volume[param - VOLUME0], &track.prevVolume[param - VOLUME0],
- &track.volumeInc[param - VOLUME0],
- &track.mVolume[param - VOLUME0], &track.mPrevVolume[param - VOLUME0],
- &track.mVolumeInc[param - VOLUME0])) {
+ target == RAMP_VOLUME ? mFrameCount : 0,
+ &track->volume[param - VOLUME0],
+ &track->prevVolume[param - VOLUME0],
+ &track->volumeInc[param - VOLUME0],
+ &track->mVolume[param - VOLUME0],
+ &track->mPrevVolume[param - VOLUME0],
+ &track->mVolumeInc[param - VOLUME0])) {
ALOGV("setParameter(%s, VOLUME%d: %04x)",
target == VOLUME ? "VOLUME" : "RAMP_VOLUME", param - VOLUME0,
- track.volume[param - VOLUME0]);
- invalidateState(1 << name);
+ track->volume[param - VOLUME0]);
+ invalidate();
}
} else {
LOG_ALWAYS_FATAL("setParameter volume: bad param %d", param);
@@ -744,16 +658,16 @@
const AudioPlaybackRate *playbackRate =
reinterpret_cast<AudioPlaybackRate*>(value);
ALOGW_IF(!isAudioPlaybackRateValid(*playbackRate),
- "bad parameters speed %f, pitch %f",playbackRate->mSpeed,
- playbackRate->mPitch);
- if (track.setPlaybackRate(*playbackRate)) {
+ "bad parameters speed %f, pitch %f",
+ playbackRate->mSpeed, playbackRate->mPitch);
+ if (track->setPlaybackRate(*playbackRate)) {
ALOGV("setParameter(TIMESTRETCH, PLAYBACK_RATE, STRETCH_MODE, FALLBACK_MODE "
"%f %f %d %d",
playbackRate->mSpeed,
playbackRate->mPitch,
playbackRate->mStretchMode,
playbackRate->mFallbackMode);
- // invalidateState(1 << name);
+ // invalidate(); (should not require reconfigure)
}
} break;
default:
@@ -766,12 +680,12 @@
}
}
-bool AudioMixer::track_t::setResampler(uint32_t trackSampleRate, uint32_t devSampleRate)
+bool AudioMixer::Track::setResampler(uint32_t trackSampleRate, uint32_t devSampleRate)
{
- if (trackSampleRate != devSampleRate || resampler != NULL) {
+ if (trackSampleRate != devSampleRate || mResampler.get() != nullptr) {
if (sampleRate != trackSampleRate) {
sampleRate = trackSampleRate;
- if (resampler == NULL) {
+ if (mResampler.get() == nullptr) {
ALOGV("Creating resampler from track %d Hz to device %d Hz",
trackSampleRate, devSampleRate);
AudioResampler::src_quality quality;
@@ -787,15 +701,15 @@
// TODO: Remove MONO_HACK. Resampler sees #channels after the downmixer
// but if none exists, it is the channel count (1 for mono).
- const int resamplerChannelCount = downmixerBufferProvider != NULL
+ const int resamplerChannelCount = mDownmixerBufferProvider.get() != nullptr
? mMixerChannelCount : channelCount;
ALOGVV("Creating resampler:"
" format(%#x) channels(%d) devSampleRate(%u) quality(%d)\n",
mMixerInFormat, resamplerChannelCount, devSampleRate, quality);
- resampler = AudioResampler::create(
+ mResampler.reset(AudioResampler::create(
mMixerInFormat,
resamplerChannelCount,
- devSampleRate, quality);
+ devSampleRate, quality));
}
return true;
}
@@ -803,25 +717,25 @@
return false;
}
-bool AudioMixer::track_t::setPlaybackRate(const AudioPlaybackRate &playbackRate)
+bool AudioMixer::Track::setPlaybackRate(const AudioPlaybackRate &playbackRate)
{
- if ((mTimestretchBufferProvider == NULL &&
+ if ((mTimestretchBufferProvider.get() == nullptr &&
fabs(playbackRate.mSpeed - mPlaybackRate.mSpeed) < AUDIO_TIMESTRETCH_SPEED_MIN_DELTA &&
fabs(playbackRate.mPitch - mPlaybackRate.mPitch) < AUDIO_TIMESTRETCH_PITCH_MIN_DELTA) ||
isAudioPlaybackRateEqual(playbackRate, mPlaybackRate)) {
return false;
}
mPlaybackRate = playbackRate;
- if (mTimestretchBufferProvider == NULL) {
+ if (mTimestretchBufferProvider.get() == nullptr) {
// TODO: Remove MONO_HACK. Resampler sees #channels after the downmixer
// but if none exists, it is the channel count (1 for mono).
- const int timestretchChannelCount = downmixerBufferProvider != NULL
+ const int timestretchChannelCount = mDownmixerBufferProvider.get() != nullptr
? mMixerChannelCount : channelCount;
- mTimestretchBufferProvider = new TimestretchBufferProvider(timestretchChannelCount,
- mMixerInFormat, sampleRate, playbackRate);
+ mTimestretchBufferProvider.reset(new TimestretchBufferProvider(timestretchChannelCount,
+ mMixerInFormat, sampleRate, playbackRate));
reconfigureBufferProviders();
} else {
- static_cast<TimestretchBufferProvider*>(mTimestretchBufferProvider)
+ static_cast<TimestretchBufferProvider*>(mTimestretchBufferProvider.get())
->setPlaybackRate(playbackRate);
}
return true;
@@ -840,7 +754,7 @@
*
* There is a bit of duplicated code here, but it keeps backward compatibility.
*/
-inline void AudioMixer::track_t::adjustVolumeRamp(bool aux, bool useFloat)
+inline void AudioMixer::Track::adjustVolumeRamp(bool aux, bool useFloat)
{
if (useFloat) {
for (uint32_t i = 0; i < MAX_NUM_VOLUMES; i++) {
@@ -895,8 +809,9 @@
size_t AudioMixer::getUnreleasedFrames(int name) const
{
name -= TRACK0;
- if (uint32_t(name) < MAX_NUM_TRACKS) {
- return mState.tracks[name].getUnreleasedFrames();
+ const auto it = mTracks.find(name);
+ if (it != mTracks.end()) {
+ return it->second->getUnreleasedFrames();
}
return 0;
}
@@ -904,87 +819,63 @@
void AudioMixer::setBufferProvider(int name, AudioBufferProvider* bufferProvider)
{
name -= TRACK0;
- ALOG_ASSERT(uint32_t(name) < MAX_NUM_TRACKS, "bad track name %d", name);
+ const std::shared_ptr<Track> &track = mTracks[name];
- if (mState.tracks[name].mInputBufferProvider == bufferProvider) {
+ if (track->mInputBufferProvider == bufferProvider) {
return; // don't reset any buffer providers if identical.
}
- if (mState.tracks[name].mReformatBufferProvider != NULL) {
- mState.tracks[name].mReformatBufferProvider->reset();
- } else if (mState.tracks[name].downmixerBufferProvider != NULL) {
- mState.tracks[name].downmixerBufferProvider->reset();
- } else if (mState.tracks[name].mPostDownmixReformatBufferProvider != NULL) {
- mState.tracks[name].mPostDownmixReformatBufferProvider->reset();
- } else if (mState.tracks[name].mTimestretchBufferProvider != NULL) {
- mState.tracks[name].mTimestretchBufferProvider->reset();
+ if (track->mReformatBufferProvider.get() != nullptr) {
+ track->mReformatBufferProvider->reset();
+ } else if (track->mDownmixerBufferProvider != nullptr) {
+ track->mDownmixerBufferProvider->reset();
+ } else if (track->mPostDownmixReformatBufferProvider.get() != nullptr) {
+ track->mPostDownmixReformatBufferProvider->reset();
+ } else if (track->mTimestretchBufferProvider.get() != nullptr) {
+ track->mTimestretchBufferProvider->reset();
}
- mState.tracks[name].mInputBufferProvider = bufferProvider;
- mState.tracks[name].reconfigureBufferProviders();
+ track->mInputBufferProvider = bufferProvider;
+ track->reconfigureBufferProviders();
}
-
-void AudioMixer::process()
+void AudioMixer::process__validate()
{
- mState.hook(&mState);
-}
-
-
-void AudioMixer::process__validate(state_t* state)
-{
- ALOGW_IF(!state->needsChanged,
- "in process__validate() but nothing's invalid");
-
- uint32_t changed = state->needsChanged;
- state->needsChanged = 0; // clear the validation flag
-
- // recompute which tracks are enabled / disabled
- uint32_t enabled = 0;
- uint32_t disabled = 0;
- while (changed) {
- const int i = 31 - __builtin_clz(changed);
- const uint32_t mask = 1<<i;
- changed &= ~mask;
- track_t& t = state->tracks[i];
- (t.enabled ? enabled : disabled) |= mask;
- }
- state->enabledTracks &= ~disabled;
- state->enabledTracks |= enabled;
-
- // compute everything we need...
- int countActiveTracks = 0;
// TODO: fix all16BitsStereNoResample logic to
// either properly handle muted tracks (it should ignore them)
// or remove altogether as an obsolete optimization.
bool all16BitsStereoNoResample = true;
bool resampling = false;
bool volumeRamp = false;
- uint32_t en = state->enabledTracks;
- while (en) {
- const int i = 31 - __builtin_clz(en);
- en &= ~(1<<i);
- countActiveTracks++;
- track_t& t = state->tracks[i];
+ mEnabled.clear();
+ mGroups.clear();
+ for (const auto &pair : mTracks) {
+ const int name = pair.first;
+ const std::shared_ptr<Track> &t = pair.second;
+ if (!t->enabled) continue;
+
+ mEnabled.emplace_back(name); // we add to mEnabled in order of name.
+ mGroups[t->mainBuffer].emplace_back(name); // mGroups also in order of name.
+
uint32_t n = 0;
// FIXME can overflow (mask is only 3 bits)
- n |= NEEDS_CHANNEL_1 + t.channelCount - 1;
- if (t.doesResample()) {
+ n |= NEEDS_CHANNEL_1 + t->channelCount - 1;
+ if (t->doesResample()) {
n |= NEEDS_RESAMPLE;
}
- if (t.auxLevel != 0 && t.auxBuffer != NULL) {
+ if (t->auxLevel != 0 && t->auxBuffer != NULL) {
n |= NEEDS_AUX;
}
- if (t.volumeInc[0]|t.volumeInc[1]) {
+ if (t->volumeInc[0]|t->volumeInc[1]) {
volumeRamp = true;
- } else if (!t.doesResample() && t.volumeRL == 0) {
+ } else if (!t->doesResample() && t->volumeRL == 0) {
n |= NEEDS_MUTE;
}
- t.needs = n;
+ t->needs = n;
if (n & NEEDS_MUTE) {
- t.hook = track__nop;
+ t->hook = &Track::track__nop;
} else {
if (n & NEEDS_AUX) {
all16BitsStereoNoResample = false;
@@ -992,23 +883,23 @@
if (n & NEEDS_RESAMPLE) {
all16BitsStereoNoResample = false;
resampling = true;
- t.hook = getTrackHook(TRACKTYPE_RESAMPLE, t.mMixerChannelCount,
- t.mMixerInFormat, t.mMixerFormat);
+ t->hook = Track::getTrackHook(TRACKTYPE_RESAMPLE, t->mMixerChannelCount,
+ t->mMixerInFormat, t->mMixerFormat);
ALOGV_IF((n & NEEDS_CHANNEL_COUNT__MASK) > NEEDS_CHANNEL_2,
"Track %d needs downmix + resample", i);
} else {
if ((n & NEEDS_CHANNEL_COUNT__MASK) == NEEDS_CHANNEL_1){
- t.hook = getTrackHook(
- (t.mMixerChannelMask == AUDIO_CHANNEL_OUT_STEREO // TODO: MONO_HACK
- && t.channelMask == AUDIO_CHANNEL_OUT_MONO)
+ t->hook = Track::getTrackHook(
+ (t->mMixerChannelMask == AUDIO_CHANNEL_OUT_STEREO // TODO: MONO_HACK
+ && t->channelMask == AUDIO_CHANNEL_OUT_MONO)
? TRACKTYPE_NORESAMPLEMONO : TRACKTYPE_NORESAMPLE,
- t.mMixerChannelCount,
- t.mMixerInFormat, t.mMixerFormat);
+ t->mMixerChannelCount,
+ t->mMixerInFormat, t->mMixerFormat);
all16BitsStereoNoResample = false;
}
if ((n & NEEDS_CHANNEL_COUNT__MASK) >= NEEDS_CHANNEL_2){
- t.hook = getTrackHook(TRACKTYPE_NORESAMPLE, t.mMixerChannelCount,
- t.mMixerInFormat, t.mMixerFormat);
+ t->hook = Track::getTrackHook(TRACKTYPE_NORESAMPLE, t->mMixerChannelCount,
+ t->mMixerInFormat, t->mMixerFormat);
ALOGV_IF((n & NEEDS_CHANNEL_COUNT__MASK) > NEEDS_CHANNEL_2,
"Track %d needs downmix", i);
}
@@ -1017,137 +908,125 @@
}
// select the processing hooks
- state->hook = process__nop;
- if (countActiveTracks > 0) {
+ mHook = &AudioMixer::process__nop;
+ if (mEnabled.size() > 0) {
if (resampling) {
- if (!state->outputTemp) {
- state->outputTemp = new int32_t[MAX_NUM_CHANNELS * state->frameCount];
+ if (mOutputTemp.get() == nullptr) {
+ mOutputTemp.reset(new int32_t[MAX_NUM_CHANNELS * mFrameCount]);
}
- if (!state->resampleTemp) {
- state->resampleTemp = new int32_t[MAX_NUM_CHANNELS * state->frameCount];
+ if (mResampleTemp.get() == nullptr) {
+ mResampleTemp.reset(new int32_t[MAX_NUM_CHANNELS * mFrameCount]);
}
- state->hook = process__genericResampling;
+ mHook = &AudioMixer::process__genericResampling;
} else {
- if (state->outputTemp) {
- delete [] state->outputTemp;
- state->outputTemp = NULL;
- }
- if (state->resampleTemp) {
- delete [] state->resampleTemp;
- state->resampleTemp = NULL;
- }
- state->hook = process__genericNoResampling;
+ // we keep temp arrays around.
+ mHook = &AudioMixer::process__genericNoResampling;
if (all16BitsStereoNoResample && !volumeRamp) {
- if (countActiveTracks == 1) {
- const int i = 31 - __builtin_clz(state->enabledTracks);
- track_t& t = state->tracks[i];
- if ((t.needs & NEEDS_MUTE) == 0) {
+ if (mEnabled.size() == 1) {
+ const std::shared_ptr<Track> &t = mTracks[mEnabled[0]];
+ if ((t->needs & NEEDS_MUTE) == 0) {
// The check prevents a muted track from acquiring a process hook.
//
// This is dangerous if the track is MONO as that requires
// special case handling due to implicit channel duplication.
// Stereo or Multichannel should actually be fine here.
- state->hook = getProcessHook(PROCESSTYPE_NORESAMPLEONETRACK,
- t.mMixerChannelCount, t.mMixerInFormat, t.mMixerFormat);
+ mHook = getProcessHook(PROCESSTYPE_NORESAMPLEONETRACK,
+ t->mMixerChannelCount, t->mMixerInFormat, t->mMixerFormat);
}
}
}
}
}
- ALOGV("mixer configuration change: %d activeTracks (%08x) "
+ ALOGV("mixer configuration change: %zu "
"all16BitsStereoNoResample=%d, resampling=%d, volumeRamp=%d",
- countActiveTracks, state->enabledTracks,
- all16BitsStereoNoResample, resampling, volumeRamp);
+ mEnabled.size(), all16BitsStereoNoResample, resampling, volumeRamp);
- state->hook(state);
+ process();
// Now that the volume ramp has been done, set optimal state and
// track hooks for subsequent mixer process
- if (countActiveTracks > 0) {
+ if (mEnabled.size() > 0) {
bool allMuted = true;
- uint32_t en = state->enabledTracks;
- while (en) {
- const int i = 31 - __builtin_clz(en);
- en &= ~(1<<i);
- track_t& t = state->tracks[i];
- if (!t.doesResample() && t.volumeRL == 0) {
- t.needs |= NEEDS_MUTE;
- t.hook = track__nop;
+
+ for (const int name : mEnabled) {
+ const std::shared_ptr<Track> &t = mTracks[name];
+ if (!t->doesResample() && t->volumeRL == 0) {
+ t->needs |= NEEDS_MUTE;
+ t->hook = &Track::track__nop;
} else {
allMuted = false;
}
}
if (allMuted) {
- state->hook = process__nop;
+ mHook = &AudioMixer::process__nop;
} else if (all16BitsStereoNoResample) {
- if (countActiveTracks == 1) {
- const int i = 31 - __builtin_clz(state->enabledTracks);
- track_t& t = state->tracks[i];
+ if (mEnabled.size() == 1) {
+ //const int i = 31 - __builtin_clz(enabledTracks);
+ const std::shared_ptr<Track> &t = mTracks[mEnabled[0]];
// Muted single tracks handled by allMuted above.
- state->hook = getProcessHook(PROCESSTYPE_NORESAMPLEONETRACK,
- t.mMixerChannelCount, t.mMixerInFormat, t.mMixerFormat);
+ mHook = getProcessHook(PROCESSTYPE_NORESAMPLEONETRACK,
+ t->mMixerChannelCount, t->mMixerInFormat, t->mMixerFormat);
}
}
}
}
-
-void AudioMixer::track__genericResample(track_t* t, int32_t* out, size_t outFrameCount,
- int32_t* temp, int32_t* aux)
+void AudioMixer::Track::track__genericResample(
+ int32_t* out, size_t outFrameCount, int32_t* temp, int32_t* aux)
{
ALOGVV("track__genericResample\n");
- t->resampler->setSampleRate(t->sampleRate);
+ mResampler->setSampleRate(sampleRate);
// ramp gain - resample to temp buffer and scale/mix in 2nd step
if (aux != NULL) {
// always resample with unity gain when sending to auxiliary buffer to be able
// to apply send level after resampling
- t->resampler->setVolume(UNITY_GAIN_FLOAT, UNITY_GAIN_FLOAT);
- memset(temp, 0, outFrameCount * t->mMixerChannelCount * sizeof(int32_t));
- t->resampler->resample(temp, outFrameCount, t->bufferProvider);
- if (CC_UNLIKELY(t->volumeInc[0]|t->volumeInc[1]|t->auxInc)) {
- volumeRampStereo(t, out, outFrameCount, temp, aux);
+ mResampler->setVolume(UNITY_GAIN_FLOAT, UNITY_GAIN_FLOAT);
+ memset(temp, 0, outFrameCount * mMixerChannelCount * sizeof(int32_t));
+ mResampler->resample(temp, outFrameCount, bufferProvider);
+ if (CC_UNLIKELY(volumeInc[0]|volumeInc[1]|auxInc)) {
+ volumeRampStereo(out, outFrameCount, temp, aux);
} else {
- volumeStereo(t, out, outFrameCount, temp, aux);
+ volumeStereo(out, outFrameCount, temp, aux);
}
} else {
- if (CC_UNLIKELY(t->volumeInc[0]|t->volumeInc[1])) {
- t->resampler->setVolume(UNITY_GAIN_FLOAT, UNITY_GAIN_FLOAT);
+ if (CC_UNLIKELY(volumeInc[0]|volumeInc[1])) {
+ mResampler->setVolume(UNITY_GAIN_FLOAT, UNITY_GAIN_FLOAT);
memset(temp, 0, outFrameCount * MAX_NUM_CHANNELS * sizeof(int32_t));
- t->resampler->resample(temp, outFrameCount, t->bufferProvider);
- volumeRampStereo(t, out, outFrameCount, temp, aux);
+ mResampler->resample(temp, outFrameCount, bufferProvider);
+ volumeRampStereo(out, outFrameCount, temp, aux);
}
// constant gain
else {
- t->resampler->setVolume(t->mVolume[0], t->mVolume[1]);
- t->resampler->resample(out, outFrameCount, t->bufferProvider);
+ mResampler->setVolume(mVolume[0], mVolume[1]);
+ mResampler->resample(out, outFrameCount, bufferProvider);
}
}
}
-void AudioMixer::track__nop(track_t* t __unused, int32_t* out __unused,
+void AudioMixer::Track::track__nop(int32_t* out __unused,
size_t outFrameCount __unused, int32_t* temp __unused, int32_t* aux __unused)
{
}
-void AudioMixer::volumeRampStereo(track_t* t, int32_t* out, size_t frameCount, int32_t* temp,
- int32_t* aux)
+void AudioMixer::Track::volumeRampStereo(
+ int32_t* out, size_t frameCount, int32_t* temp, int32_t* aux)
{
- int32_t vl = t->prevVolume[0];
- int32_t vr = t->prevVolume[1];
- const int32_t vlInc = t->volumeInc[0];
- const int32_t vrInc = t->volumeInc[1];
+ int32_t vl = prevVolume[0];
+ int32_t vr = prevVolume[1];
+ const int32_t vlInc = volumeInc[0];
+ const int32_t vrInc = volumeInc[1];
//ALOGD("[0] %p: inc=%f, v0=%f, v1=%d, final=%f, count=%d",
- // t, vlInc/65536.0f, vl/65536.0f, t->volume[0],
+ // t, vlInc/65536.0f, vl/65536.0f, volume[0],
// (vl + vlInc*frameCount)/65536.0f, frameCount);
// ramp volume
if (CC_UNLIKELY(aux != NULL)) {
- int32_t va = t->prevAuxLevel;
- const int32_t vaInc = t->auxInc;
+ int32_t va = prevAuxLevel;
+ const int32_t vaInc = auxInc;
int32_t l;
int32_t r;
@@ -1161,7 +1040,7 @@
vr += vrInc;
va += vaInc;
} while (--frameCount);
- t->prevAuxLevel = va;
+ prevAuxLevel = va;
} else {
do {
*out++ += (vl >> 16) * (*temp++ >> 12);
@@ -1170,19 +1049,19 @@
vr += vrInc;
} while (--frameCount);
}
- t->prevVolume[0] = vl;
- t->prevVolume[1] = vr;
- t->adjustVolumeRamp(aux != NULL);
+ prevVolume[0] = vl;
+ prevVolume[1] = vr;
+ adjustVolumeRamp(aux != NULL);
}
-void AudioMixer::volumeStereo(track_t* t, int32_t* out, size_t frameCount, int32_t* temp,
- int32_t* aux)
+void AudioMixer::Track::volumeStereo(
+ int32_t* out, size_t frameCount, int32_t* temp, int32_t* aux)
{
- const int16_t vl = t->volume[0];
- const int16_t vr = t->volume[1];
+ const int16_t vl = volume[0];
+ const int16_t vr = volume[1];
if (CC_UNLIKELY(aux != NULL)) {
- const int16_t va = t->auxLevel;
+ const int16_t va = auxLevel;
do {
int16_t l = (int16_t)(*temp++ >> 12);
int16_t r = (int16_t)(*temp++ >> 12);
@@ -1204,25 +1083,25 @@
}
}
-void AudioMixer::track__16BitsStereo(track_t* t, int32_t* out, size_t frameCount,
- int32_t* temp __unused, int32_t* aux)
+void AudioMixer::Track::track__16BitsStereo(
+ int32_t* out, size_t frameCount, int32_t* temp __unused, int32_t* aux)
{
ALOGVV("track__16BitsStereo\n");
- const int16_t *in = static_cast<const int16_t *>(t->in);
+ const int16_t *in = static_cast<const int16_t *>(mIn);
if (CC_UNLIKELY(aux != NULL)) {
int32_t l;
int32_t r;
// ramp gain
- if (CC_UNLIKELY(t->volumeInc[0]|t->volumeInc[1]|t->auxInc)) {
- int32_t vl = t->prevVolume[0];
- int32_t vr = t->prevVolume[1];
- int32_t va = t->prevAuxLevel;
- const int32_t vlInc = t->volumeInc[0];
- const int32_t vrInc = t->volumeInc[1];
- const int32_t vaInc = t->auxInc;
+ if (CC_UNLIKELY(volumeInc[0]|volumeInc[1]|auxInc)) {
+ int32_t vl = prevVolume[0];
+ int32_t vr = prevVolume[1];
+ int32_t va = prevAuxLevel;
+ const int32_t vlInc = volumeInc[0];
+ const int32_t vrInc = volumeInc[1];
+ const int32_t vaInc = auxInc;
// ALOGD("[1] %p: inc=%f, v0=%f, v1=%d, final=%f, count=%d",
- // t, vlInc/65536.0f, vl/65536.0f, t->volume[0],
+ // t, vlInc/65536.0f, vl/65536.0f, volume[0],
// (vl + vlInc*frameCount)/65536.0f, frameCount);
do {
@@ -1236,16 +1115,16 @@
va += vaInc;
} while (--frameCount);
- t->prevVolume[0] = vl;
- t->prevVolume[1] = vr;
- t->prevAuxLevel = va;
- t->adjustVolumeRamp(true);
+ prevVolume[0] = vl;
+ prevVolume[1] = vr;
+ prevAuxLevel = va;
+ adjustVolumeRamp(true);
}
// constant gain
else {
- const uint32_t vrl = t->volumeRL;
- const int16_t va = (int16_t)t->auxLevel;
+ const uint32_t vrl = volumeRL;
+ const int16_t va = (int16_t)auxLevel;
do {
uint32_t rl = *reinterpret_cast<const uint32_t *>(in);
int16_t a = (int16_t)(((int32_t)in[0] + in[1]) >> 1);
@@ -1259,14 +1138,14 @@
}
} else {
// ramp gain
- if (CC_UNLIKELY(t->volumeInc[0]|t->volumeInc[1])) {
- int32_t vl = t->prevVolume[0];
- int32_t vr = t->prevVolume[1];
- const int32_t vlInc = t->volumeInc[0];
- const int32_t vrInc = t->volumeInc[1];
+ if (CC_UNLIKELY(volumeInc[0]|volumeInc[1])) {
+ int32_t vl = prevVolume[0];
+ int32_t vr = prevVolume[1];
+ const int32_t vlInc = volumeInc[0];
+ const int32_t vrInc = volumeInc[1];
// ALOGD("[1] %p: inc=%f, v0=%f, v1=%d, final=%f, count=%d",
- // t, vlInc/65536.0f, vl/65536.0f, t->volume[0],
+ // t, vlInc/65536.0f, vl/65536.0f, volume[0],
// (vl + vlInc*frameCount)/65536.0f, frameCount);
do {
@@ -1276,14 +1155,14 @@
vr += vrInc;
} while (--frameCount);
- t->prevVolume[0] = vl;
- t->prevVolume[1] = vr;
- t->adjustVolumeRamp(false);
+ prevVolume[0] = vl;
+ prevVolume[1] = vr;
+ adjustVolumeRamp(false);
}
// constant gain
else {
- const uint32_t vrl = t->volumeRL;
+ const uint32_t vrl = volumeRL;
do {
uint32_t rl = *reinterpret_cast<const uint32_t *>(in);
in += 2;
@@ -1293,27 +1172,27 @@
} while (--frameCount);
}
}
- t->in = in;
+ mIn = in;
}
-void AudioMixer::track__16BitsMono(track_t* t, int32_t* out, size_t frameCount,
- int32_t* temp __unused, int32_t* aux)
+void AudioMixer::Track::track__16BitsMono(
+ int32_t* out, size_t frameCount, int32_t* temp __unused, int32_t* aux)
{
ALOGVV("track__16BitsMono\n");
- const int16_t *in = static_cast<int16_t const *>(t->in);
+ const int16_t *in = static_cast<int16_t const *>(mIn);
if (CC_UNLIKELY(aux != NULL)) {
// ramp gain
- if (CC_UNLIKELY(t->volumeInc[0]|t->volumeInc[1]|t->auxInc)) {
- int32_t vl = t->prevVolume[0];
- int32_t vr = t->prevVolume[1];
- int32_t va = t->prevAuxLevel;
- const int32_t vlInc = t->volumeInc[0];
- const int32_t vrInc = t->volumeInc[1];
- const int32_t vaInc = t->auxInc;
+ if (CC_UNLIKELY(volumeInc[0]|volumeInc[1]|auxInc)) {
+ int32_t vl = prevVolume[0];
+ int32_t vr = prevVolume[1];
+ int32_t va = prevAuxLevel;
+ const int32_t vlInc = volumeInc[0];
+ const int32_t vrInc = volumeInc[1];
+ const int32_t vaInc = auxInc;
// ALOGD("[2] %p: inc=%f, v0=%f, v1=%d, final=%f, count=%d",
- // t, vlInc/65536.0f, vl/65536.0f, t->volume[0],
+ // t, vlInc/65536.0f, vl/65536.0f, volume[0],
// (vl + vlInc*frameCount)/65536.0f, frameCount);
do {
@@ -1326,16 +1205,16 @@
va += vaInc;
} while (--frameCount);
- t->prevVolume[0] = vl;
- t->prevVolume[1] = vr;
- t->prevAuxLevel = va;
- t->adjustVolumeRamp(true);
+ prevVolume[0] = vl;
+ prevVolume[1] = vr;
+ prevAuxLevel = va;
+ adjustVolumeRamp(true);
}
// constant gain
else {
- const int16_t vl = t->volume[0];
- const int16_t vr = t->volume[1];
- const int16_t va = (int16_t)t->auxLevel;
+ const int16_t vl = volume[0];
+ const int16_t vr = volume[1];
+ const int16_t va = (int16_t)auxLevel;
do {
int16_t l = *in++;
out[0] = mulAdd(l, vl, out[0]);
@@ -1347,14 +1226,14 @@
}
} else {
// ramp gain
- if (CC_UNLIKELY(t->volumeInc[0]|t->volumeInc[1])) {
- int32_t vl = t->prevVolume[0];
- int32_t vr = t->prevVolume[1];
- const int32_t vlInc = t->volumeInc[0];
- const int32_t vrInc = t->volumeInc[1];
+ if (CC_UNLIKELY(volumeInc[0]|volumeInc[1])) {
+ int32_t vl = prevVolume[0];
+ int32_t vr = prevVolume[1];
+ const int32_t vlInc = volumeInc[0];
+ const int32_t vrInc = volumeInc[1];
// ALOGD("[2] %p: inc=%f, v0=%f, v1=%d, final=%f, count=%d",
- // t, vlInc/65536.0f, vl/65536.0f, t->volume[0],
+ // t, vlInc/65536.0f, vl/65536.0f, volume[0],
// (vl + vlInc*frameCount)/65536.0f, frameCount);
do {
@@ -1365,14 +1244,14 @@
vr += vrInc;
} while (--frameCount);
- t->prevVolume[0] = vl;
- t->prevVolume[1] = vr;
- t->adjustVolumeRamp(false);
+ prevVolume[0] = vl;
+ prevVolume[1] = vr;
+ adjustVolumeRamp(false);
}
// constant gain
else {
- const int16_t vl = t->volume[0];
- const int16_t vr = t->volume[1];
+ const int16_t vl = volume[0];
+ const int16_t vr = volume[1];
do {
int16_t l = *in++;
out[0] = mulAdd(l, vl, out[0]);
@@ -1381,273 +1260,214 @@
} while (--frameCount);
}
}
- t->in = in;
+ mIn = in;
}
// no-op case
-void AudioMixer::process__nop(state_t* state)
+void AudioMixer::process__nop()
{
ALOGVV("process__nop\n");
- uint32_t e0 = state->enabledTracks;
- while (e0) {
+
+ for (const auto &pair : mGroups) {
// process by group of tracks with same output buffer to
// avoid multiple memset() on same buffer
- uint32_t e1 = e0, e2 = e0;
- int i = 31 - __builtin_clz(e1);
- {
- track_t& t1 = state->tracks[i];
- e2 &= ~(1<<i);
- while (e2) {
- i = 31 - __builtin_clz(e2);
- e2 &= ~(1<<i);
- track_t& t2 = state->tracks[i];
- if (CC_UNLIKELY(t2.mainBuffer != t1.mainBuffer)) {
- e1 &= ~(1<<i);
- }
- }
- e0 &= ~(e1);
+ const auto &group = pair.second;
- memset(t1.mainBuffer, 0, state->frameCount * t1.mMixerChannelCount
- * audio_bytes_per_sample(t1.mMixerFormat));
- }
+ const std::shared_ptr<Track> &t = mTracks[group[0]];
+ memset(t->mainBuffer, 0,
+ mFrameCount * t->mMixerChannelCount
+ * audio_bytes_per_sample(t->mMixerFormat));
- while (e1) {
- i = 31 - __builtin_clz(e1);
- e1 &= ~(1<<i);
- {
- track_t& t3 = state->tracks[i];
- size_t outFrames = state->frameCount;
- while (outFrames) {
- t3.buffer.frameCount = outFrames;
- t3.bufferProvider->getNextBuffer(&t3.buffer);
- if (t3.buffer.raw == NULL) break;
- outFrames -= t3.buffer.frameCount;
- t3.bufferProvider->releaseBuffer(&t3.buffer);
- }
+ // now consume data
+ for (const int name : group) {
+ const std::shared_ptr<Track> &t = mTracks[name];
+ size_t outFrames = mFrameCount;
+ while (outFrames) {
+ t->buffer.frameCount = outFrames;
+ t->bufferProvider->getNextBuffer(&t->buffer);
+ if (t->buffer.raw == NULL) break;
+ outFrames -= t->buffer.frameCount;
+ t->bufferProvider->releaseBuffer(&t->buffer);
}
}
}
}
// generic code without resampling
-void AudioMixer::process__genericNoResampling(state_t* state)
+void AudioMixer::process__genericNoResampling()
{
ALOGVV("process__genericNoResampling\n");
int32_t outTemp[BLOCKSIZE * MAX_NUM_CHANNELS] __attribute__((aligned(32)));
- // acquire each track's buffer
- uint32_t enabledTracks = state->enabledTracks;
- uint32_t e0 = enabledTracks;
- while (e0) {
- const int i = 31 - __builtin_clz(e0);
- e0 &= ~(1<<i);
- track_t& t = state->tracks[i];
- t.buffer.frameCount = state->frameCount;
- t.bufferProvider->getNextBuffer(&t.buffer);
- t.frameCount = t.buffer.frameCount;
- t.in = t.buffer.raw;
- }
+ for (const auto &pair : mGroups) {
+ // process by group of tracks with same output main buffer to
+ // avoid multiple memset() on same buffer
+ const auto &group = pair.second;
- e0 = enabledTracks;
- while (e0) {
- // process by group of tracks with same output buffer to
- // optimize cache use
- uint32_t e1 = e0, e2 = e0;
- int j = 31 - __builtin_clz(e1);
- track_t& t1 = state->tracks[j];
- e2 &= ~(1<<j);
- while (e2) {
- j = 31 - __builtin_clz(e2);
- e2 &= ~(1<<j);
- track_t& t2 = state->tracks[j];
- if (CC_UNLIKELY(t2.mainBuffer != t1.mainBuffer)) {
- e1 &= ~(1<<j);
- }
+ // acquire buffer
+ for (const int name : group) {
+ const std::shared_ptr<Track> &t = mTracks[name];
+ t->buffer.frameCount = mFrameCount;
+ t->bufferProvider->getNextBuffer(&t->buffer);
+ t->frameCount = t->buffer.frameCount;
+ t->mIn = t->buffer.raw;
}
- e0 &= ~(e1);
- // this assumes output 16 bits stereo, no resampling
- int32_t *out = t1.mainBuffer;
+
+ int32_t *out = (int *)pair.first;
size_t numFrames = 0;
do {
+ const size_t frameCount = std::min((size_t)BLOCKSIZE, mFrameCount - numFrames);
memset(outTemp, 0, sizeof(outTemp));
- e2 = e1;
- while (e2) {
- const int i = 31 - __builtin_clz(e2);
- e2 &= ~(1<<i);
- track_t& t = state->tracks[i];
- size_t outFrames = BLOCKSIZE;
+ for (const int name : group) {
+ const std::shared_ptr<Track> &t = mTracks[name];
int32_t *aux = NULL;
- if (CC_UNLIKELY(t.needs & NEEDS_AUX)) {
- aux = t.auxBuffer + numFrames;
+ if (CC_UNLIKELY(t->needs & NEEDS_AUX)) {
+ aux = t->auxBuffer + numFrames;
}
- while (outFrames) {
- // t.in == NULL can happen if the track was flushed just after having
+ for (int outFrames = frameCount; outFrames > 0; ) {
+ // t->in == nullptr can happen if the track was flushed just after having
// been enabled for mixing.
- if (t.in == NULL) {
- enabledTracks &= ~(1<<i);
- e1 &= ~(1<<i);
+ if (t->mIn == nullptr) {
break;
}
- size_t inFrames = (t.frameCount > outFrames)?outFrames:t.frameCount;
+ size_t inFrames = (t->frameCount > outFrames)?outFrames:t->frameCount;
if (inFrames > 0) {
- t.hook(&t, outTemp + (BLOCKSIZE - outFrames) * t.mMixerChannelCount,
- inFrames, state->resampleTemp, aux);
- t.frameCount -= inFrames;
+ (t.get()->*t->hook)(
+ outTemp + (frameCount - outFrames) * t->mMixerChannelCount,
+ inFrames, mResampleTemp.get() /* naked ptr */, aux);
+ t->frameCount -= inFrames;
outFrames -= inFrames;
if (CC_UNLIKELY(aux != NULL)) {
aux += inFrames;
}
}
- if (t.frameCount == 0 && outFrames) {
- t.bufferProvider->releaseBuffer(&t.buffer);
- t.buffer.frameCount = (state->frameCount - numFrames) -
- (BLOCKSIZE - outFrames);
- t.bufferProvider->getNextBuffer(&t.buffer);
- t.in = t.buffer.raw;
- if (t.in == NULL) {
- enabledTracks &= ~(1<<i);
- e1 &= ~(1<<i);
+ if (t->frameCount == 0 && outFrames) {
+ t->bufferProvider->releaseBuffer(&t->buffer);
+ t->buffer.frameCount = (mFrameCount - numFrames) -
+ (frameCount - outFrames);
+ t->bufferProvider->getNextBuffer(&t->buffer);
+ t->mIn = t->buffer.raw;
+ if (t->mIn == nullptr) {
break;
}
- t.frameCount = t.buffer.frameCount;
+ t->frameCount = t->buffer.frameCount;
}
}
}
- convertMixerFormat(out, t1.mMixerFormat, outTemp, t1.mMixerInFormat,
- BLOCKSIZE * t1.mMixerChannelCount);
+ const std::shared_ptr<Track> &t1 = mTracks[group[0]];
+ convertMixerFormat(out, t1->mMixerFormat, outTemp, t1->mMixerInFormat,
+ frameCount * t1->mMixerChannelCount);
// TODO: fix ugly casting due to choice of out pointer type
out = reinterpret_cast<int32_t*>((uint8_t*)out
- + BLOCKSIZE * t1.mMixerChannelCount
- * audio_bytes_per_sample(t1.mMixerFormat));
- numFrames += BLOCKSIZE;
- } while (numFrames < state->frameCount);
- }
+ + frameCount * t1->mMixerChannelCount
+ * audio_bytes_per_sample(t1->mMixerFormat));
+ numFrames += frameCount;
+ } while (numFrames < mFrameCount);
- // release each track's buffer
- e0 = enabledTracks;
- while (e0) {
- const int i = 31 - __builtin_clz(e0);
- e0 &= ~(1<<i);
- track_t& t = state->tracks[i];
- t.bufferProvider->releaseBuffer(&t.buffer);
+ // release each track's buffer
+ for (const int name : group) {
+ const std::shared_ptr<Track> &t = mTracks[name];
+ t->bufferProvider->releaseBuffer(&t->buffer);
+ }
}
}
-
// generic code with resampling
-void AudioMixer::process__genericResampling(state_t* state)
+void AudioMixer::process__genericResampling()
{
ALOGVV("process__genericResampling\n");
- // this const just means that local variable outTemp doesn't change
- int32_t* const outTemp = state->outputTemp;
- size_t numFrames = state->frameCount;
+ int32_t * const outTemp = mOutputTemp.get(); // naked ptr
+ size_t numFrames = mFrameCount;
- uint32_t e0 = state->enabledTracks;
- while (e0) {
- // process by group of tracks with same output buffer
- // to optimize cache use
- uint32_t e1 = e0, e2 = e0;
- int j = 31 - __builtin_clz(e1);
- track_t& t1 = state->tracks[j];
- e2 &= ~(1<<j);
- while (e2) {
- j = 31 - __builtin_clz(e2);
- e2 &= ~(1<<j);
- track_t& t2 = state->tracks[j];
- if (CC_UNLIKELY(t2.mainBuffer != t1.mainBuffer)) {
- e1 &= ~(1<<j);
- }
- }
- e0 &= ~(e1);
- int32_t *out = t1.mainBuffer;
- memset(outTemp, 0, sizeof(*outTemp) * t1.mMixerChannelCount * state->frameCount);
- while (e1) {
- const int i = 31 - __builtin_clz(e1);
- e1 &= ~(1<<i);
- track_t& t = state->tracks[i];
+ for (const auto &pair : mGroups) {
+ const auto &group = pair.second;
+ const std::shared_ptr<Track> &t1 = mTracks[group[0]];
+
+ // clear temp buffer
+ memset(outTemp, 0, sizeof(*outTemp) * t1->mMixerChannelCount * mFrameCount);
+ for (const int name : group) {
+ const std::shared_ptr<Track> &t = mTracks[name];
int32_t *aux = NULL;
- if (CC_UNLIKELY(t.needs & NEEDS_AUX)) {
- aux = t.auxBuffer;
+ if (CC_UNLIKELY(t->needs & NEEDS_AUX)) {
+ aux = t->auxBuffer;
}
// this is a little goofy, on the resampling case we don't
// acquire/release the buffers because it's done by
// the resampler.
- if (t.needs & NEEDS_RESAMPLE) {
- t.hook(&t, outTemp, numFrames, state->resampleTemp, aux);
+ if (t->needs & NEEDS_RESAMPLE) {
+ (t.get()->*t->hook)(outTemp, numFrames, mResampleTemp.get() /* naked ptr */, aux);
} else {
size_t outFrames = 0;
while (outFrames < numFrames) {
- t.buffer.frameCount = numFrames - outFrames;
- t.bufferProvider->getNextBuffer(&t.buffer);
- t.in = t.buffer.raw;
- // t.in == NULL can happen if the track was flushed just after having
+ t->buffer.frameCount = numFrames - outFrames;
+ t->bufferProvider->getNextBuffer(&t->buffer);
+ t->mIn = t->buffer.raw;
+ // t->mIn == nullptr can happen if the track was flushed just after having
// been enabled for mixing.
- if (t.in == NULL) break;
+ if (t->mIn == nullptr) break;
if (CC_UNLIKELY(aux != NULL)) {
aux += outFrames;
}
- t.hook(&t, outTemp + outFrames * t.mMixerChannelCount, t.buffer.frameCount,
- state->resampleTemp, aux);
- outFrames += t.buffer.frameCount;
- t.bufferProvider->releaseBuffer(&t.buffer);
+ (t.get()->*t->hook)(
+ outTemp + outFrames * t->mMixerChannelCount, t->buffer.frameCount,
+ mResampleTemp.get() /* naked ptr */, aux);
+ outFrames += t->buffer.frameCount;
+ t->bufferProvider->releaseBuffer(&t->buffer);
}
}
}
- convertMixerFormat(out, t1.mMixerFormat,
- outTemp, t1.mMixerInFormat, numFrames * t1.mMixerChannelCount);
+ convertMixerFormat(t1->mainBuffer, t1->mMixerFormat,
+ outTemp, t1->mMixerInFormat, numFrames * t1->mMixerChannelCount);
}
}
// one track, 16 bits stereo without resampling is the most common case
-void AudioMixer::process__OneTrack16BitsStereoNoResampling(state_t* state)
+void AudioMixer::process__oneTrack16BitsStereoNoResampling()
{
- ALOGVV("process__OneTrack16BitsStereoNoResampling\n");
- // This method is only called when state->enabledTracks has exactly
- // one bit set. The asserts below would verify this, but are commented out
- // since the whole point of this method is to optimize performance.
- //ALOG_ASSERT(0 != state->enabledTracks, "no tracks enabled");
- const int i = 31 - __builtin_clz(state->enabledTracks);
- //ALOG_ASSERT((1 << i) == state->enabledTracks, "more than 1 track enabled");
- const track_t& t = state->tracks[i];
+ ALOGVV("process__oneTrack16BitsStereoNoResampling\n");
+ LOG_ALWAYS_FATAL_IF(mEnabled.size() != 0,
+ "%zu != 1 tracks enabled", mEnabled.size());
+ const int name = mEnabled[0];
+ const std::shared_ptr<Track> &t = mTracks[name];
- AudioBufferProvider::Buffer& b(t.buffer);
+ AudioBufferProvider::Buffer& b(t->buffer);
- int32_t* out = t.mainBuffer;
+ int32_t* out = t->mainBuffer;
float *fout = reinterpret_cast<float*>(out);
- size_t numFrames = state->frameCount;
+ size_t numFrames = mFrameCount;
- const int16_t vl = t.volume[0];
- const int16_t vr = t.volume[1];
- const uint32_t vrl = t.volumeRL;
+ const int16_t vl = t->volume[0];
+ const int16_t vr = t->volume[1];
+ const uint32_t vrl = t->volumeRL;
while (numFrames) {
b.frameCount = numFrames;
- t.bufferProvider->getNextBuffer(&b);
+ t->bufferProvider->getNextBuffer(&b);
const int16_t *in = b.i16;
// in == NULL can happen if the track was flushed just after having
// been enabled for mixing.
if (in == NULL || (((uintptr_t)in) & 3)) {
- if ( AUDIO_FORMAT_PCM_FLOAT == t.mMixerFormat ) {
+ if ( AUDIO_FORMAT_PCM_FLOAT == t->mMixerFormat ) {
memset((char*)fout, 0, numFrames
- * t.mMixerChannelCount * audio_bytes_per_sample(t.mMixerFormat));
+ * t->mMixerChannelCount * audio_bytes_per_sample(t->mMixerFormat));
} else {
memset((char*)out, 0, numFrames
- * t.mMixerChannelCount * audio_bytes_per_sample(t.mMixerFormat));
+ * t->mMixerChannelCount * audio_bytes_per_sample(t->mMixerFormat));
}
ALOGE_IF((((uintptr_t)in) & 3),
- "process__OneTrack16BitsStereoNoResampling: misaligned buffer"
+ "process__oneTrack16BitsStereoNoResampling: misaligned buffer"
" %p track %d, channels %d, needs %08x, volume %08x vfl %f vfr %f",
- in, i, t.channelCount, t.needs, vrl, t.mVolume[0], t.mVolume[1]);
+ in, name, t->channelCount, t->needs, vrl, t->mVolume[0], t->mVolume[1]);
return;
}
size_t outFrames = b.frameCount;
- switch (t.mMixerFormat) {
+ switch (t->mMixerFormat) {
case AUDIO_FORMAT_PCM_FLOAT:
do {
uint32_t rl = *reinterpret_cast<const uint32_t *>(in);
@@ -1685,10 +1505,10 @@
}
break;
default:
- LOG_ALWAYS_FATAL("bad mixer format: %d", t.mMixerFormat);
+ LOG_ALWAYS_FATAL("bad mixer format: %d", t->mMixerFormat);
}
numFrames -= b.frameCount;
- t.bufferProvider->releaseBuffer(&b);
+ t->bufferProvider->releaseBuffer(&b);
}
}
@@ -1799,42 +1619,42 @@
*/
template <int MIXTYPE, bool USEFLOATVOL, bool ADJUSTVOL,
typename TO, typename TI, typename TA>
-void AudioMixer::volumeMix(TO *out, size_t outFrames,
- const TI *in, TA *aux, bool ramp, AudioMixer::track_t *t)
+void AudioMixer::Track::volumeMix(TO *out, size_t outFrames,
+ const TI *in, TA *aux, bool ramp)
{
if (USEFLOATVOL) {
if (ramp) {
- volumeRampMulti<MIXTYPE>(t->mMixerChannelCount, out, outFrames, in, aux,
- t->mPrevVolume, t->mVolumeInc,
+ volumeRampMulti<MIXTYPE>(mMixerChannelCount, out, outFrames, in, aux,
+ mPrevVolume, mVolumeInc,
#ifdef FLOAT_AUX
- &t->mPrevAuxLevel, t->mAuxInc
+ &mPrevAuxLevel, mAuxInc
#else
- &t->prevAuxLevel, t->auxInc
+ &prevAuxLevel, auxInc
#endif
);
if (ADJUSTVOL) {
- t->adjustVolumeRamp(aux != NULL, true);
+ adjustVolumeRamp(aux != NULL, true);
}
} else {
- volumeMulti<MIXTYPE>(t->mMixerChannelCount, out, outFrames, in, aux,
- t->mVolume,
+ volumeMulti<MIXTYPE>(mMixerChannelCount, out, outFrames, in, aux,
+ mVolume,
#ifdef FLOAT_AUX
- t->mAuxLevel
+ mAuxLevel
#else
- t->auxLevel
+ auxLevel
#endif
);
}
} else {
if (ramp) {
- volumeRampMulti<MIXTYPE>(t->mMixerChannelCount, out, outFrames, in, aux,
- t->prevVolume, t->volumeInc, &t->prevAuxLevel, t->auxInc);
+ volumeRampMulti<MIXTYPE>(mMixerChannelCount, out, outFrames, in, aux,
+ prevVolume, volumeInc, &prevAuxLevel, auxInc);
if (ADJUSTVOL) {
- t->adjustVolumeRamp(aux != NULL);
+ adjustVolumeRamp(aux != NULL);
}
} else {
- volumeMulti<MIXTYPE>(t->mMixerChannelCount, out, outFrames, in, aux,
- t->volume, t->auxLevel);
+ volumeMulti<MIXTYPE>(mMixerChannelCount, out, outFrames, in, aux,
+ volume, auxLevel);
}
}
}
@@ -1849,19 +1669,18 @@
* TA: int32_t (Q4.27)
*/
template <int MIXTYPE, typename TO, typename TI, typename TA>
-void AudioMixer::process_NoResampleOneTrack(state_t* state)
+void AudioMixer::process__noResampleOneTrack()
{
- ALOGVV("process_NoResampleOneTrack\n");
- // CLZ is faster than CTZ on ARM, though really not sure if true after 31 - clz.
- const int i = 31 - __builtin_clz(state->enabledTracks);
- ALOG_ASSERT((1 << i) == state->enabledTracks, "more than 1 track enabled");
- track_t *t = &state->tracks[i];
+ ALOGVV("process__noResampleOneTrack\n");
+ LOG_ALWAYS_FATAL_IF(mEnabled.size() != 1,
+ "%zu != 1 tracks enabled", mEnabled.size());
+ const std::shared_ptr<Track> &t = mTracks[mEnabled[0]];
const uint32_t channels = t->mMixerChannelCount;
TO* out = reinterpret_cast<TO*>(t->mainBuffer);
TA* aux = reinterpret_cast<TA*>(t->auxBuffer);
const bool ramp = t->needsRamp();
- for (size_t numFrames = state->frameCount; numFrames; ) {
+ for (size_t numFrames = mFrameCount; numFrames > 0; ) {
AudioBufferProvider::Buffer& b(t->buffer);
// get input buffer
b.frameCount = numFrames;
@@ -1873,15 +1692,15 @@
if (in == NULL || (((uintptr_t)in) & 3)) {
memset(out, 0, numFrames
* channels * audio_bytes_per_sample(t->mMixerFormat));
- ALOGE_IF((((uintptr_t)in) & 3), "process_NoResampleOneTrack: bus error: "
+ ALOGE_IF((((uintptr_t)in) & 3), "process__noResampleOneTrack: bus error: "
"buffer %p track %p, channels %d, needs %#x",
- in, t, t->channelCount, t->needs);
+ in, &t, t->channelCount, t->needs);
return;
}
const size_t outFrames = b.frameCount;
- volumeMix<MIXTYPE, is_same<TI, float>::value /* USEFLOATVOL */, false /* ADJUSTVOL */> (
- out, outFrames, in, aux, ramp, t);
+ t->volumeMix<MIXTYPE, is_same<TI, float>::value /* USEFLOATVOL */, false /* ADJUSTVOL */> (
+ out, outFrames, in, aux, ramp);
out += outFrames * channels;
if (aux != NULL) {
@@ -1906,30 +1725,30 @@
* TA: int32_t (Q4.27) or float
*/
template <int MIXTYPE, typename TO, typename TI, typename TA>
-void AudioMixer::track__Resample(track_t* t, TO* out, size_t outFrameCount, TO* temp, TA* aux)
+void AudioMixer::Track::track__Resample(TO* out, size_t outFrameCount, TO* temp, TA* aux)
{
ALOGVV("track__Resample\n");
- t->resampler->setSampleRate(t->sampleRate);
- const bool ramp = t->needsRamp();
+ mResampler->setSampleRate(sampleRate);
+ const bool ramp = needsRamp();
if (ramp || aux != NULL) {
// if ramp: resample with unity gain to temp buffer and scale/mix in 2nd step.
// if aux != NULL: resample with unity gain to temp buffer then apply send level.
- t->resampler->setVolume(UNITY_GAIN_FLOAT, UNITY_GAIN_FLOAT);
- memset(temp, 0, outFrameCount * t->mMixerChannelCount * sizeof(TO));
- t->resampler->resample((int32_t*)temp, outFrameCount, t->bufferProvider);
+ mResampler->setVolume(UNITY_GAIN_FLOAT, UNITY_GAIN_FLOAT);
+ memset(temp, 0, outFrameCount * mMixerChannelCount * sizeof(TO));
+ mResampler->resample((int32_t*)temp, outFrameCount, bufferProvider);
volumeMix<MIXTYPE, is_same<TI, float>::value /* USEFLOATVOL */, true /* ADJUSTVOL */>(
- out, outFrameCount, temp, aux, ramp, t);
+ out, outFrameCount, temp, aux, ramp);
} else { // constant volume gain
- t->resampler->setVolume(t->mVolume[0], t->mVolume[1]);
- t->resampler->resample((int32_t*)out, outFrameCount, t->bufferProvider);
+ mResampler->setVolume(mVolume[0], mVolume[1]);
+ mResampler->resample((int32_t*)out, outFrameCount, bufferProvider);
}
}
/* This track hook is called to mix a track, when no resampling is required.
- * The input buffer should be present in t->in.
+ * The input buffer should be present in in.
*
* MIXTYPE (see AudioMixerOps.h MIXTYPE_* enumeration)
* TO: int32_t (Q4.27) or float
@@ -1937,25 +1756,25 @@
* TA: int32_t (Q4.27) or float
*/
template <int MIXTYPE, typename TO, typename TI, typename TA>
-void AudioMixer::track__NoResample(track_t* t, TO* out, size_t frameCount,
- TO* temp __unused, TA* aux)
+void AudioMixer::Track::track__NoResample(TO* out, size_t frameCount, TO* temp __unused, TA* aux)
{
ALOGVV("track__NoResample\n");
- const TI *in = static_cast<const TI *>(t->in);
+ const TI *in = static_cast<const TI *>(mIn);
volumeMix<MIXTYPE, is_same<TI, float>::value /* USEFLOATVOL */, true /* ADJUSTVOL */>(
- out, frameCount, in, aux, t->needsRamp(), t);
+ out, frameCount, in, aux, needsRamp());
// MIXTYPE_MONOEXPAND reads a single input channel and expands to NCHAN output channels.
// MIXTYPE_MULTI reads NCHAN input channels and places to NCHAN output channels.
- in += (MIXTYPE == MIXTYPE_MONOEXPAND) ? frameCount : frameCount * t->mMixerChannelCount;
- t->in = in;
+ in += (MIXTYPE == MIXTYPE_MONOEXPAND) ? frameCount : frameCount * mMixerChannelCount;
+ mIn = in;
}
/* The Mixer engine generates either int32_t (Q4_27) or float data.
* We use this function to convert the engine buffers
* to the desired mixer output format, either int16_t (Q.15) or float.
*/
+/* static */
void AudioMixer::convertMixerFormat(void *out, audio_format_t mixerOutFormat,
void *in, audio_format_t mixerInFormat, size_t sampleCount)
{
@@ -1994,19 +1813,20 @@
/* Returns the proper track hook to use for mixing the track into the output buffer.
*/
-AudioMixer::hook_t AudioMixer::getTrackHook(int trackType, uint32_t channelCount,
+/* static */
+AudioMixer::hook_t AudioMixer::Track::getTrackHook(int trackType, uint32_t channelCount,
audio_format_t mixerInFormat, audio_format_t mixerOutFormat __unused)
{
if (!kUseNewMixer && channelCount == FCC_2 && mixerInFormat == AUDIO_FORMAT_PCM_16_BIT) {
switch (trackType) {
case TRACKTYPE_NOP:
- return track__nop;
+ return &Track::track__nop;
case TRACKTYPE_RESAMPLE:
- return track__genericResample;
+ return &Track::track__genericResample;
case TRACKTYPE_NORESAMPLEMONO:
- return track__16BitsMono;
+ return &Track::track__16BitsMono;
case TRACKTYPE_NORESAMPLE:
- return track__16BitsStereo;
+ return &Track::track__16BitsStereo;
default:
LOG_ALWAYS_FATAL("bad trackType: %d", trackType);
break;
@@ -2015,14 +1835,14 @@
LOG_ALWAYS_FATAL_IF(channelCount > MAX_NUM_CHANNELS);
switch (trackType) {
case TRACKTYPE_NOP:
- return track__nop;
+ return &Track::track__nop;
case TRACKTYPE_RESAMPLE:
switch (mixerInFormat) {
case AUDIO_FORMAT_PCM_FLOAT:
- return (AudioMixer::hook_t)track__Resample<
+ return (AudioMixer::hook_t) &Track::track__Resample<
MIXTYPE_MULTI, float /*TO*/, float /*TI*/, TYPE_AUX>;
case AUDIO_FORMAT_PCM_16_BIT:
- return (AudioMixer::hook_t)track__Resample<
+ return (AudioMixer::hook_t) &Track::track__Resample<
MIXTYPE_MULTI, int32_t /*TO*/, int16_t /*TI*/, TYPE_AUX>;
default:
LOG_ALWAYS_FATAL("bad mixerInFormat: %#x", mixerInFormat);
@@ -2032,10 +1852,10 @@
case TRACKTYPE_NORESAMPLEMONO:
switch (mixerInFormat) {
case AUDIO_FORMAT_PCM_FLOAT:
- return (AudioMixer::hook_t)track__NoResample<
+ return (AudioMixer::hook_t) &Track::track__NoResample<
MIXTYPE_MONOEXPAND, float /*TO*/, float /*TI*/, TYPE_AUX>;
case AUDIO_FORMAT_PCM_16_BIT:
- return (AudioMixer::hook_t)track__NoResample<
+ return (AudioMixer::hook_t) &Track::track__NoResample<
MIXTYPE_MONOEXPAND, int32_t /*TO*/, int16_t /*TI*/, TYPE_AUX>;
default:
LOG_ALWAYS_FATAL("bad mixerInFormat: %#x", mixerInFormat);
@@ -2045,10 +1865,10 @@
case TRACKTYPE_NORESAMPLE:
switch (mixerInFormat) {
case AUDIO_FORMAT_PCM_FLOAT:
- return (AudioMixer::hook_t)track__NoResample<
+ return (AudioMixer::hook_t) &Track::track__NoResample<
MIXTYPE_MULTI, float /*TO*/, float /*TI*/, TYPE_AUX>;
case AUDIO_FORMAT_PCM_16_BIT:
- return (AudioMixer::hook_t)track__NoResample<
+ return (AudioMixer::hook_t) &Track::track__NoResample<
MIXTYPE_MULTI, int32_t /*TO*/, int16_t /*TI*/, TYPE_AUX>;
default:
LOG_ALWAYS_FATAL("bad mixerInFormat: %#x", mixerInFormat);
@@ -2069,7 +1889,9 @@
* a stereo output track, the input track cannot be MONO. This should be
* prevented by the caller.
*/
-AudioMixer::process_hook_t AudioMixer::getProcessHook(int processType, uint32_t channelCount,
+/* static */
+AudioMixer::process_hook_t AudioMixer::getProcessHook(
+ int processType, uint32_t channelCount,
audio_format_t mixerInFormat, audio_format_t mixerOutFormat)
{
if (processType != PROCESSTYPE_NORESAMPLEONETRACK) { // Only NORESAMPLEONETRACK
@@ -2077,17 +1899,17 @@
return NULL;
}
if (!kUseNewMixer && channelCount == FCC_2 && mixerInFormat == AUDIO_FORMAT_PCM_16_BIT) {
- return process__OneTrack16BitsStereoNoResampling;
+ return &AudioMixer::process__oneTrack16BitsStereoNoResampling;
}
LOG_ALWAYS_FATAL_IF(channelCount > MAX_NUM_CHANNELS);
switch (mixerInFormat) {
case AUDIO_FORMAT_PCM_FLOAT:
switch (mixerOutFormat) {
case AUDIO_FORMAT_PCM_FLOAT:
- return process_NoResampleOneTrack<
+ return &AudioMixer::process__noResampleOneTrack<
MIXTYPE_MULTI_SAVEONLY, float /*TO*/, float /*TI*/, TYPE_AUX>;
case AUDIO_FORMAT_PCM_16_BIT:
- return process_NoResampleOneTrack<
+ return &AudioMixer::process__noResampleOneTrack<
MIXTYPE_MULTI_SAVEONLY, int16_t /*TO*/, float /*TI*/, TYPE_AUX>;
default:
LOG_ALWAYS_FATAL("bad mixerOutFormat: %#x", mixerOutFormat);
@@ -2097,10 +1919,10 @@
case AUDIO_FORMAT_PCM_16_BIT:
switch (mixerOutFormat) {
case AUDIO_FORMAT_PCM_FLOAT:
- return process_NoResampleOneTrack<
+ return &AudioMixer::process__noResampleOneTrack<
MIXTYPE_MULTI_SAVEONLY, float /*TO*/, int16_t /*TI*/, TYPE_AUX>;
case AUDIO_FORMAT_PCM_16_BIT:
- return process_NoResampleOneTrack<
+ return &AudioMixer::process__noResampleOneTrack<
MIXTYPE_MULTI_SAVEONLY, int16_t /*TO*/, int16_t /*TI*/, TYPE_AUX>;
default:
LOG_ALWAYS_FATAL("bad mixerOutFormat: %#x", mixerOutFormat);
diff --git a/media/libaudioprocessing/BufferProviders.cpp b/media/libaudioprocessing/BufferProviders.cpp
index 862fef6..e19af4a 100644
--- a/media/libaudioprocessing/BufferProviders.cpp
+++ b/media/libaudioprocessing/BufferProviders.cpp
@@ -183,7 +183,7 @@
mOutFrameSize =
audio_bytes_per_sample(format) * audio_channel_count_from_out_mask(outputChannelMask);
status_t status;
- status = EffectBufferHalInterface::mirror(
+ status = mEffectsFactory->mirrorBuffer(
nullptr, mInFrameSize * bufferFrameCount, &mInBuffer);
if (status != 0) {
ALOGE("DownmixerBufferProvider() error %d while creating input buffer", status);
@@ -191,7 +191,7 @@
mEffectsFactory.clear();
return;
}
- status = EffectBufferHalInterface::mirror(
+ status = mEffectsFactory->mirrorBuffer(
nullptr, mOutFrameSize * bufferFrameCount, &mOutBuffer);
if (status != 0) {
ALOGE("DownmixerBufferProvider() error %d while creating output buffer", status);
diff --git a/media/libmedia/Android.bp b/media/libmedia/Android.bp
index fd7400a..28684da 100644
--- a/media/libmedia/Android.bp
+++ b/media/libmedia/Android.bp
@@ -146,7 +146,7 @@
],
}
-cc_library_shared {
+cc_library {
name: "libmedia",
srcs: [
@@ -249,7 +249,7 @@
},
}
-cc_library_shared {
+cc_library {
name: "libmedia_player2_util",
srcs: [
@@ -259,6 +259,7 @@
"IMediaExtractorService.cpp",
"IMediaSource.cpp",
"IStreamSource.cpp",
+ "MediaCodecBuffer.cpp",
"MediaUtils.cpp",
"Metadata.cpp",
"NdkWrapper.cpp",
@@ -267,7 +268,6 @@
shared_libs: [
"libbinder",
"libcutils",
- "libgui",
"liblog",
"libmediaextractor",
"libmediandk",
@@ -287,9 +287,6 @@
],
static_libs: [
- "libc_malloc_debug_backtrace", // for memory heap analysis
-
- "libstagefright_nuplayer2",
"libstagefright_rtsp",
"libstagefright_timedtext",
],
@@ -316,16 +313,14 @@
},
}
-cc_library_shared {
+cc_library {
name: "libmedia_player2",
srcs: [
- "AudioParameter.cpp",
"JAudioTrack.cpp",
"MediaPlayer2Factory.cpp",
"MediaPlayer2Manager.cpp",
"TestPlayerStub.cpp",
- "TypeConverter.cpp",
"mediaplayer2.cpp",
],
@@ -366,8 +361,7 @@
],
static_libs: [
- "libc_malloc_debug_backtrace", // for memory heap analysis
-
+ "libmedia_helper",
"libstagefright_nuplayer2",
"libstagefright_rtsp",
"libstagefright_timedtext",
diff --git a/media/libmedia/JAudioTrack.cpp b/media/libmedia/JAudioTrack.cpp
index b228d8b..99da0f7 100644
--- a/media/libmedia/JAudioTrack.cpp
+++ b/media/libmedia/JAudioTrack.cpp
@@ -112,6 +112,11 @@
return env->CallIntMethod(mAudioTrackObj, jGetChannelCount);
}
+uint32_t JAudioTrack::latency() {
+ // TODO: Currently hard-coded as returning zero.
+ return 0;
+}
+
status_t JAudioTrack::getPosition(uint32_t *position) {
if (position == NULL) {
return BAD_VALUE;
@@ -125,7 +130,7 @@
return NO_ERROR;
}
-bool JAudioTrack::getTimeStamp(AudioTimestamp& timestamp) {
+bool JAudioTrack::getTimestamp(AudioTimestamp& timestamp) {
JNIEnv *env = AndroidRuntime::getJNIEnv();
jclass jAudioTimeStampCls = env->FindClass("android/media/AudioTimestamp");
@@ -392,6 +397,51 @@
return audioFormatToNative(javaFormat);
}
+status_t JAudioTrack::dump(int fd, const Vector<String16>& args __unused) const
+{
+ String8 result;
+
+ result.append(" JAudioTrack::dump\n");
+
+ // TODO: Remove logs that includes unavailable information from below.
+// result.appendFormat(" status(%d), state(%d), session Id(%d), flags(%#x)\n",
+// mStatus, mState, mSessionId, mFlags);
+// result.appendFormat(" stream type(%d), left - right volume(%f, %f)\n",
+// (mStreamType == AUDIO_STREAM_DEFAULT) ?
+// audio_attributes_to_stream_type(&mAttributes) : mStreamType,
+// mVolume[AUDIO_INTERLEAVE_LEFT], mVolume[AUDIO_INTERLEAVE_RIGHT]);
+// result.appendFormat(" format(%#x), channel mask(%#x), channel count(%u)\n",
+// format(), mChannelMask, channelCount());
+// result.appendFormat(" sample rate(%u), original sample rate(%u), speed(%f)\n",
+// getSampleRate(), mOriginalSampleRate, mPlaybackRate.mSpeed);
+// result.appendFormat(" frame count(%zu), req. frame count(%zu)\n",
+// frameCount(), mReqFrameCount);
+// result.appendFormat(" notif. frame count(%u), req. notif. frame count(%u),"
+// " req. notif. per buff(%u)\n",
+// mNotificationFramesAct, mNotificationFramesReq, mNotificationsPerBufferReq);
+// result.appendFormat(" latency (%d), selected device Id(%d), routed device Id(%d)\n",
+// latency(), mSelectedDeviceId, getRoutedDeviceId());
+// result.appendFormat(" output(%d) AF latency (%u) AF frame count(%zu) AF SampleRate(%u)\n",
+// mOutput, mAfLatency, mAfFrameCount, mAfSampleRate);
+ ::write(fd, result.string(), result.size());
+ return NO_ERROR;
+}
+
+audio_port_handle_t JAudioTrack::getRoutedDeviceId() {
+ JNIEnv *env = AndroidRuntime::getJNIEnv();
+ jmethodID jGetRoutedDevice = env->GetMethodID(mAudioTrackCls, "getRoutedDevice",
+ "()Landroid/media/AudioDeviceInfo;");
+ jobject jAudioDeviceInfoObj = env->CallObjectMethod(mAudioTrackObj, jGetRoutedDevice);
+ if (env->IsSameObject(jAudioDeviceInfoObj, NULL)) {
+ return AUDIO_PORT_HANDLE_NONE;
+ }
+
+ jclass jAudioDeviceInfoCls = env->FindClass("Landroid/media/AudioDeviceInfo");
+ jmethodID jGetId = env->GetMethodID(jAudioDeviceInfoCls, "getId", "()I");
+ jint routedDeviceId = env->CallIntMethod(jAudioDeviceInfoObj, jGetId);
+ return routedDeviceId;
+}
+
jobject JAudioTrack::createVolumeShaperConfigurationObj(
const sp<media::VolumeShaper::Configuration>& config) {
diff --git a/media/libmedia/MediaCodecBuffer.cpp b/media/libmedia/MediaCodecBuffer.cpp
index 59d6164..68ae3ea 100644
--- a/media/libmedia/MediaCodecBuffer.cpp
+++ b/media/libmedia/MediaCodecBuffer.cpp
@@ -21,15 +21,13 @@
#include <media/MediaCodecBuffer.h>
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/AMessage.h>
-#include <media/stagefright/foundation/MediaBufferBase.h>
namespace android {
MediaCodecBuffer::MediaCodecBuffer(const sp<AMessage> &format, const sp<ABuffer> &buffer)
: mMeta(new AMessage),
mFormat(format),
- mBuffer(buffer),
- mMediaBufferBase(nullptr) {
+ mBuffer(buffer) {
}
// ABuffer-like interface
@@ -58,20 +56,6 @@
return OK;
}
-MediaBufferBase *MediaCodecBuffer::getMediaBufferBase() {
- if (mMediaBufferBase != NULL) {
- mMediaBufferBase->add_ref();
- }
- return mMediaBufferBase;
-}
-
-void MediaCodecBuffer::setMediaBufferBase(MediaBufferBase *mediaBuffer) {
- if (mMediaBufferBase != NULL) {
- mMediaBufferBase->release();
- }
- mMediaBufferBase = mediaBuffer;
-}
-
sp<AMessage> MediaCodecBuffer::meta() {
return mMeta;
}
diff --git a/media/libmedia/MediaPlayer2Factory.cpp b/media/libmedia/MediaPlayer2Factory.cpp
index d6aab70..df567ce 100644
--- a/media/libmedia/MediaPlayer2Factory.cpp
+++ b/media/libmedia/MediaPlayer2Factory.cpp
@@ -22,7 +22,6 @@
#include <cutils/properties.h>
#include <media/DataSource.h>
#include <media/MediaPlayer2Engine.h>
-#include <media/stagefright/FileSource.h>
#include <media/stagefright/foundation/ADebug.h>
#include <utils/Errors.h>
#include <utils/misc.h>
diff --git a/media/libmedia/MediaPlayer2Manager.cpp b/media/libmedia/MediaPlayer2Manager.cpp
index 720c1e3..c119750 100644
--- a/media/libmedia/MediaPlayer2Manager.cpp
+++ b/media/libmedia/MediaPlayer2Manager.cpp
@@ -64,6 +64,7 @@
#include <memunreachable/memunreachable.h>
#include <system/audio.h>
+#include <system/window.h>
#include <private/android_filesystem_config.h>
@@ -470,8 +471,9 @@
if (unreachableMemory) {
result.append("\nDumping unreachable memory:\n");
// TODO - should limit be an argument parameter?
- std::string s = GetUnreachableMemoryString(true /* contents */, 10000 /* limit */);
- result.append(s.c_str(), s.size());
+ // TODO: enable GetUnreachableMemoryString if it's part of stable API
+ //std::string s = GetUnreachableMemoryString(true /* contents */, 10000 /* limit */);
+ //result.append(s.c_str(), s.size());
}
}
write(fd, result.string(), result.size());
@@ -738,8 +740,8 @@
void MediaPlayer2Manager::Client::disconnectNativeWindow_l() {
if (mConnectedWindow != NULL && mConnectedWindow->getANativeWindow() != NULL) {
- status_t err = nativeWindowDisconnect(
- mConnectedWindow->getANativeWindow(), "disconnectNativeWindow");
+ status_t err = native_window_api_disconnect(
+ mConnectedWindow->getANativeWindow(), NATIVE_WINDOW_API_MEDIA);
if (err != OK) {
ALOGW("nativeWindowDisconnect returned an error: %s (%d)",
@@ -763,7 +765,8 @@
&& mConnectedWindow->getANativeWindow() == nww->getANativeWindow()) {
return OK;
}
- status_t err = nativeWindowConnect(nww->getANativeWindow(), "setVideoSurfaceTexture");
+ status_t err = native_window_api_connect(
+ nww->getANativeWindow(), NATIVE_WINDOW_API_MEDIA);
if (err != OK) {
ALOGE("setVideoSurfaceTexture failed: %d", err);
@@ -792,8 +795,8 @@
mLock.unlock();
} else if (nww != NULL) {
mLock.unlock();
- status_t err = nativeWindowDisconnect(
- nww->getANativeWindow(), "disconnectNativeWindow");
+ status_t err = native_window_api_disconnect(
+ nww->getANativeWindow(), NATIVE_WINDOW_API_MEDIA);
if (err != OK) {
ALOGW("nativeWindowDisconnect returned an error: %s (%d)",
diff --git a/media/libmedia/TypeConverter.cpp b/media/libmedia/TypeConverter.cpp
index 9b06047..4cadeb1 100644
--- a/media/libmedia/TypeConverter.cpp
+++ b/media/libmedia/TypeConverter.cpp
@@ -115,6 +115,9 @@
MAKE_STRING_FROM_ENUM(AUDIO_OUTPUT_FLAG_DIRECT_PCM),
MAKE_STRING_FROM_ENUM(AUDIO_OUTPUT_FLAG_MMAP_NOIRQ),
MAKE_STRING_FROM_ENUM(AUDIO_OUTPUT_FLAG_VOIP_RX),
+ // FIXME: this cast will be removed when the flag will be
+ // declared in types.hal for audio HAL V4.0 and auto imported to audio-base.h
+ {"AUDIO_OUTPUT_FLAG_INCALL_MUSIC", static_cast<audio_output_flags_t>(AUDIO_OUTPUT_FLAG_INCALL_MUSIC)},
TERMINATOR
};
diff --git a/media/libmedia/include/media/CounterMetric.h b/media/libmedia/include/media/CounterMetric.h
new file mode 100644
index 0000000..b53470d
--- /dev/null
+++ b/media/libmedia/include/media/CounterMetric.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef ANDROID_COUNTER_METRIC_H_
+#define ANDROID_COUNTER_METRIC_H_
+
+#include <functional>
+#include <map>
+#include <string>
+
+#include <media/MediaAnalyticsItem.h>
+#include <utils/Log.h>
+
+namespace android {
+
+
+// The CounterMetric class is used to hold counts of operations or events.
+// A CounterMetric can break down counts by a dimension specified by the
+// application. E.g. an application may want to track counts broken out by
+// error code or the size of some parameter.
+//
+// Example:
+//
+// CounterMetric<status_t> workCounter;
+// workCounter("workCounterName", "result_status");
+//
+// status_t err = DoWork();
+//
+// // Increments the number of times called with the given error code.
+// workCounter.Increment(err);
+//
+// std::map<int, int64_t> values;
+// metric.ExportValues(
+// [&] (int attribute_value, int64_t value) {
+// values[attribute_value] = value;
+// });
+//
+// // Do something with the exported stat.
+//
+template<typename AttributeType>
+class CounterMetric {
+ public:
+ // Instantiate the counter with the given metric name and
+ // attribute names. |attribute_names| must not be null.
+ CounterMetric(
+ const std::string& metric_name,
+ const std::string& attribute_name)
+ : metric_name_(metric_name),
+ attribute_name_(attribute_name) {}
+
+ // Increment the count of times the operation occurred with this
+ // combination of attributes.
+ void Increment(AttributeType attribute) {
+ if (values_.find(attribute) == values_.end()) {
+ values_[attribute] = 1;
+ } else {
+ values_[attribute] = values_[attribute] + 1;
+ }
+ };
+
+ // Export the metrics to the provided |function|. Each value for Attribute
+ // has a separate count. As such, |function| will be called once per value
+ // of Attribute.
+ void ExportValues(
+ std::function<void (const AttributeType&,
+ const int64_t count)> function) const {
+ for (auto it = values_.begin(); it != values_.end(); it++) {
+ function(it->first, it->second);
+ }
+ }
+
+ const std::string& metric_name() const { return metric_name_; };
+
+ private:
+ const std::string metric_name_;
+ const std::string attribute_name_;
+ std::map<AttributeType, int64_t> values_;
+};
+
+} // namespace android
+
+#endif // ANDROID_COUNTER_METRIC_H_
diff --git a/media/libmedia/include/media/DrmHal.h b/media/libmedia/include/media/DrmHal.h
index 5d25e4d..c18d845 100644
--- a/media/libmedia/include/media/DrmHal.h
+++ b/media/libmedia/include/media/DrmHal.h
@@ -19,18 +19,22 @@
#define DRM_HAL_H_
#include <android/hardware/drm/1.0/IDrmPlugin.h>
+#include <android/hardware/drm/1.1/IDrmPlugin.h>
#include <android/hardware/drm/1.0/IDrmPluginListener.h>
#include <android/hardware/drm/1.0/IDrmFactory.h>
+#include <media/DrmMetrics.h>
#include <media/IDrm.h>
#include <media/IDrmClient.h>
+#include <media/MediaAnalyticsItem.h>
#include <utils/threads.h>
-using ::android::hardware::drm::V1_0::EventType;
-using ::android::hardware::drm::V1_0::IDrmFactory;
-using ::android::hardware::drm::V1_0::IDrmPlugin;
-using ::android::hardware::drm::V1_0::IDrmPluginListener;
-using ::android::hardware::drm::V1_0::KeyStatus;
+namespace drm = ::android::hardware::drm;
+using drm::V1_0::EventType;
+using drm::V1_0::IDrmFactory;
+using drm::V1_0::IDrmPlugin;
+using drm::V1_0::IDrmPluginListener;
+using drm::V1_0::KeyStatus;
using ::android::hardware::hidl_vec;
using ::android::hardware::Return;
using ::android::hardware::Void;
@@ -98,12 +102,22 @@
virtual status_t releaseSecureStops(Vector<uint8_t> const &ssRelease);
virtual status_t releaseAllSecureStops();
+ virtual status_t getHdcpLevels(DrmPlugin::HdcpLevel *connectedLevel,
+ DrmPlugin::HdcpLevel *maxLevel) const;
+ virtual status_t getNumberOfSessions(uint32_t *currentSessions,
+ uint32_t *maxSessions) const;
+ virtual status_t getSecurityLevel(Vector<uint8_t> const &sessionId,
+ DrmPlugin::SecurityLevel *level) const;
+ virtual status_t setSecurityLevel(Vector<uint8_t> const &sessionId,
+ const DrmPlugin::SecurityLevel& level);
+
virtual status_t getPropertyString(String8 const &name, String8 &value ) const;
virtual status_t getPropertyByteArray(String8 const &name,
Vector<uint8_t> &value ) const;
virtual status_t setPropertyString(String8 const &name, String8 const &value ) const;
virtual status_t setPropertyByteArray(String8 const &name,
Vector<uint8_t> const &value ) const;
+ virtual status_t getMetrics(MediaAnalyticsItem *item);
virtual status_t setCipherAlgorithm(Vector<uint8_t> const &sessionId,
String8 const &algorithm);
@@ -165,6 +179,10 @@
const Vector<sp<IDrmFactory>> mFactories;
sp<IDrmPlugin> mPlugin;
+ sp<drm::V1_1::IDrmPlugin> mPluginV1_1;
+
+ // Mutable to allow modification within GetPropertyByteArray.
+ mutable MediaDrmMetrics mMetrics;
Vector<Vector<uint8_t>> mOpenSessions;
void closeOpenSessions();
diff --git a/media/libmedia/include/media/DrmMetrics.h b/media/libmedia/include/media/DrmMetrics.h
new file mode 100644
index 0000000..bb7509b
--- /dev/null
+++ b/media/libmedia/include/media/DrmMetrics.h
@@ -0,0 +1,66 @@
+/*
+ * 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 DRM_METRICS_H_
+#define DRM_METRICS_H_
+
+#include <map>
+
+#include <android/hardware/drm/1.0/types.h>
+#include <media/CounterMetric.h>
+#include <media/EventMetric.h>
+
+namespace android {
+
+/**
+ * This class contains the definition of metrics captured within MediaDrm.
+ * It also contains a method for exporting all of the metrics to a
+ * MediaAnalyticsItem instance.
+ */
+class MediaDrmMetrics {
+ public:
+ explicit MediaDrmMetrics();
+ // Count of openSession calls.
+ CounterMetric<status_t> mOpenSessionCounter;
+ // Count of closeSession calls.
+ CounterMetric<status_t> mCloseSessionCounter;
+ // Count and timing of getKeyRequest calls.
+ EventMetric<status_t> mGetKeyRequestTiming;
+ // Count and timing of provideKeyResponse calls.
+ EventMetric<status_t> mProvideKeyResponseTiming;
+ // Count of getProvisionRequest calls.
+ CounterMetric<status_t> mGetProvisionRequestCounter;
+ // Count of provideProvisionResponse calls.
+ CounterMetric<status_t> mProvideProvisionResponseCounter;
+
+ // Count of key status events broken out by status type.
+ CounterMetric<::android::hardware::drm::V1_0::KeyStatusType>
+ mKeyStatusChangeCounter;
+ // Count of events broken out by event type
+ CounterMetric<::android::hardware::drm::V1_0::EventType> mEventCounter;
+
+ // Count getPropertyByteArray calls to retrieve the device unique id.
+ CounterMetric<status_t> mGetDeviceUniqueIdCounter;
+
+ // TODO: Add session start and end time support. These are a special case.
+
+ // Export the metrics to a MediaAnalyticsItem.
+ void Export(MediaAnalyticsItem* item);
+};
+
+} // namespace android
+
+#endif // DRM_METRICS_H_
diff --git a/media/libmedia/include/media/EventMetric.h b/media/libmedia/include/media/EventMetric.h
new file mode 100644
index 0000000..dbb736a
--- /dev/null
+++ b/media/libmedia/include/media/EventMetric.h
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef ANDROID_EVENT_METRIC_H_
+#define ANDROID_EVENT_METRIC_H_
+
+#include <media/MediaAnalyticsItem.h>
+#include <utils/Timers.h>
+
+namespace android {
+
+// This is a simple holder for the statistics recorded in EventMetric.
+struct EventStatistics {
+ // The count of times the event occurred.
+ int64_t count;
+
+ // The minimum and maximum values recorded in the Record method.
+ double min;
+ double max;
+
+ // The average (mean) of all values recorded.
+ double mean;
+ // The sum of squared devation. Variance can be calculated from
+ // this value.
+ // var = sum_squared_deviation / count;
+ double sum_squared_deviation;
+};
+
+// The EventMetric class is used to accumulate stats about an event over time.
+// A common use case is to track clock timings for a method call or operation.
+// An EventMetric can break down stats by a dimension specified by the
+// application. E.g. an application may want to track counts broken out by
+// error code or the size of some parameter.
+//
+// Example:
+//
+// struct C {
+// status_t DoWork() {
+// unsigned long start_time = now();
+// status_t result;
+//
+// // DO WORK and determine result;
+//
+// work_event_.Record(now() - start_time, result);
+//
+// return result;
+// }
+// EventMetric<status_t> work_event_;
+// };
+//
+// C c;
+// c.DoWork();
+//
+// std::map<int, int64_t> values;
+// metric.ExportValues(
+// [&] (int attribute_value, int64_t value) {
+// values[attribute_value] = value;
+// });
+// // Do something with the exported stat.
+//
+template<typename AttributeType>
+class EventMetric {
+ public:
+ // Instantiate the counter with the given metric name and
+ // attribute names. |attribute_names| must not be null.
+ EventMetric(
+ const std::string& metric_name,
+ const std::string& attribute_name)
+ : metric_name_(metric_name),
+ attribute_name_(attribute_name) {}
+
+ // Increment the count of times the operation occurred with this
+ // combination of attributes.
+ void Record(double value, AttributeType attribute) {
+ if (values_.find(attribute) != values_.end()) {
+ EventStatistics* stats = values_[attribute].get();
+ // Using method of provisional means.
+ double deviation = value - stats->mean;
+ stats->mean = stats->mean + (deviation / stats->count);
+ stats->sum_squared_deviation =
+ stats->sum_squared_deviation + (deviation * (value - stats->mean));
+ stats->count++;
+
+ stats->min = stats->min < value ? stats->min : value;
+ stats->max = stats->max > value ? stats->max : value;
+ } else {
+ std::unique_ptr<EventStatistics> stats =
+ std::make_unique<EventStatistics>();
+ stats->count = 1;
+ stats->min = value;
+ stats->max = value;
+ stats->mean = value;
+ stats->sum_squared_deviation = 0;
+ values_[attribute] = std::move(stats);
+ }
+ };
+
+ // Export the metrics to the provided |function|. Each value for Attribute
+ // has a separate set of stats. As such, |function| will be called once per
+ // value of Attribute.
+ void ExportValues(
+ std::function<void (const AttributeType&,
+ const EventStatistics&)> function) const {
+ for (auto it = values_.begin(); it != values_.end(); it++) {
+ function(it->first, *(it->second));
+ }
+ }
+
+ const std::string& metric_name() const { return metric_name_; };
+
+ private:
+ const std::string metric_name_;
+ const std::string attribute_name_;
+ std::map<AttributeType, std::unique_ptr<struct EventStatistics>> values_;
+};
+
+// The EventTimer is a supporting class for EventMetric instances that are used
+// to time methods. The EventTimer starts a timer when first in scope, and
+// records the timing when exiting scope.
+//
+// Example:
+//
+// EventMetric<int> my_metric;
+//
+// {
+// EventTimer<int> my_timer(&my_metric);
+// // Set the attribute to associate with this timing.
+// my_timer.SetAttribtue(42);
+//
+// // Do some work that you want to time.
+//
+// } // The EventTimer destructor will record the the timing in my_metric;
+//
+template<typename AttributeType>
+class EventTimer {
+ public:
+ explicit EventTimer(EventMetric<AttributeType>* metric)
+ :start_time_(systemTime()), metric_(metric) {
+ }
+
+ virtual ~EventTimer() {
+ if (metric_) {
+ metric_->Record(ns2us(systemTime() - start_time_), attribute_);
+ }
+ }
+
+ // Set the attribute to associate with this timing. E.g. this can be used to
+ // record the return code from the work that was timed.
+ void SetAttribute(const AttributeType& attribute) {
+ attribute_ = attribute;
+ }
+
+ protected:
+ // Visible for testing only.
+ nsecs_t start_time_;
+
+ private:
+ EventMetric<AttributeType>* metric_;
+ AttributeType attribute_;
+};
+
+} // namespace android
+
+#endif // ANDROID_EVENT_METRIC_H_
diff --git a/media/libmedia/include/media/IDrm.h b/media/libmedia/include/media/IDrm.h
index a57e372..9266f99 100644
--- a/media/libmedia/include/media/IDrm.h
+++ b/media/libmedia/include/media/IDrm.h
@@ -18,6 +18,7 @@
#include <media/stagefright/foundation/ABase.h>
#include <media/drm/DrmAPI.h>
#include <media/IDrmClient.h>
+#include <media/MediaAnalyticsItem.h>
#ifndef ANDROID_IDRM_H_
@@ -78,6 +79,16 @@
virtual status_t releaseSecureStops(Vector<uint8_t> const &ssRelease) = 0;
virtual status_t releaseAllSecureStops() = 0;
+ virtual status_t getHdcpLevels(DrmPlugin::HdcpLevel *connectedLevel,
+ DrmPlugin::HdcpLevel *maxLevel)
+ const = 0;
+ virtual status_t getNumberOfSessions(uint32_t *currentSessions,
+ uint32_t *maxSessions) const = 0;
+ virtual status_t getSecurityLevel(Vector<uint8_t> const &sessionId,
+ DrmPlugin::SecurityLevel *level) const = 0;
+ virtual status_t setSecurityLevel(Vector<uint8_t> const &sessionId,
+ const DrmPlugin::SecurityLevel& level) = 0;
+
virtual status_t getPropertyString(String8 const &name, String8 &value) const = 0;
virtual status_t getPropertyByteArray(String8 const &name,
Vector<uint8_t> &value) const = 0;
@@ -86,6 +97,8 @@
virtual status_t setPropertyByteArray(String8 const &name,
Vector<uint8_t> const &value) const = 0;
+ virtual status_t getMetrics(MediaAnalyticsItem *item) = 0;
+
virtual status_t setCipherAlgorithm(Vector<uint8_t> const &sessionId,
String8 const &algorithm) = 0;
diff --git a/media/libmedia/include/media/IMediaSource.h b/media/libmedia/include/media/IMediaSource.h
index 493742e..dabe231 100644
--- a/media/libmedia/include/media/IMediaSource.h
+++ b/media/libmedia/include/media/IMediaSource.h
@@ -28,7 +28,6 @@
namespace android {
-class MetaData;
class MediaBufferGroup;
class IMediaSource : public IInterface {
diff --git a/media/libmedia/include/media/JAudioTrack.h b/media/libmedia/include/media/JAudioTrack.h
index 8af30b7..10fa5e8 100644
--- a/media/libmedia/include/media/JAudioTrack.h
+++ b/media/libmedia/include/media/JAudioTrack.h
@@ -104,6 +104,12 @@
size_t frameCount();
size_t channelCount();
+ /* Returns this track's estimated latency in milliseconds.
+ * This includes the latency due to AudioTrack buffer size, AudioMixer (if any)
+ * and audio hardware driver.
+ */
+ uint32_t latency();
+
/* Return the total number of frames played since playback start.
* The counter will wrap (overflow) periodically, e.g. every ~27 hours at 44.1 kHz.
* It is reset to zero by flush(), reload(), and stop().
@@ -130,7 +136,7 @@
* Returns true if timestamp is valid.
* The timestamp parameter is undefined on return, if false is returned.
*/
- bool getTimeStamp(AudioTimestamp& timestamp);
+ bool getTimestamp(AudioTimestamp& timestamp);
/* Set source playback rate for timestretch
* 1.0 is normal speed: < 1.0 is slower, > 1.0 is faster
@@ -253,6 +259,17 @@
audio_format_t format();
+ /*
+ * Dumps the state of an audio track.
+ * Not a general-purpose API; intended only for use by media player service to dump its tracks.
+ */
+ status_t dump(int fd, const Vector<String16>& args) const;
+
+ /* Returns the ID of the audio device actually used by the output to which this AudioTrack is
+ * attached. When the AudioTrack is inactive, it will return AUDIO_PORT_HANDLE_NONE.
+ */
+ audio_port_handle_t getRoutedDeviceId();
+
private:
jclass mAudioTrackCls;
jobject mAudioTrackObj;
diff --git a/media/libmedia/include/media/MediaBufferHolder.h b/media/libmedia/include/media/MediaBufferHolder.h
new file mode 100644
index 0000000..e8e2c4b
--- /dev/null
+++ b/media/libmedia/include/media/MediaBufferHolder.h
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+#ifndef MEDIA_BUFFER_HOLDER_H_
+
+#define MEDIA_BUFFER_HOLDER_H_
+
+#include <media/stagefright/MediaBuffer.h>
+#include <utils/RefBase.h>
+
+namespace android {
+
+struct MediaBufferHolder : public RefBase {
+ MediaBufferHolder(MediaBuffer* buffer)
+ : mMediaBuffer(buffer) {
+ if (mMediaBuffer != nullptr) {
+ mMediaBuffer->add_ref();
+ }
+ }
+
+ virtual ~MediaBufferHolder() {
+ if (mMediaBuffer != nullptr) {
+ mMediaBuffer->release();
+ }
+ }
+
+ MediaBuffer* mediaBuffer() { return mMediaBuffer; }
+
+private:
+ MediaBuffer* const mMediaBuffer;
+};
+
+} // android
+
+#endif // MEDIA_BUFFER_HOLDER_H_
diff --git a/media/libmedia/include/media/MediaCodecBuffer.h b/media/libmedia/include/media/MediaCodecBuffer.h
index 501c00b..2c16fba 100644
--- a/media/libmedia/include/media/MediaCodecBuffer.h
+++ b/media/libmedia/include/media/MediaCodecBuffer.h
@@ -50,9 +50,6 @@
size_t offset() const;
// Default implementation calls ABuffer::setRange() and returns OK.
virtual status_t setRange(size_t offset, size_t size);
- // TODO: These can be removed if we finish replacing all MediaBuffer's.
- MediaBufferBase *getMediaBufferBase();
- void setMediaBufferBase(MediaBufferBase *mediaBuffer);
// TODO: Specify each field for meta/format.
sp<AMessage> meta();
@@ -66,7 +63,6 @@
const sp<AMessage> mMeta;
sp<AMessage> mFormat;
const sp<ABuffer> mBuffer;
- MediaBufferBase *mMediaBufferBase;
};
} // namespace android
diff --git a/media/libmedia/nuplayer2/Android.bp b/media/libmedia/nuplayer2/Android.bp
index d609ba0..700c840 100644
--- a/media/libmedia/nuplayer2/Android.bp
+++ b/media/libmedia/nuplayer2/Android.bp
@@ -22,7 +22,6 @@
],
include_dirs: [
- "frameworks/av/media/libmedia/include",
"frameworks/av/media/libstagefright",
"frameworks/av/media/libstagefright/httplive",
"frameworks/av/media/libstagefright/include",
@@ -54,6 +53,10 @@
"libpowermanager",
],
+ static_libs: [
+ "libmedia_helper",
+ ],
+
name: "libstagefright_nuplayer2",
tags: ["eng"],
diff --git a/media/libmedia/nuplayer2/GenericSource.cpp b/media/libmedia/nuplayer2/GenericSource.cpp
index 011691a..6d5b14d 100644
--- a/media/libmedia/nuplayer2/GenericSource.cpp
+++ b/media/libmedia/nuplayer2/GenericSource.cpp
@@ -24,6 +24,7 @@
#include <binder/IServiceManager.h>
#include <cutils/properties.h>
#include <media/DataSource.h>
+#include <media/MediaBufferHolder.h>
#include <media/IMediaExtractorService.h>
#include <media/MediaHTTPService.h>
#include <media/MediaExtractor.h>
@@ -33,7 +34,6 @@
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/AMessage.h>
#include <media/stagefright/DataSourceFactory.h>
-#include <media/stagefright/FileSource.h>
#include <media/stagefright/InterfaceUtils.h>
#include <media/stagefright/MediaBuffer.h>
#include <media/stagefright/MediaClock.h>
@@ -1168,8 +1168,7 @@
// data is already provided in the buffer
ab = new ABuffer(NULL, mb->range_length());
- mb->add_ref();
- ab->setMediaBufferBase(mb);
+ ab->meta()->setObject("mediaBufferHolder", new MediaBufferHolder(mb));
// Modular DRM: Required b/c of the above add_ref.
// If ref>0, there must be an observer, or it'll crash at release().
diff --git a/media/libmedia/nuplayer2/NuPlayer2Decoder.cpp b/media/libmedia/nuplayer2/NuPlayer2Decoder.cpp
index 25d41f3..a436592 100644
--- a/media/libmedia/nuplayer2/NuPlayer2Decoder.cpp
+++ b/media/libmedia/nuplayer2/NuPlayer2Decoder.cpp
@@ -28,6 +28,7 @@
#include "NuPlayer2Source.h"
#include <cutils/properties.h>
+#include <media/MediaBufferHolder.h>
#include <media/MediaCodecBuffer.h>
#include <media/NdkMediaCodec.h>
#include <media/NdkWrapper.h>
@@ -40,6 +41,7 @@
#include <media/stagefright/MediaErrors.h>
#include <media/stagefright/SurfaceUtils.h>
+#include <system/window.h>
#include "ATSParser.h"
namespace android {
@@ -241,22 +243,25 @@
//
// at this point MediaPlayer2Manager::client has already connected to the
// surface, which MediaCodec does not expect
- err = nativeWindowDisconnect(nww->getANativeWindow(), "kWhatSetVideoSurface(nww)");
+ err = native_window_api_disconnect(nww->getANativeWindow(),
+ NATIVE_WINDOW_API_MEDIA);
if (err == OK) {
err = mCodec->setOutputSurface(nww);
ALOGI_IF(err, "codec setOutputSurface returned: %d", err);
if (err == OK) {
// reconnect to the old surface as MPS::Client will expect to
// be able to disconnect from it.
- (void)nativeWindowConnect(mNativeWindow->getANativeWindow(),
- "kWhatSetVideoSurface(mNativeWindow)");
+ (void)native_window_api_connect(mNativeWindow->getANativeWindow(),
+ NATIVE_WINDOW_API_MEDIA);
+
mNativeWindow = nww;
}
}
if (err != OK) {
// reconnect to the new surface on error as MPS::Client will expect to
// be able to disconnect from it.
- (void)nativeWindowConnect(nww->getANativeWindow(), "kWhatSetVideoSurface(err)");
+ (void)native_window_api_connect(nww->getANativeWindow(),
+ NATIVE_WINDOW_API_MEDIA);
}
}
@@ -326,7 +331,8 @@
status_t err;
if (mNativeWindow != NULL && mNativeWindow->getANativeWindow() != NULL) {
// disconnect from surface as MediaCodec will reconnect
- err = nativeWindowDisconnect(mNativeWindow->getANativeWindow(), "onConfigure");
+ err = native_window_api_disconnect(mNativeWindow->getANativeWindow(),
+ NATIVE_WINDOW_API_MEDIA);
// We treat this as a warning, as this is a preparatory step.
// Codec will try to connect to the surface, which is where
// any error signaling will occur.
@@ -540,7 +546,8 @@
if (mNativeWindow != NULL && mNativeWindow->getANativeWindow() != NULL) {
// reconnect to surface as MediaCodec disconnected from it
- status_t error = nativeWindowConnect(mNativeWindow->getANativeWindow(), "onShutdown");
+ status_t error = native_window_api_connect(mNativeWindow->getANativeWindow(),
+ NATIVE_WINDOW_API_MEDIA);
ALOGW_IF(error != NO_ERROR,
"[%s] failed to connect to native window, error=%d",
mComponentName.c_str(), error);
@@ -1075,16 +1082,17 @@
memcpy(codecBuffer->data(), buffer->data(), buffer->size());
} else { // No buffer->data()
//Modular DRM
- mediaBuf = (MediaBuffer*)buffer->getMediaBufferBase();
+ sp<RefBase> holder;
+ if (buffer->meta()->findObject("mediaBufferHolder", &holder)) {
+ mediaBuf = (holder != nullptr) ?
+ static_cast<MediaBufferHolder*>(holder.get())->mediaBuffer() : nullptr;
+ }
if (mediaBuf != NULL) {
codecBuffer->setRange(0, mediaBuf->size());
memcpy(codecBuffer->data(), mediaBuf->data(), mediaBuf->size());
sp<MetaData> meta_data = mediaBuf->meta_data();
cryptInfo = AMediaCodecCryptoInfoWrapper::Create(meta_data);
-
- // since getMediaBuffer() has incremented the refCount
- mediaBuf->release();
} else { // No mediaBuf
ALOGE("onInputBufferFetched: buffer->data()/mediaBuf are NULL for %p",
buffer.get());
diff --git a/media/libmedia/nuplayer2/NuPlayer2Driver.cpp b/media/libmedia/nuplayer2/NuPlayer2Driver.cpp
index 629a7eb..ccfcc47 100644
--- a/media/libmedia/nuplayer2/NuPlayer2Driver.cpp
+++ b/media/libmedia/nuplayer2/NuPlayer2Driver.cpp
@@ -77,7 +77,7 @@
};
// key for media statistics
-static const char *kKeyPlayer = "nuplayer2";
+static const char *kKeyPlayer = "nuplayer";
// attrs for media statistics
static const char *kPlayerVMime = "android.media.mediaplayer.video.mime";
static const char *kPlayerVCodec = "android.media.mediaplayer.video.codec";
@@ -666,7 +666,7 @@
// re-init in case we prepare() and start() again.
delete mAnalyticsItem ;
- mAnalyticsItem = new MediaAnalyticsItem("nuplayer");
+ mAnalyticsItem = new MediaAnalyticsItem(kKeyPlayer);
if (mAnalyticsItem) {
mAnalyticsItem->generateSessionID();
mAnalyticsItem->setUid(mClientUid);
diff --git a/media/libmedia/nuplayer2/NuPlayer2Drm.h b/media/libmedia/nuplayer2/NuPlayer2Drm.h
index e762ccc..f9c8711 100644
--- a/media/libmedia/nuplayer2/NuPlayer2Drm.h
+++ b/media/libmedia/nuplayer2/NuPlayer2Drm.h
@@ -18,7 +18,6 @@
#define NUPLAYER2_DRM_H_
#include <binder/Parcel.h>
-#include <media/stagefright/MetaData.h> // for CryptInfo
namespace android {
diff --git a/media/libmedia/nuplayer2/NuPlayer2Renderer.cpp b/media/libmedia/nuplayer2/NuPlayer2Renderer.cpp
index 71f5dce..1a9f246 100644
--- a/media/libmedia/nuplayer2/NuPlayer2Renderer.cpp
+++ b/media/libmedia/nuplayer2/NuPlayer2Renderer.cpp
@@ -27,7 +27,6 @@
#include <media/stagefright/foundation/AUtils.h>
#include <media/stagefright/MediaClock.h>
#include <media/stagefright/MediaErrors.h>
-#include <media/stagefright/MetaData.h>
#include <media/stagefright/Utils.h>
#include <media/stagefright/VideoFrameScheduler.h>
#include <media/MediaCodecBuffer.h>
diff --git a/media/libmediaextractor/Android.bp b/media/libmediaextractor/Android.bp
index dcdb320..83fea39 100644
--- a/media/libmediaextractor/Android.bp
+++ b/media/libmediaextractor/Android.bp
@@ -1,4 +1,4 @@
-cc_library_shared {
+cc_library {
name: "libmediaextractor",
include_dirs: [
@@ -15,6 +15,7 @@
],
shared_libs: [
+ "libbinder",
"libstagefright_foundation",
"libutils",
"libcutils",
@@ -23,6 +24,8 @@
srcs: [
"DataSource.cpp",
+ "MediaBuffer.cpp",
+ "MediaBufferGroup.cpp",
"MediaSource.cpp",
"MediaExtractor.cpp",
],
diff --git a/media/libmediaextractor/MediaBuffer.cpp b/media/libmediaextractor/MediaBuffer.cpp
new file mode 100644
index 0000000..28fc760
--- /dev/null
+++ b/media/libmediaextractor/MediaBuffer.cpp
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "MediaBuffer"
+#include <utils/Log.h>
+
+#include <errno.h>
+#include <pthread.h>
+#include <stdlib.h>
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/MediaBuffer.h>
+#include <media/stagefright/MetaData.h>
+
+namespace android {
+
+/* static */
+std::atomic_int_least32_t MediaBuffer::mUseSharedMemory(0);
+
+MediaBuffer::MediaBuffer(void *data, size_t size)
+ : mObserver(NULL),
+ mRefCount(0),
+ mData(data),
+ mSize(size),
+ mRangeOffset(0),
+ mRangeLength(size),
+ mOwnsData(false),
+ mMetaData(new MetaData),
+ mOriginal(NULL) {
+}
+
+MediaBuffer::MediaBuffer(size_t size)
+ : mObserver(NULL),
+ mRefCount(0),
+ mData(NULL),
+ mSize(size),
+ mRangeOffset(0),
+ mRangeLength(size),
+ mOwnsData(true),
+ mMetaData(new MetaData),
+ mOriginal(NULL) {
+ if (size < kSharedMemThreshold
+ || std::atomic_load_explicit(&mUseSharedMemory, std::memory_order_seq_cst) == 0) {
+ mData = malloc(size);
+ } else {
+ ALOGV("creating memoryDealer");
+ sp<MemoryDealer> memoryDealer =
+ new MemoryDealer(size + sizeof(SharedControl), "MediaBuffer");
+ mMemory = memoryDealer->allocate(size + sizeof(SharedControl));
+ if (mMemory == NULL) {
+ ALOGW("Failed to allocate shared memory, trying regular allocation!");
+ mData = malloc(size);
+ if (mData == NULL) {
+ ALOGE("Out of memory");
+ }
+ } else {
+ getSharedControl()->clear();
+ mData = (uint8_t *)mMemory->pointer() + sizeof(SharedControl);
+ ALOGV("Allocated shared mem buffer of size %zu @ %p", size, mData);
+ }
+ }
+}
+
+MediaBuffer::MediaBuffer(const sp<ABuffer> &buffer)
+ : mObserver(NULL),
+ mRefCount(0),
+ mData(buffer->data()),
+ mSize(buffer->size()),
+ mRangeOffset(0),
+ mRangeLength(mSize),
+ mBuffer(buffer),
+ mOwnsData(false),
+ mMetaData(new MetaData),
+ mOriginal(NULL) {
+}
+
+void MediaBuffer::release() {
+ if (mObserver == NULL) {
+ // Legacy contract for MediaBuffer without a MediaBufferGroup.
+ CHECK_EQ(mRefCount, 0);
+ delete this;
+ return;
+ }
+
+ int prevCount = __sync_fetch_and_sub(&mRefCount, 1);
+ if (prevCount == 1) {
+ if (mObserver == NULL) {
+ delete this;
+ return;
+ }
+
+ mObserver->signalBufferReturned(this);
+ }
+ CHECK(prevCount > 0);
+}
+
+void MediaBuffer::claim() {
+ CHECK(mObserver != NULL);
+ CHECK_EQ(mRefCount, 1);
+
+ mRefCount = 0;
+}
+
+void MediaBuffer::add_ref() {
+ (void) __sync_fetch_and_add(&mRefCount, 1);
+}
+
+void *MediaBuffer::data() const {
+ return mData;
+}
+
+size_t MediaBuffer::size() const {
+ return mSize;
+}
+
+size_t MediaBuffer::range_offset() const {
+ return mRangeOffset;
+}
+
+size_t MediaBuffer::range_length() const {
+ return mRangeLength;
+}
+
+void MediaBuffer::set_range(size_t offset, size_t length) {
+ if (offset + length > mSize) {
+ ALOGE("offset = %zu, length = %zu, mSize = %zu", offset, length, mSize);
+ }
+ CHECK(offset + length <= mSize);
+
+ mRangeOffset = offset;
+ mRangeLength = length;
+}
+
+sp<MetaData> MediaBuffer::meta_data() {
+ return mMetaData;
+}
+
+void MediaBuffer::reset() {
+ mMetaData->clear();
+ set_range(0, mSize);
+}
+
+MediaBuffer::~MediaBuffer() {
+ CHECK(mObserver == NULL);
+
+ if (mOwnsData && mData != NULL && mMemory == NULL) {
+ free(mData);
+ mData = NULL;
+ }
+
+ if (mOriginal != NULL) {
+ mOriginal->release();
+ mOriginal = NULL;
+ }
+
+ if (mMemory.get() != nullptr) {
+ getSharedControl()->setDeadObject();
+ }
+}
+
+void MediaBuffer::setObserver(MediaBufferObserver *observer) {
+ CHECK(observer == NULL || mObserver == NULL);
+ mObserver = observer;
+}
+
+MediaBuffer *MediaBuffer::clone() {
+ MediaBuffer *buffer = new MediaBuffer(mData, mSize);
+ buffer->set_range(mRangeOffset, mRangeLength);
+ buffer->mMetaData = new MetaData(*mMetaData.get());
+
+ add_ref();
+ buffer->mOriginal = this;
+
+ return buffer;
+}
+
+} // namespace android
diff --git a/media/libstagefright/foundation/MediaBufferGroup.cpp b/media/libmediaextractor/MediaBufferGroup.cpp
similarity index 100%
rename from media/libstagefright/foundation/MediaBufferGroup.cpp
rename to media/libmediaextractor/MediaBufferGroup.cpp
diff --git a/media/libmediaextractor/include/media/MediaSource.h b/media/libmediaextractor/include/media/MediaSource.h
index 98b136b..25d691d 100644
--- a/media/libmediaextractor/include/media/MediaSource.h
+++ b/media/libmediaextractor/include/media/MediaSource.h
@@ -30,7 +30,6 @@
namespace android {
class MediaBuffer;
-class MetaData;
class IMediaSource;
struct MediaSource : public virtual RefBase {
diff --git a/media/libmediaextractor/include/media/stagefright/MediaBuffer.h b/media/libmediaextractor/include/media/stagefright/MediaBuffer.h
new file mode 100644
index 0000000..2b51081
--- /dev/null
+++ b/media/libmediaextractor/include/media/stagefright/MediaBuffer.h
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2009 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 MEDIA_BUFFER_H_
+
+#define MEDIA_BUFFER_H_
+
+#include <atomic>
+#include <list>
+
+#include <pthread.h>
+
+#include <binder/MemoryDealer.h>
+#include <utils/Errors.h>
+#include <utils/RefBase.h>
+
+namespace android {
+
+struct ABuffer;
+class GraphicBuffer;
+class MediaBuffer;
+class MediaBufferObserver;
+class MetaData;
+
+class MediaBufferObserver {
+public:
+ MediaBufferObserver() {}
+ virtual ~MediaBufferObserver() {}
+
+ virtual void signalBufferReturned(MediaBuffer *buffer) = 0;
+
+private:
+ MediaBufferObserver(const MediaBufferObserver &);
+ MediaBufferObserver &operator=(const MediaBufferObserver &);
+};
+
+class MediaBuffer {
+public:
+ // allocations larger than or equal to this will use shared memory.
+ static const size_t kSharedMemThreshold = 64 * 1024;
+
+ // The underlying data remains the responsibility of the caller!
+ MediaBuffer(void *data, size_t size);
+
+ explicit MediaBuffer(size_t size);
+
+ explicit MediaBuffer(const sp<ABuffer> &buffer);
+
+ MediaBuffer(const sp<IMemory> &mem) :
+ MediaBuffer((uint8_t *)mem->pointer() + sizeof(SharedControl), mem->size()) {
+ // delegate and override mMemory
+ mMemory = mem;
+ }
+
+ // If MediaBufferGroup is set, decrement the local reference count;
+ // if the local reference count drops to 0, return the buffer to the
+ // associated MediaBufferGroup.
+ //
+ // If no MediaBufferGroup is set, the local reference count must be zero
+ // when called, whereupon the MediaBuffer is deleted.
+ void release();
+
+ // Increments the local reference count.
+ // Use only when MediaBufferGroup is set.
+ void add_ref();
+
+ void *data() const;
+ size_t size() const;
+
+ size_t range_offset() const;
+ size_t range_length() const;
+
+ void set_range(size_t offset, size_t length);
+
+ sp<MetaData> meta_data();
+
+ // Clears meta data and resets the range to the full extent.
+ void reset();
+
+ void setObserver(MediaBufferObserver *group);
+
+ // Returns a clone of this MediaBuffer increasing its reference count.
+ // The clone references the same data but has its own range and
+ // MetaData.
+ MediaBuffer *clone();
+
+ // sum of localRefcount() and remoteRefcount()
+ int refcount() const {
+ return localRefcount() + remoteRefcount();
+ }
+
+ int localRefcount() const {
+ return mRefCount;
+ }
+
+ int remoteRefcount() const {
+ if (mMemory.get() == nullptr || mMemory->pointer() == nullptr) return 0;
+ int32_t remoteRefcount =
+ reinterpret_cast<SharedControl *>(mMemory->pointer())->getRemoteRefcount();
+ // Sanity check so that remoteRefCount() is non-negative.
+ return remoteRefcount >= 0 ? remoteRefcount : 0; // do not allow corrupted data.
+ }
+
+ // returns old value
+ int addRemoteRefcount(int32_t value) {
+ if (mMemory.get() == nullptr || mMemory->pointer() == nullptr) return 0;
+ return reinterpret_cast<SharedControl *>(mMemory->pointer())->addRemoteRefcount(value);
+ }
+
+ bool isDeadObject() const {
+ return isDeadObject(mMemory);
+ }
+
+ static bool isDeadObject(const sp<IMemory> &memory) {
+ if (memory.get() == nullptr || memory->pointer() == nullptr) return false;
+ return reinterpret_cast<SharedControl *>(memory->pointer())->isDeadObject();
+ }
+
+ // Sticky on enabling of shared memory MediaBuffers. By default we don't use
+ // shared memory for MediaBuffers, but we enable this for those processes
+ // that export MediaBuffers.
+ static void useSharedMemory() {
+ std::atomic_store_explicit(
+ &mUseSharedMemory, (int_least32_t)1, std::memory_order_seq_cst);
+ }
+
+protected:
+ // true if MediaBuffer is observed (part of a MediaBufferGroup).
+ inline bool isObserved() const {
+ return mObserver != nullptr;
+ }
+
+ ~MediaBuffer();
+
+ sp<IMemory> mMemory;
+
+private:
+ friend class MediaBufferGroup;
+ friend class OMXDecoder;
+ friend class BnMediaSource;
+ friend class BpMediaSource;
+
+ // For use by OMXDecoder, reference count must be 1, drop reference
+ // count to 0 without signalling the observer.
+ void claim();
+
+ MediaBufferObserver *mObserver;
+ int mRefCount;
+
+ void *mData;
+ size_t mSize, mRangeOffset, mRangeLength;
+ sp<ABuffer> mBuffer;
+
+ bool mOwnsData;
+
+ sp<MetaData> mMetaData;
+
+ MediaBuffer *mOriginal;
+
+ static std::atomic_int_least32_t mUseSharedMemory;
+
+ MediaBuffer(const MediaBuffer &);
+ MediaBuffer &operator=(const MediaBuffer &);
+
+ // SharedControl block at the start of IMemory.
+ struct SharedControl {
+ enum {
+ FLAG_DEAD_OBJECT = (1 << 0),
+ };
+
+ // returns old value
+ inline int32_t addRemoteRefcount(int32_t value) {
+ return std::atomic_fetch_add_explicit(
+ &mRemoteRefcount, (int_least32_t)value, std::memory_order_seq_cst);
+ }
+
+ inline int32_t getRemoteRefcount() const {
+ return std::atomic_load_explicit(&mRemoteRefcount, std::memory_order_seq_cst);
+ }
+
+ inline void setRemoteRefcount(int32_t value) {
+ std::atomic_store_explicit(
+ &mRemoteRefcount, (int_least32_t)value, std::memory_order_seq_cst);
+ }
+
+ inline bool isDeadObject() const {
+ return (std::atomic_load_explicit(
+ &mFlags, std::memory_order_seq_cst) & FLAG_DEAD_OBJECT) != 0;
+ }
+
+ inline void setDeadObject() {
+ (void)std::atomic_fetch_or_explicit(
+ &mFlags, (int_least32_t)FLAG_DEAD_OBJECT, std::memory_order_seq_cst);
+ }
+
+ inline void clear() {
+ std::atomic_store_explicit(
+ &mFlags, (int_least32_t)0, std::memory_order_seq_cst);
+ std::atomic_store_explicit(
+ &mRemoteRefcount, (int_least32_t)0, std::memory_order_seq_cst);
+ }
+
+ private:
+ // Caution: atomic_int_fast32_t is 64 bits on LP64.
+ std::atomic_int_least32_t mFlags;
+ std::atomic_int_least32_t mRemoteRefcount;
+ int32_t unused[6] __attribute__((__unused__)); // additional buffer space
+ };
+
+ inline SharedControl *getSharedControl() const {
+ return reinterpret_cast<SharedControl *>(mMemory->pointer());
+ }
+};
+
+} // namespace android
+
+#endif // MEDIA_BUFFER_H_
diff --git a/media/libmediaextractor/include/media/stagefright/MediaBufferGroup.h b/media/libmediaextractor/include/media/stagefright/MediaBufferGroup.h
new file mode 100644
index 0000000..3041181
--- /dev/null
+++ b/media/libmediaextractor/include/media/stagefright/MediaBufferGroup.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2009 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 MEDIA_BUFFER_GROUP_H_
+
+#define MEDIA_BUFFER_GROUP_H_
+
+#include <media/stagefright/MediaBuffer.h>
+#include <utils/Errors.h>
+#include <utils/threads.h>
+
+namespace android {
+
+class MediaBuffer;
+
+class MediaBufferGroup : public MediaBufferObserver {
+public:
+ MediaBufferGroup(size_t growthLimit = 0);
+
+ // create a media buffer group with preallocated buffers
+ MediaBufferGroup(size_t buffers, size_t buffer_size, size_t growthLimit = 0);
+
+ ~MediaBufferGroup();
+
+ void add_buffer(MediaBuffer *buffer);
+
+ bool has_buffers();
+
+ // If nonBlocking is false, it blocks until a buffer is available and
+ // passes it to the caller in *buffer, while returning OK.
+ // The returned buffer will have a reference count of 1.
+ // If nonBlocking is true and a buffer is not immediately available,
+ // buffer is set to NULL and it returns WOULD_BLOCK.
+ // If requestedSize is 0, any free MediaBuffer will be returned.
+ // If requestedSize is > 0, the returned MediaBuffer should have buffer
+ // size of at least requstedSize.
+ status_t acquire_buffer(
+ MediaBuffer **buffer, bool nonBlocking = false, size_t requestedSize = 0);
+
+ size_t buffers() const { return mBuffers.size(); }
+
+ // If buffer is nullptr, have acquire_buffer() check for remote release.
+ virtual void signalBufferReturned(MediaBuffer *buffer);
+
+private:
+ friend class MediaBuffer;
+
+ Mutex mLock;
+ Condition mCondition;
+ size_t mGrowthLimit; // Do not automatically grow group larger than this.
+ std::list<MediaBuffer *> mBuffers;
+
+ MediaBufferGroup(const MediaBufferGroup &);
+ MediaBufferGroup &operator=(const MediaBufferGroup &);
+};
+
+} // namespace android
+
+#endif // MEDIA_BUFFER_GROUP_H_
diff --git a/media/libmediametrics/Android.bp b/media/libmediametrics/Android.bp
index 15dac59..07e124b 100644
--- a/media/libmediametrics/Android.bp
+++ b/media/libmediametrics/Android.bp
@@ -1,4 +1,6 @@
-cc_library_shared {
+// TODO: change it back to cc_library_shared when there is a way to
+// expose media metrics as stable API.
+cc_library {
name: "libmediametrics",
srcs: [
diff --git a/media/libmediametrics/MediaAnalyticsItem.cpp b/media/libmediametrics/MediaAnalyticsItem.cpp
index 423dfb8..2e7efad 100644
--- a/media/libmediametrics/MediaAnalyticsItem.cpp
+++ b/media/libmediametrics/MediaAnalyticsItem.cpp
@@ -722,7 +722,7 @@
std::string MediaAnalyticsItem::toString() {
- return toString(-1);
+ return toString(PROTO_LAST);
}
std::string MediaAnalyticsItem::toString(int version) {
diff --git a/media/libmediaplayerservice/nuplayer/GenericSource.cpp b/media/libmediaplayerservice/nuplayer/GenericSource.cpp
index 33c3094..511f46f 100644
--- a/media/libmediaplayerservice/nuplayer/GenericSource.cpp
+++ b/media/libmediaplayerservice/nuplayer/GenericSource.cpp
@@ -24,6 +24,7 @@
#include <binder/IServiceManager.h>
#include <cutils/properties.h>
#include <media/DataSource.h>
+#include <media/MediaBufferHolder.h>
#include <media/MediaExtractor.h>
#include <media/MediaSource.h>
#include <media/IMediaHTTPService.h>
@@ -1160,14 +1161,12 @@
// data is already provided in the buffer
ab = new ABuffer(NULL, mb->range_length());
- mb->add_ref();
- ab->setMediaBufferBase(mb);
+ ab->meta()->setObject("mediaBufferHolder", new MediaBufferHolder(mb));
// Modular DRM: Required b/c of the above add_ref.
// If ref>0, there must be an observer, or it'll crash at release().
// TODO: MediaBuffer might need to be revised to ease such need.
mb->setObserver(this);
- // setMediaBufferBase() interestingly doesn't increment the ref count on its own.
// Extra increment (since we want to keep mb alive and attached to ab beyond this function
// call. This is to counter the effect of mb->release() towards the end.
mb->add_ref();
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp
index 1b02adb..1aca96c 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp
@@ -29,6 +29,7 @@
#include <cutils/properties.h>
#include <media/ICrypto.h>
+#include <media/MediaBufferHolder.h>
#include <media/MediaCodecBuffer.h>
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/ADebug.h>
@@ -1061,16 +1062,17 @@
memcpy(codecBuffer->data(), buffer->data(), buffer->size());
} else { // No buffer->data()
//Modular DRM
- mediaBuf = (MediaBuffer*)buffer->getMediaBufferBase();
+ sp<RefBase> holder;
+ if (buffer->meta()->findObject("mediaBufferHolder", &holder)) {
+ mediaBuf = (holder != nullptr) ?
+ static_cast<MediaBufferHolder*>(holder.get())->mediaBuffer() : nullptr;
+ }
if (mediaBuf != NULL) {
codecBuffer->setRange(0, mediaBuf->size());
memcpy(codecBuffer->data(), mediaBuf->data(), mediaBuf->size());
sp<MetaData> meta_data = mediaBuf->meta_data();
cryptInfo = NuPlayerDrm::getSampleCryptoInfo(meta_data);
-
- // since getMediaBuffer() has incremented the refCount
- mediaBuf->release();
} else { // No mediaBuf
ALOGE("onInputBufferFetched: buffer->data()/mediaBuf are NULL for %p",
buffer.get());
diff --git a/media/libstagefright/ACodec.cpp b/media/libstagefright/ACodec.cpp
index 14ea2a8..541093a 100644
--- a/media/libstagefright/ACodec.cpp
+++ b/media/libstagefright/ACodec.cpp
@@ -43,6 +43,7 @@
#include <media/stagefright/PersistentSurface.h>
#include <media/stagefright/SurfaceUtils.h>
#include <media/hardware/HardwareAPI.h>
+#include <media/MediaBufferHolder.h>
#include <media/OMXBuffer.h>
#include <media/omx/1.0/WOmxNode.h>
@@ -125,6 +126,32 @@
}
}
+static OMX_VIDEO_CONTROLRATETYPE getVideoBitrateMode(const sp<AMessage> &msg) {
+ int32_t tmp;
+ if (msg->findInt32("bitrate-mode", &tmp)) {
+ // explicitly translate from MediaCodecInfo.EncoderCapabilities.
+ // BITRATE_MODE_* into OMX bitrate mode.
+ switch (tmp) {
+ //BITRATE_MODE_CQ
+ case 0: return OMX_Video_ControlRateConstantQuality;
+ //BITRATE_MODE_VBR
+ case 1: return OMX_Video_ControlRateVariable;
+ //BITRATE_MODE_CBR
+ case 2: return OMX_Video_ControlRateConstant;
+ default: break;
+ }
+ }
+ return OMX_Video_ControlRateVariable;
+}
+
+static bool findVideoBitrateControlInfo(const sp<AMessage> &msg,
+ OMX_VIDEO_CONTROLRATETYPE *mode, int32_t *bitrate, int32_t *quality) {
+ *mode = getVideoBitrateMode(msg);
+ bool isCQ = (*mode == OMX_Video_ControlRateConstantQuality);
+ return (!isCQ && msg->findInt32("bitrate", bitrate))
+ || (isCQ && msg->findInt32("quality", quality));
+}
+
struct MessageList : public RefBase {
MessageList() {
}
@@ -1685,6 +1712,7 @@
mConfigFormat = msg;
mIsEncoder = encoder;
+ mIsVideo = !strncasecmp(mime, "video/", 6);
mPortMode[kPortIndexInput] = IOMX::kPortModePresetByteBuffer;
mPortMode[kPortIndexOutput] = IOMX::kPortModePresetByteBuffer;
@@ -1695,19 +1723,26 @@
return err;
}
- int32_t bitRate = 0;
- // FLAC encoder doesn't need a bitrate, other encoders do
- if (encoder && strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_FLAC)
- && !msg->findInt32("bitrate", &bitRate)) {
- return INVALID_OPERATION;
+ OMX_VIDEO_CONTROLRATETYPE bitrateMode;
+ int32_t bitrate = 0, quality;
+ // FLAC encoder or video encoder in constant quality mode doesn't need a
+ // bitrate, other encoders do.
+ if (encoder) {
+ if (mIsVideo && !findVideoBitrateControlInfo(
+ msg, &bitrateMode, &bitrate, &quality)) {
+ return INVALID_OPERATION;
+ } else if (!mIsVideo && strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_FLAC)
+ && !msg->findInt32("bitrate", &bitrate)) {
+ return INVALID_OPERATION;
+ }
}
// propagate bitrate to the output so that the muxer has it
- if (encoder && msg->findInt32("bitrate", &bitRate)) {
+ if (encoder && msg->findInt32("bitrate", &bitrate)) {
// Technically ISO spec says that 'bitrate' should be 0 for VBR even though it is the
// average bitrate. We've been setting both bitrate and max-bitrate to this same value.
- outputFormat->setInt32("bitrate", bitRate);
- outputFormat->setInt32("max-bitrate", bitRate);
+ outputFormat->setInt32("bitrate", bitrate);
+ outputFormat->setInt32("max-bitrate", bitrate);
}
int32_t storeMeta;
@@ -1764,9 +1799,7 @@
// Only enable metadata mode on encoder output if encoder can prepend
// sps/pps to idr frames, since in metadata mode the bitstream is in an
// opaque handle, to which we don't have access.
- int32_t video = !strncasecmp(mime, "video/", 6);
- mIsVideo = video;
- if (encoder && video) {
+ if (encoder && mIsVideo) {
OMX_BOOL enable = (OMX_BOOL) (prependSPSPPS
&& msg->findInt32("android._store-metadata-in-buffers-output", &storeMeta)
&& storeMeta != 0);
@@ -1812,9 +1845,9 @@
// NOTE: we only use native window for video decoders
sp<RefBase> obj;
bool haveNativeWindow = msg->findObject("native-window", &obj)
- && obj != NULL && video && !encoder;
+ && obj != NULL && mIsVideo && !encoder;
mUsingNativeWindow = haveNativeWindow;
- if (video && !encoder) {
+ if (mIsVideo && !encoder) {
inputFormat->setInt32("adaptive-playback", false);
int32_t usageProtected;
@@ -1977,7 +2010,7 @@
(void)msg->findInt32("pcm-encoding", (int32_t*)&pcmEncoding);
// invalid encodings will default to PCM-16bit in setupRawAudioFormat.
- if (video) {
+ if (mIsVideo) {
// determine need for software renderer
bool usingSwRenderer = false;
if (haveNativeWindow && mComponentName.startsWith("OMX.google.")) {
@@ -2111,14 +2144,14 @@
}
err = setupAACCodec(
- encoder, numChannels, sampleRate, bitRate, aacProfile,
+ encoder, numChannels, sampleRate, bitrate, aacProfile,
isADTS != 0, sbrMode, maxOutputChannelCount, drc,
pcmLimiterEnable);
}
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_NB)) {
- err = setupAMRCodec(encoder, false /* isWAMR */, bitRate);
+ err = setupAMRCodec(encoder, false /* isWAMR */, bitrate);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_WB)) {
- err = setupAMRCodec(encoder, true /* isWAMR */, bitRate);
+ err = setupAMRCodec(encoder, true /* isWAMR */, bitrate);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_G711_ALAW)
|| !strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_G711_MLAW)) {
// These are PCM-like formats with a fixed sample rate but
@@ -2232,7 +2265,7 @@
rateFloat = (float)rateInt; // 16MHz (FLINTMAX) is OK for upper bound.
}
if (rateFloat > 0) {
- err = setOperatingRate(rateFloat, video);
+ err = setOperatingRate(rateFloat, mIsVideo);
err = OK; // ignore errors
}
@@ -2257,7 +2290,7 @@
}
// create data converters if needed
- if (!video && err == OK) {
+ if (!mIsVideo && err == OK) {
AudioEncoding codecPcmEncoding = kAudioEncodingPcm16bit;
if (encoder) {
(void)mInputFormat->findInt32("pcm-encoding", (int32_t*)&codecPcmEncoding);
@@ -3708,10 +3741,12 @@
return err;
}
- int32_t width, height, bitrate;
+ OMX_VIDEO_CONTROLRATETYPE bitrateMode;
+ int32_t width, height, bitrate = 0, quality;
if (!msg->findInt32("width", &width)
|| !msg->findInt32("height", &height)
- || !msg->findInt32("bitrate", &bitrate)) {
+ || !findVideoBitrateControlInfo(
+ msg, &bitrateMode, &bitrate, &quality)) {
return INVALID_OPERATION;
}
@@ -3964,15 +3999,6 @@
return ret > 0 ? ret - 1 : 0;
}
-static OMX_VIDEO_CONTROLRATETYPE getBitrateMode(const sp<AMessage> &msg) {
- int32_t tmp;
- if (!msg->findInt32("bitrate-mode", &tmp)) {
- return OMX_Video_ControlRateVariable;
- }
-
- return static_cast<OMX_VIDEO_CONTROLRATETYPE>(tmp);
-}
-
status_t ACodec::setupMPEG4EncoderParameters(const sp<AMessage> &msg) {
int32_t bitrate;
float iFrameInterval;
@@ -3981,7 +4007,7 @@
return INVALID_OPERATION;
}
- OMX_VIDEO_CONTROLRATETYPE bitrateMode = getBitrateMode(msg);
+ OMX_VIDEO_CONTROLRATETYPE bitrateMode = getVideoBitrateMode(msg);
float frameRate;
if (!msg->findFloat("frame-rate", &frameRate)) {
@@ -4046,7 +4072,7 @@
return err;
}
- err = configureBitrate(bitrate, bitrateMode);
+ err = configureBitrate(bitrateMode, bitrate);
if (err != OK) {
return err;
@@ -4063,7 +4089,7 @@
return INVALID_OPERATION;
}
- OMX_VIDEO_CONTROLRATETYPE bitrateMode = getBitrateMode(msg);
+ OMX_VIDEO_CONTROLRATETYPE bitrateMode = getVideoBitrateMode(msg);
float frameRate;
if (!msg->findFloat("frame-rate", &frameRate)) {
@@ -4123,7 +4149,7 @@
return err;
}
- err = configureBitrate(bitrate, bitrateMode);
+ err = configureBitrate(bitrateMode, bitrate);
if (err != OK) {
return err;
@@ -4193,7 +4219,7 @@
return INVALID_OPERATION;
}
- OMX_VIDEO_CONTROLRATETYPE bitrateMode = getBitrateMode(msg);
+ OMX_VIDEO_CONTROLRATETYPE bitrateMode = getVideoBitrateMode(msg);
float frameRate;
if (!msg->findFloat("frame-rate", &frameRate)) {
@@ -4349,18 +4375,20 @@
}
}
- return configureBitrate(bitrate, bitrateMode);
+ return configureBitrate(bitrateMode, bitrate);
}
status_t ACodec::setupHEVCEncoderParameters(const sp<AMessage> &msg) {
- int32_t bitrate;
float iFrameInterval;
- if (!msg->findInt32("bitrate", &bitrate)
- || !msg->findAsFloat("i-frame-interval", &iFrameInterval)) {
+ if (!msg->findAsFloat("i-frame-interval", &iFrameInterval)) {
return INVALID_OPERATION;
}
- OMX_VIDEO_CONTROLRATETYPE bitrateMode = getBitrateMode(msg);
+ OMX_VIDEO_CONTROLRATETYPE bitrateMode;
+ int32_t bitrate, quality;
+ if (!findVideoBitrateControlInfo(msg, &bitrateMode, &bitrate, &quality)) {
+ return INVALID_OPERATION;
+ }
float frameRate;
if (!msg->findFloat("frame-rate", &frameRate)) {
@@ -4406,7 +4434,7 @@
return err;
}
- return configureBitrate(bitrate, bitrateMode);
+ return configureBitrate(bitrateMode, bitrate, quality);
}
status_t ACodec::setupVPXEncoderParameters(const sp<AMessage> &msg, sp<AMessage> &outputFormat) {
@@ -4427,7 +4455,7 @@
}
msg->findAsFloat("i-frame-interval", &iFrameInterval);
- OMX_VIDEO_CONTROLRATETYPE bitrateMode = getBitrateMode(msg);
+ OMX_VIDEO_CONTROLRATETYPE bitrateMode = getVideoBitrateMode(msg);
float frameRate;
if (!msg->findFloat("frame-rate", &frameRate)) {
@@ -4504,7 +4532,7 @@
}
}
- return configureBitrate(bitrate, bitrateMode);
+ return configureBitrate(bitrateMode, bitrate);
}
status_t ACodec::verifySupportForProfileAndLevel(
@@ -4540,7 +4568,7 @@
}
status_t ACodec::configureBitrate(
- int32_t bitrate, OMX_VIDEO_CONTROLRATETYPE bitrateMode) {
+ OMX_VIDEO_CONTROLRATETYPE bitrateMode, int32_t bitrate, int32_t quality) {
OMX_VIDEO_PARAM_BITRATETYPE bitrateType;
InitOMXParams(&bitrateType);
bitrateType.nPortIndex = kPortIndexOutput;
@@ -4553,7 +4581,13 @@
}
bitrateType.eControlRate = bitrateMode;
- bitrateType.nTargetBitrate = bitrate;
+
+ // write it out explicitly even if it's a union
+ if (bitrateMode == OMX_Video_ControlRateConstantQuality) {
+ bitrateType.nQualityFactor = quality;
+ } else {
+ bitrateType.nTargetBitrate = bitrate;
+ }
return mOMXNode->setParameter(
OMX_IndexParamVideoBitrate, &bitrateType, sizeof(bitrateType));
@@ -5606,7 +5640,7 @@
// by this "MediaBuffer" object. Now that the OMX component has
// told us that it's done with the input buffer, we can decrement
// the mediaBuffer's reference count.
- info->mData->setMediaBufferBase(NULL);
+ info->mData->meta()->setObject("mediaBufferHolder", sp<MediaBufferHolder>(nullptr));
PortMode mode = getPortMode(kPortIndexInput);
diff --git a/media/libstagefright/Android.bp b/media/libstagefright/Android.bp
index c3d9c24..90f6ed7 100644
--- a/media/libstagefright/Android.bp
+++ b/media/libstagefright/Android.bp
@@ -105,6 +105,7 @@
"libmedia_helper",
"libstagefright_codec2",
"libstagefright_foundation",
+ "libstagefright_gbs",
"libstagefright_omx",
"libstagefright_omx_utils",
"libstagefright_xmlparser",
@@ -128,7 +129,6 @@
"libstagefright_timedtext",
"libvpx",
"libwebm",
- "libstagefright_mpeg2support",
"libstagefright_esds",
"libstagefright_id3",
"libFLAC",
@@ -173,7 +173,7 @@
},
}
-cc_library_shared {
+cc_library {
name: "libstagefright_player2",
srcs: [
@@ -190,7 +190,6 @@
"NuCachedSource2.cpp",
"RemoteMediaExtractor.cpp",
"RemoteMediaSource.cpp",
- "SurfaceUtils.cpp",
"Utils.cpp",
"VideoFrameScheduler.cpp",
"http/MediaHTTP.cpp",
@@ -202,7 +201,6 @@
"libdrmframework",
"libgui",
"liblog",
- "libmedia_omx",
"libmedia_player2_util",
"libaudioclient",
"libmediaextractor",
@@ -213,15 +211,15 @@
"libutils",
"libmedia_helper",
"libstagefright_foundation",
- "libdl",
"libziparchive",
],
static_libs: [
"libstagefright_esds",
- "libstagefright_id3",
- "libstagefright_mpeg2support",
- "libstagefright_timedtext",
+ ],
+
+ header_libs:[
+ "media_plugin_headers",
],
export_shared_lib_headers: [
diff --git a/media/libstagefright/CCodec.cpp b/media/libstagefright/CCodec.cpp
index 0103abd..b058f37 100644
--- a/media/libstagefright/CCodec.cpp
+++ b/media/libstagefright/CCodec.cpp
@@ -23,7 +23,9 @@
#include <C2PlatformSupport.h>
#include <gui/Surface.h>
+#include <media/stagefright/BufferProducerWrapper.h>
#include <media/stagefright/CCodec.h>
+#include <media/stagefright/PersistentSurface.h>
#include "include/CCodecBufferChannel.h"
@@ -111,33 +113,35 @@
class CCodecListener : public C2Component::Listener {
public:
- CCodecListener(const std::shared_ptr<CCodecBufferChannel> &channel)
- : mChannel(channel) {
- }
+ explicit CCodecListener(const wp<CCodec> &codec) : mCodec(codec) {}
virtual void onWorkDone_nb(
std::weak_ptr<C2Component> component,
std::vector<std::unique_ptr<C2Work>> workItems) override {
- (void) component;
- mChannel->onWorkDone(std::move(workItems));
+ (void)component;
+ sp<CCodec> codec(mCodec.promote());
+ if (!codec) {
+ return;
+ }
+ codec->onWorkDone(workItems);
}
virtual void onTripped_nb(
std::weak_ptr<C2Component> component,
std::vector<std::shared_ptr<C2SettingResult>> settingResult) override {
// TODO
- (void) component;
- (void) settingResult;
+ (void)component;
+ (void)settingResult;
}
virtual void onError_nb(std::weak_ptr<C2Component> component, uint32_t errorCode) override {
// TODO
- (void) component;
- (void) errorCode;
+ (void)component;
+ (void)errorCode;
}
private:
- std::shared_ptr<CCodecBufferChannel> mChannel;
+ wp<CCodec> mCodec;
};
} // namespace
@@ -159,11 +163,11 @@
void CCodec::initiateAllocateComponent(const sp<AMessage> &msg) {
{
Mutexed<State>::Locked state(mState);
- if (state->mState != RELEASED) {
+ if (state->get() != RELEASED) {
mCallback->onError(INVALID_OPERATION, ACTION_CODE_FATAL);
return;
}
- state->mState = ALLOCATING;
+ state->set(ALLOCATING);
}
AString componentName;
@@ -178,14 +182,14 @@
void CCodec::allocate(const AString &componentName) {
// TODO: use C2ComponentStore to create component
- mListener.reset(new CCodecListener(mChannel));
+ mListener.reset(new CCodecListener(this));
std::shared_ptr<C2Component> comp;
c2_status_t err = GetCodec2PlatformComponentStore()->createComponent(
componentName.c_str(), &comp);
if (err != C2_OK) {
Mutexed<State>::Locked state(mState);
- state->mState = RELEASED;
+ state->set(RELEASED);
state.unlock();
mCallback->onError(err, ACTION_CODE_FATAL);
state.lock();
@@ -194,15 +198,15 @@
comp->setListener_vb(mListener, C2_MAY_BLOCK);
{
Mutexed<State>::Locked state(mState);
- if (state->mState != ALLOCATING) {
- state->mState = RELEASED;
+ if (state->get() != ALLOCATING) {
+ state->set(RELEASED);
state.unlock();
mCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
state.lock();
return;
}
- state->mState = ALLOCATED;
- state->mComp = comp;
+ state->set(ALLOCATED);
+ state->comp = comp;
}
mChannel->setComponent(comp);
mCallback->onComponentAllocated(comp->intf()->getName().c_str());
@@ -211,7 +215,7 @@
void CCodec::initiateConfigureComponent(const sp<AMessage> &format) {
{
Mutexed<State>::Locked state(mState);
- if (state->mState != ALLOCATED) {
+ if (state->get() != ALLOCATED) {
mCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
return;
}
@@ -252,6 +256,9 @@
inputFormat->setInt32("sample-rate", 44100);
outputFormat->setInt32("channel-count", 1);
outputFormat->setInt32("sample-rate", 44100);
+ } else {
+ outputFormat->setInt32("width", 1080);
+ outputFormat->setInt32("height", 1920);
}
} else {
inputFormat->setString("mime", mime);
@@ -272,32 +279,81 @@
{
Mutexed<Formats>::Locked formats(mFormats);
- formats->mInputFormat = inputFormat;
- formats->mOutputFormat = outputFormat;
+ formats->inputFormat = inputFormat;
+ formats->outputFormat = outputFormat;
}
mCallback->onComponentConfigured(inputFormat, outputFormat);
}
-
void CCodec::initiateCreateInputSurface() {
- // TODO
+ (new AMessage(kWhatCreateInputSurface, this))->post();
+}
+
+void CCodec::createInputSurface() {
+ sp<IGraphicBufferProducer> producer;
+ sp<GraphicBufferSource> source(new GraphicBufferSource);
+
+ status_t err = source->initCheck();
+ if (err != OK) {
+ ALOGE("Failed to initialize graphic buffer source: %d", err);
+ mCallback->onInputSurfaceCreationFailed(err);
+ return;
+ }
+ producer = source->getIGraphicBufferProducer();
+
+ err = setupInputSurface(source);
+ if (err != OK) {
+ ALOGE("Failed to set up input surface: %d", err);
+ mCallback->onInputSurfaceCreationFailed(err);
+ return;
+ }
+
+ sp<AMessage> inputFormat;
+ sp<AMessage> outputFormat;
+ {
+ Mutexed<Formats>::Locked formats(mFormats);
+ inputFormat = formats->inputFormat;
+ outputFormat = formats->outputFormat;
+ }
+ mCallback->onInputSurfaceCreated(
+ inputFormat,
+ outputFormat,
+ new BufferProducerWrapper(producer));
+}
+
+status_t CCodec::setupInputSurface(const sp<GraphicBufferSource> &source) {
+ status_t err = mChannel->setGraphicBufferSource(source);
+ if (err != OK) {
+ return err;
+ }
+
+ // TODO: configure |source| with other settings.
+ return OK;
}
void CCodec::initiateSetInputSurface(const sp<PersistentSurface> &surface) {
+ sp<AMessage> msg = new AMessage(kWhatSetInputSurface, this);
+ msg->setObject("surface", surface);
+ msg->post();
+}
+
+void CCodec::setInputSurface(const sp<PersistentSurface> &surface) {
// TODO
- (void) surface;
+ (void)surface;
+
+ mCallback->onInputSurfaceDeclined(ERROR_UNSUPPORTED);
}
void CCodec::initiateStart() {
{
Mutexed<State>::Locked state(mState);
- if (state->mState != ALLOCATED) {
+ if (state->get() != ALLOCATED) {
state.unlock();
mCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
state.lock();
return;
}
- state->mState = STARTING;
+ state->set(STARTING);
}
(new AMessage(kWhatStart, this))->post();
@@ -307,13 +363,13 @@
std::shared_ptr<C2Component> comp;
{
Mutexed<State>::Locked state(mState);
- if (state->mState != STARTING) {
+ if (state->get() != STARTING) {
state.unlock();
mCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
state.lock();
return;
}
- comp = state->mComp;
+ comp = state->comp;
}
c2_status_t err = comp->start();
if (err != C2_OK) {
@@ -325,20 +381,20 @@
sp<AMessage> outputFormat;
{
Mutexed<Formats>::Locked formats(mFormats);
- inputFormat = formats->mInputFormat;
- outputFormat = formats->mOutputFormat;
+ inputFormat = formats->inputFormat;
+ outputFormat = formats->outputFormat;
}
mChannel->start(inputFormat, outputFormat);
{
Mutexed<State>::Locked state(mState);
- if (state->mState != STARTING) {
+ if (state->get() != STARTING) {
state.unlock();
mCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
state.lock();
return;
}
- state->mState = RUNNING;
+ state->set(RUNNING);
}
mCallback->onStartCompleted();
}
@@ -354,17 +410,17 @@
void CCodec::initiateStop() {
{
Mutexed<State>::Locked state(mState);
- if (state->mState == ALLOCATED
- || state->mState == RELEASED
- || state->mState == STOPPING
- || state->mState == RELEASING) {
+ if (state->get() == ALLOCATED
+ || state->get() == RELEASED
+ || state->get() == STOPPING
+ || state->get() == RELEASING) {
// We're already stopped, released, or doing it right now.
state.unlock();
mCallback->onStopCompleted();
state.lock();
return;
}
- state->mState = STOPPING;
+ state->set(STOPPING);
}
(new AMessage(kWhatStop, this))->post();
@@ -374,19 +430,19 @@
std::shared_ptr<C2Component> comp;
{
Mutexed<State>::Locked state(mState);
- if (state->mState == RELEASING) {
+ if (state->get() == RELEASING) {
state.unlock();
// We're already stopped or release is in progress.
mCallback->onStopCompleted();
state.lock();
return;
- } else if (state->mState != STOPPING) {
+ } else if (state->get() != STOPPING) {
state.unlock();
mCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
state.lock();
return;
}
- comp = state->mComp;
+ comp = state->comp;
}
mChannel->stop();
status_t err = comp->stop();
@@ -397,8 +453,8 @@
{
Mutexed<State>::Locked state(mState);
- if (state->mState == STOPPING) {
- state->mState = ALLOCATED;
+ if (state->get() == STOPPING) {
+ state->set(ALLOCATED);
}
}
mCallback->onStopCompleted();
@@ -407,7 +463,7 @@
void CCodec::initiateRelease(bool sendCallback /* = true */) {
{
Mutexed<State>::Locked state(mState);
- if (state->mState == RELEASED || state->mState == RELEASING) {
+ if (state->get() == RELEASED || state->get() == RELEASING) {
// We're already released or doing it right now.
if (sendCallback) {
state.unlock();
@@ -416,8 +472,8 @@
}
return;
}
- if (state->mState == ALLOCATING) {
- state->mState = RELEASING;
+ if (state->get() == ALLOCATING) {
+ state->set(RELEASING);
// With the altered state allocate() would fail and clean up.
if (sendCallback) {
state.unlock();
@@ -426,7 +482,7 @@
}
return;
}
- state->mState = RELEASING;
+ state->set(RELEASING);
}
std::thread([this, sendCallback] { release(sendCallback); }).detach();
@@ -436,7 +492,7 @@
std::shared_ptr<C2Component> comp;
{
Mutexed<State>::Locked state(mState);
- if (state->mState == RELEASED) {
+ if (state->get() == RELEASED) {
if (sendCallback) {
state.unlock();
mCallback->onReleaseCompleted();
@@ -444,15 +500,15 @@
}
return;
}
- comp = state->mComp;
+ comp = state->comp;
}
mChannel->stop();
comp->release();
{
Mutexed<State>::Locked state(mState);
- state->mState = RELEASED;
- state->mComp.reset();
+ state->set(RELEASED);
+ state->comp.reset();
}
if (sendCallback) {
mCallback->onReleaseCompleted();
@@ -466,11 +522,11 @@
void CCodec::signalFlush() {
{
Mutexed<State>::Locked state(mState);
- if (state->mState != RUNNING) {
+ if (state->get() != RUNNING) {
mCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
return;
}
- state->mState = FLUSHING;
+ state->set(FLUSHING);
}
(new AMessage(kWhatFlush, this))->post();
@@ -480,13 +536,13 @@
std::shared_ptr<C2Component> comp;
{
Mutexed<State>::Locked state(mState);
- if (state->mState != FLUSHING) {
+ if (state->get() != FLUSHING) {
state.unlock();
mCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
state.lock();
return;
}
- comp = state->mComp;
+ comp = state->comp;
}
mChannel->stop();
@@ -502,7 +558,7 @@
{
Mutexed<State>::Locked state(mState);
- state->mState = FLUSHED;
+ state->set(FLUSHED);
}
mCallback->onFlushCompleted();
}
@@ -510,26 +566,26 @@
void CCodec::signalResume() {
{
Mutexed<State>::Locked state(mState);
- if (state->mState != FLUSHED) {
+ if (state->get() != FLUSHED) {
state.unlock();
mCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
state.lock();
return;
}
- state->mState = RESUMING;
+ state->set(RESUMING);
}
mChannel->start(nullptr, nullptr);
{
Mutexed<State>::Locked state(mState);
- if (state->mState != RESUMING) {
+ if (state->get() != RESUMING) {
state.unlock();
mCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
state.lock();
return;
}
- state->mState = RUNNING;
+ state->set(RUNNING);
}
}
@@ -545,6 +601,14 @@
// TODO
}
+void CCodec::onWorkDone(std::vector<std::unique_ptr<C2Work>> &workItems) {
+ Mutexed<std::list<std::unique_ptr<C2Work>>>::Locked queue(mWorkDoneQueue);
+ for (std::unique_ptr<C2Work> &item : workItems) {
+ queue->push_back(std::move(item));
+ }
+ (new AMessage(kWhatWorkDone, this))->post();
+}
+
void CCodec::onMessageReceived(const sp<AMessage> &msg) {
TimePoint now = std::chrono::steady_clock::now();
switch (msg->what()) {
@@ -582,6 +646,37 @@
flush();
break;
}
+ case kWhatCreateInputSurface: {
+ // Surface operations may be briefly blocking.
+ setDeadline(now + 100ms);
+ createInputSurface();
+ break;
+ }
+ case kWhatSetInputSurface: {
+ // Surface operations may be briefly blocking.
+ setDeadline(now + 100ms);
+ sp<RefBase> obj;
+ CHECK(msg->findObject("surface", &obj));
+ sp<PersistentSurface> surface(static_cast<PersistentSurface *>(obj.get()));
+ setInputSurface(surface);
+ break;
+ }
+ case kWhatWorkDone: {
+ std::unique_ptr<C2Work> work;
+ {
+ Mutexed<std::list<std::unique_ptr<C2Work>>>::Locked queue(mWorkDoneQueue);
+ if (queue->empty()) {
+ break;
+ }
+ work.swap(queue->front());
+ queue->pop_front();
+ if (!queue->empty()) {
+ (new AMessage(kWhatWorkDone, this))->post();
+ }
+ }
+ mChannel->onWorkDone(work);
+ break;
+ }
default: {
ALOGE("unrecognized message");
break;
diff --git a/media/libstagefright/CCodecBufferChannel.cpp b/media/libstagefright/CCodecBufferChannel.cpp
index eea9c78..2f4a7ea 100644
--- a/media/libstagefright/CCodecBufferChannel.cpp
+++ b/media/libstagefright/CCodecBufferChannel.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#define LOG_NDEBUG 0
+//#define LOG_NDEBUG 0
#define LOG_TAG "CCodecBufferChannel"
#include <utils/Log.h>
@@ -48,10 +48,189 @@
using namespace hardware::cas::V1_0;
using namespace hardware::cas::native::V1_0;
+/**
+ * Base class for representation of buffers at one port.
+ */
+class CCodecBufferChannel::Buffers {
+public:
+ Buffers() = default;
+ virtual ~Buffers() = default;
+
+ /**
+ * Set format for MediaCodec-facing buffers.
+ */
+ void setFormat(const sp<AMessage> &format) { mFormat = format; }
+
+ /**
+ * Returns true if the buffers are operating under array mode.
+ */
+ virtual bool isArrayMode() const { return false; }
+
+ /**
+ * Fills the vector with MediaCodecBuffer's if in array mode; otherwise,
+ * no-op.
+ */
+ virtual void getArray(Vector<sp<MediaCodecBuffer>> *) const {}
+
+protected:
+ // Format to be used for creating MediaCodec-facing buffers.
+ sp<AMessage> mFormat;
+
+private:
+ DISALLOW_EVIL_CONSTRUCTORS(Buffers);
+};
+
+class CCodecBufferChannel::InputBuffers : public CCodecBufferChannel::Buffers {
+public:
+ InputBuffers() = default;
+ virtual ~InputBuffers() = default;
+
+ /**
+ * Set a block pool to obtain input memory blocks.
+ */
+ void setPool(const std::shared_ptr<C2BlockPool> &pool) { mPool = pool; }
+
+ /**
+ * Get a new MediaCodecBuffer for input and its corresponding index.
+ * Returns false if no new buffer can be obtained at the moment.
+ */
+ virtual bool requestNewBuffer(size_t *index, sp<MediaCodecBuffer> *buffer) = 0;
+
+ /**
+ * Release the buffer obtained from requestNewBuffer() and get the
+ * associated C2Buffer object back. Returns empty shared_ptr if the
+ * buffer is not on file.
+ *
+ * XXX: this is a quick hack to be removed
+ */
+ virtual std::shared_ptr<C2Buffer> releaseBufferIndex(size_t /* index */) { return nullptr; }
+
+ /**
+ * Release the buffer obtained from requestNewBuffer() and get the
+ * associated C2Buffer object back. Returns empty shared_ptr if the
+ * buffer is not on file.
+ */
+ virtual std::shared_ptr<C2Buffer> releaseBuffer(const sp<MediaCodecBuffer> &buffer) = 0;
+
+ /**
+ * Flush internal state. After this call, no index or buffer previously
+ * returned from requestNewBuffer() is valid.
+ */
+ virtual void flush() = 0;
+
+ /**
+ * Return array-backed version of input buffers. The returned object
+ * shall retain the internal state so that it will honor index and
+ * buffer from previous calls of requestNewBuffer().
+ */
+ virtual std::unique_ptr<InputBuffers> toArrayMode() = 0;
+
+protected:
+ // Pool to obtain blocks for input buffers.
+ std::shared_ptr<C2BlockPool> mPool;
+
+private:
+ DISALLOW_EVIL_CONSTRUCTORS(InputBuffers);
+};
+
+class CCodecBufferChannel::InputBufferClient {
+public:
+ explicit InputBufferClient(
+ const std::shared_ptr<CCodecBufferChannel> &channel) : mChannel(channel) {}
+ virtual ~InputBufferClient() = default;
+
+ virtual void onInputBufferAdded(size_t index, const sp<MediaCodecBuffer> &buffer) {
+ std::shared_ptr<CCodecBufferChannel> channel = mChannel.lock();
+ if (!channel) {
+ return;
+ }
+ channel->mCallback->onInputBufferAvailable(index, buffer);
+ }
+
+ virtual void onStart() {
+ // no-op
+ }
+
+ virtual void onStop() {
+ // no-op
+ }
+
+ virtual void onRelease() {
+ // no-op
+ }
+
+ virtual void onInputBufferAvailable(size_t index, const sp<MediaCodecBuffer> &buffer) {
+ std::shared_ptr<CCodecBufferChannel> channel = mChannel.lock();
+ if (!channel) {
+ return;
+ }
+ channel->mCallback->onInputBufferAvailable(index, buffer);
+ }
+
+protected:
+ InputBufferClient() = default;
+ std::weak_ptr<CCodecBufferChannel> mChannel;
+
+ DISALLOW_EVIL_CONSTRUCTORS(InputBufferClient);
+};
+
+class CCodecBufferChannel::OutputBuffers : public CCodecBufferChannel::Buffers {
+public:
+ OutputBuffers() = default;
+ virtual ~OutputBuffers() = default;
+
+ /**
+ * Register output C2Buffer from the component and obtain corresponding
+ * index and MediaCodecBuffer object. Returns false if registration
+ * fails.
+ */
+ virtual bool registerBuffer(
+ const std::shared_ptr<C2Buffer> &buffer,
+ size_t *index,
+ sp<MediaCodecBuffer> *codecBuffer) = 0;
+
+ /**
+ * Register codec specific data as a buffer to be consistent with
+ * MediaCodec behavior.
+ */
+ virtual bool registerCsd(
+ const C2StreamCsdInfo::output * /* csd */,
+ size_t * /* index */,
+ sp<MediaCodecBuffer> * /* codecBuffer */) {
+ return false;
+ }
+
+ /**
+ * Release the buffer obtained from registerBuffer() and get the
+ * associated C2Buffer object back. Returns empty shared_ptr if the
+ * buffer is not on file.
+ */
+ virtual std::shared_ptr<C2Buffer> releaseBuffer(const sp<MediaCodecBuffer> &buffer) = 0;
+
+ /**
+ * Flush internal state. After this call, no index or buffer previously
+ * returned from registerBuffer() is valid.
+ */
+ virtual void flush(const std::list<std::unique_ptr<C2Work>> &flushedWork) = 0;
+
+ /**
+ * Return array-backed version of output buffers. The returned object
+ * shall retain the internal state so that it will honor index and
+ * buffer from previous calls of registerBuffer().
+ */
+ virtual std::unique_ptr<OutputBuffers> toArrayMode() = 0;
+
+private:
+ DISALLOW_EVIL_CONSTRUCTORS(OutputBuffers);
+};
+
namespace {
+constexpr int32_t kMaskI32 = ~0;
+
// TODO: get this info from component
const static size_t kMinBufferArraySize = 16;
+const static size_t kLinearBufferSize = 524288;
template <class T>
ssize_t findBufferSlot(
@@ -88,9 +267,14 @@
return Codec2Buffer::allocate(format, block);
}
-class LinearBuffer : public C2Buffer {
+class Buffer1D : public C2Buffer {
public:
- explicit LinearBuffer(C2ConstLinearBlock block) : C2Buffer({ block }) {}
+ explicit Buffer1D(C2ConstLinearBlock block) : C2Buffer({ block }) {}
+};
+
+class Buffer2D : public C2Buffer {
+public:
+ explicit Buffer2D(C2ConstGraphicBlock block) : C2Buffer({ block }) {}
};
class InputBuffersArray : public CCodecBufferChannel::InputBuffers {
@@ -99,36 +283,36 @@
void add(
size_t index,
- const sp<MediaCodecBuffer> &clientBuffer,
- const std::shared_ptr<C2Buffer> &compBuffer,
+ const sp<Codec2Buffer> &clientBuffer,
bool available) {
- if (mBufferArray.size() < index) {
+ if (mBufferArray.size() <= index) {
+ // TODO: make this more efficient
mBufferArray.resize(index + 1);
}
mBufferArray[index].clientBuffer = clientBuffer;
- mBufferArray[index].compBuffer = compBuffer;
mBufferArray[index].available = available;
}
- bool isArrayMode() final { return true; }
+ bool isArrayMode() const final { return true; }
std::unique_ptr<CCodecBufferChannel::InputBuffers> toArrayMode() final {
return nullptr;
}
- void getArray(Vector<sp<MediaCodecBuffer>> *array) final {
+ void getArray(Vector<sp<MediaCodecBuffer>> *array) const final {
array->clear();
- for (const auto &entry : mBufferArray) {
+ for (const Entry &entry : mBufferArray) {
array->push(entry.clientBuffer);
}
}
bool requestNewBuffer(size_t *index, sp<MediaCodecBuffer> *buffer) override {
for (size_t i = 0; i < mBufferArray.size(); ++i) {
- if (mBufferArray[i].available) {
+ if (mBufferArray[i].available && mBufferArray[i].compBuffer.expired()) {
mBufferArray[i].available = false;
*index = i;
*buffer = mBufferArray[i].clientBuffer;
+ (*buffer)->setRange(0, (*buffer)->capacity());
return true;
}
}
@@ -138,8 +322,10 @@
std::shared_ptr<C2Buffer> releaseBuffer(const sp<MediaCodecBuffer> &buffer) override {
for (size_t i = 0; i < mBufferArray.size(); ++i) {
if (!mBufferArray[i].available && mBufferArray[i].clientBuffer == buffer) {
+ std::shared_ptr<C2Buffer> buffer = std::make_shared<Buffer1D>(mBufferArray[i].clientBuffer->share());
mBufferArray[i].available = true;
- return std::move(mBufferArray[i].compBuffer);
+ mBufferArray[i].compBuffer = buffer;
+ return buffer;
}
}
return nullptr;
@@ -148,14 +334,13 @@
void flush() override {
for (size_t i = 0; i < mBufferArray.size(); ++i) {
mBufferArray[i].available = true;
- mBufferArray[i].compBuffer.reset();
}
}
private:
struct Entry {
- sp<MediaCodecBuffer> clientBuffer;
- std::shared_ptr<C2Buffer> compBuffer;
+ sp<Codec2Buffer> clientBuffer;
+ std::weak_ptr<C2Buffer> compBuffer;
bool available;
};
@@ -177,7 +362,7 @@
// TODO: proper max input size and usage
// TODO: read usage from intf
C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
- sp<Codec2Buffer> newBuffer = allocateLinearBuffer(mPool, mFormat, 65536, usage);
+ sp<Codec2Buffer> newBuffer = allocateLinearBuffer(mPool, mFormat, kLinearBufferSize, usage);
if (newBuffer == nullptr) {
return false;
}
@@ -195,7 +380,7 @@
sp<Codec2Buffer> codecBuffer = it->promote();
// We got sp<> reference from the caller so this should never happen..
CHECK(codecBuffer != nullptr);
- return std::make_shared<LinearBuffer>(codecBuffer->share());
+ return std::make_shared<Buffer1D>(codecBuffer->share());
}
void flush() override {
@@ -203,8 +388,10 @@
std::unique_ptr<CCodecBufferChannel::InputBuffers> toArrayMode() final {
std::unique_ptr<InputBuffersArray> array(new InputBuffersArray);
+ array->setFormat(mFormat);
// TODO
const size_t size = std::max(kMinBufferArraySize, mBuffers.size());
+ mBuffers.resize(size);
for (size_t i = 0; i < size; ++i) {
sp<Codec2Buffer> clientBuffer = mBuffers[i].promote();
bool available = false;
@@ -212,13 +399,12 @@
// TODO: proper max input size
// TODO: read usage from intf
C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
- clientBuffer = allocateLinearBuffer(mPool, mFormat, 65536, usage);
+ clientBuffer = allocateLinearBuffer(mPool, mFormat, kLinearBufferSize, usage);
available = true;
}
array->add(
i,
clientBuffer,
- std::make_shared<LinearBuffer>(clientBuffer->share()),
available);
}
return std::move(array);
@@ -230,19 +416,30 @@
std::vector<wp<Codec2Buffer>> mBuffers;
};
-// TODO: stub
class GraphicInputBuffers : public CCodecBufferChannel::InputBuffers {
public:
- using CCodecBufferChannel::InputBuffers::InputBuffers;
+ GraphicInputBuffers() = default;
bool requestNewBuffer(size_t *index, sp<MediaCodecBuffer> *buffer) override {
- (void)index;
- (void)buffer;
- return false;
+ *buffer = nullptr;
+ for (size_t i = 0; i < mAvailable.size(); ++i) {
+ if (mAvailable[i]) {
+ *index = i;
+ mAvailable[i] = false;
+ return true;
+ }
+ }
+ *index = mAvailable.size();
+ mAvailable.push_back(false);
+ return true;
}
- std::shared_ptr<C2Buffer> releaseBuffer(const sp<MediaCodecBuffer> &buffer) override {
- (void)buffer;
+ std::shared_ptr<C2Buffer> releaseBufferIndex(size_t index) override {
+ mAvailable[index] = true;
+ return nullptr;
+ }
+
+ std::shared_ptr<C2Buffer> releaseBuffer(const sp<MediaCodecBuffer> &) override {
return nullptr;
}
@@ -252,6 +449,9 @@
std::unique_ptr<CCodecBufferChannel::InputBuffers> toArrayMode() final {
return nullptr;
}
+
+private:
+ std::vector<bool> mAvailable;
};
class OutputBuffersArray : public CCodecBufferChannel::OutputBuffers {
@@ -263,7 +463,8 @@
const sp<MediaCodecBuffer> &clientBuffer,
const std::shared_ptr<C2Buffer> &compBuffer,
bool available) {
- if (mBufferArray.size() < index) {
+ if (mBufferArray.size() <= index) {
+ // TODO: make this more efficient
mBufferArray.resize(index + 1);
}
mBufferArray[index].clientBuffer = clientBuffer;
@@ -271,7 +472,7 @@
mBufferArray[index].available = available;
}
- bool isArrayMode() final { return true; }
+ bool isArrayMode() const final { return true; }
std::unique_ptr<CCodecBufferChannel::OutputBuffers> toArrayMode() final {
return nullptr;
@@ -285,6 +486,7 @@
if (mBufferArray[i].available && copy(buffer, mBufferArray[i].clientBuffer)) {
*index = i;
*codecBuffer = mBufferArray[i].clientBuffer;
+ (*codecBuffer)->setFormat(mFormat);
mBufferArray[i].compBuffer = buffer;
mBufferArray[i].available = false;
return true;
@@ -299,10 +501,17 @@
sp<MediaCodecBuffer> *codecBuffer) final {
for (size_t i = 0; i < mBufferArray.size(); ++i) {
if (mBufferArray[i].available
- && mBufferArray[i].clientBuffer->capacity() <= csd->flexCount()) {
+ && mBufferArray[i].clientBuffer->capacity() >= csd->flexCount()) {
+ // TODO: proper format update
+ sp<ABuffer> csdBuffer = ABuffer::CreateAsCopy(csd->m.value, csd->flexCount());
+ mFormat = mFormat->dup();
+ mFormat->setBuffer("csd-0", csdBuffer);
+
memcpy(mBufferArray[i].clientBuffer->base(), csd->m.value, csd->flexCount());
+ mBufferArray[i].clientBuffer->setRange(0, csd->flexCount());
*index = i;
*codecBuffer = mBufferArray[i].clientBuffer;
+ (*codecBuffer)->setFormat(mFormat);
mBufferArray[i].available = false;
return true;
}
@@ -333,9 +542,9 @@
const std::shared_ptr<C2Buffer> &buffer,
const sp<MediaCodecBuffer> &clientBuffer) = 0;
- void getArray(Vector<sp<MediaCodecBuffer>> *array) final {
+ void getArray(Vector<sp<MediaCodecBuffer>> *array) const final {
array->clear();
- for (const auto &entry : mBufferArray) {
+ for (const Entry &entry : mBufferArray) {
array->push(entry.clientBuffer);
}
}
@@ -363,6 +572,7 @@
}
C2ReadView view = buffer->data().linearBlocks().front().map().get();
if (clientBuffer->capacity() < view.capacity()) {
+ ALOGV("view.capacity() = %u", view.capacity());
return false;
}
clientBuffer->setRange(0u, view.capacity());
@@ -425,9 +635,11 @@
if (ret < 0) {
return false;
}
- sp<MediaCodecBuffer> newBuffer = new MediaCodecBuffer(
- mFormat,
- ABuffer::CreateAsCopy(csd->m.value, csd->flexCount()));
+ // TODO: proper format update
+ sp<ABuffer> csdBuffer = ABuffer::CreateAsCopy(csd->m.value, csd->flexCount());
+ mFormat = mFormat->dup();
+ mFormat->setBuffer("csd-0", csdBuffer);
+ sp<MediaCodecBuffer> newBuffer = new MediaCodecBuffer(mFormat, csdBuffer);
mBuffers[ret] = { newBuffer, nullptr };
*index = ret;
*codecBuffer = newBuffer;
@@ -498,15 +710,17 @@
std::unique_ptr<CCodecBufferChannel::OutputBuffers> toArrayMode() override {
std::unique_ptr<OutputBuffersArray> array(new LinearOutputBuffersArray);
+ array->setFormat(mFormat);
const size_t size = std::max(kMinBufferArraySize, mBuffers.size());
+ mBuffers.resize(size);
for (size_t i = 0; i < size; ++i) {
sp<MediaCodecBuffer> clientBuffer = mBuffers[i].clientBuffer.promote();
std::shared_ptr<C2Buffer> compBuffer = mBuffers[i].bufferRef;
bool available = false;
if (clientBuffer == nullptr) {
// TODO: proper max input size
- clientBuffer = new MediaCodecBuffer(mFormat, new ABuffer(65536));
+ clientBuffer = new MediaCodecBuffer(mFormat, new ABuffer(kLinearBufferSize));
available = true;
compBuffer.reset();
}
@@ -526,8 +740,10 @@
std::unique_ptr<CCodecBufferChannel::OutputBuffers> toArrayMode() override {
std::unique_ptr<OutputBuffersArray> array(new GraphicOutputBuffersArray);
+ array->setFormat(mFormat);
const size_t size = std::max(kMinBufferArraySize, mBuffers.size());
+ mBuffers.resize(size);
for (size_t i = 0; i < size; ++i) {
sp<MediaCodecBuffer> clientBuffer = mBuffers[i].clientBuffer.promote();
std::shared_ptr<C2Buffer> compBuffer = mBuffers[i].bufferRef;
@@ -543,8 +759,163 @@
}
};
+class BufferQueueClient : public CCodecBufferChannel::InputBufferClient {
+public:
+ explicit BufferQueueClient(const sp<GraphicBufferSource> &source) : mSource(source) {}
+ virtual ~BufferQueueClient() = default;
+
+ void onInputBufferAdded(size_t index, const sp<MediaCodecBuffer> &buffer) override {
+ (void)buffer;
+ mSource->onInputBufferAdded(index & kMaskI32);
+ }
+
+ void onStart() override {
+ mSource->start();
+ }
+
+ void onStop() override {
+ mSource->stop();
+ }
+
+ void onRelease() override {
+ mSource->release();
+ }
+
+ void onInputBufferAvailable(size_t index, const sp<MediaCodecBuffer> &buffer) override {
+ ALOGV("onInputBufferEmptied index = %zu", index);
+ (void)buffer;
+ // TODO: can we really ignore fence here?
+ mSource->onInputBufferEmptied(index & kMaskI32, -1 /* fenceFd */);
+ }
+
+private:
+ sp<GraphicBufferSource> mSource;
+};
+
+class GraphicBlock : public C2GraphicBlock {
+ using C2GraphicBlock::C2GraphicBlock;
+ friend class ::android::CCodecBufferChannel;
+};
+
} // namespace
+class CCodecBufferChannel::C2ComponentWrapper : public ComponentWrapper {
+public:
+ explicit C2ComponentWrapper(
+ const std::shared_ptr<CCodecBufferChannel> &channel)
+ : mChannel(channel), mLastTimestamp(0) {}
+
+ virtual ~C2ComponentWrapper() {
+ for (const std::pair<int32_t, C2Handle *> &entry : mHandles) {
+ native_handle_delete(entry.second);
+ }
+ }
+
+ status_t submitBuffer(
+ int32_t bufferId, const sp<GraphicBuffer> &buffer,
+ int64_t timestamp, int fenceFd) override {
+ ALOGV("submitBuffer bufferId = %d", bufferId);
+ // TODO: Use fd to construct fence
+ (void)fenceFd;
+
+ std::shared_ptr<CCodecBufferChannel> channel = mChannel.lock();
+ if (!channel) {
+ return NO_INIT;
+ }
+
+ std::shared_ptr<C2Allocator> allocator = mAllocator.lock();
+ if (!allocator) {
+ c2_status_t err = GetCodec2PlatformAllocatorStore()->fetchAllocator(
+ C2AllocatorStore::PLATFORM_START + 1, // GRALLOC
+ &allocator);
+ if (err != OK) {
+ return UNKNOWN_ERROR;
+ }
+ mAllocator = allocator;
+ }
+
+ std::shared_ptr<C2GraphicAllocation> alloc;
+ C2Handle *handle = WrapNativeCodec2GrallocHandle(
+ buffer->handle, buffer->width, buffer->height,
+ buffer->format, buffer->usage, buffer->stride);
+ c2_status_t err = allocator->priorGraphicAllocation(handle, &alloc);
+ if (err != OK) {
+ return UNKNOWN_ERROR;
+ }
+ std::shared_ptr<C2GraphicBlock> block(new GraphicBlock(alloc));
+
+ std::unique_ptr<C2Work> work(new C2Work);
+ work->input.flags = (C2FrameData::flags_t)0;
+ work->input.ordinal.timestamp = timestamp;
+ work->input.ordinal.frameIndex = channel->mFrameIndex++;
+ work->input.buffers.clear();
+ work->input.buffers.emplace_back(new Buffer2D(
+ // TODO: fence
+ block->share(C2Rect(block->width(), block->height()), ::android::C2Fence())));
+ work->worklets.clear();
+ work->worklets.emplace_back(new C2Worklet);
+ std::list<std::unique_ptr<C2Work>> items;
+ items.push_back(std::move(work));
+
+ err = channel->mComponent->queue_nb(&items);
+ if (err != OK) {
+ native_handle_delete(handle);
+ return UNKNOWN_ERROR;
+ }
+
+ mLastTimestamp = timestamp;
+ if (mHandles.count(bufferId) > 0) {
+ native_handle_delete(mHandles[bufferId]);
+ }
+ mHandles[bufferId] = handle;
+
+ Mutexed<std::unique_ptr<InputBuffers>>::Locked buffers(channel->mInputBuffers);
+ ALOGV("releaseBufferIndex index = %d", bufferId);
+ (*buffers)->releaseBufferIndex(bufferId);
+
+ return OK;
+ }
+
+ status_t submitEos(int32_t bufferId) override {
+ std::shared_ptr<CCodecBufferChannel> channel = mChannel.lock();
+ if (!channel) {
+ return NO_INIT;
+ }
+
+ std::unique_ptr<C2Work> work(new C2Work);
+ work->input.flags = C2FrameData::FLAG_END_OF_STREAM;
+ work->input.ordinal.timestamp = mLastTimestamp;
+ work->input.ordinal.frameIndex = channel->mFrameIndex++;
+ work->input.buffers.clear();
+ work->input.buffers.push_back(nullptr);
+ work->worklets.clear();
+ work->worklets.emplace_back(new C2Worklet);
+ std::list<std::unique_ptr<C2Work>> items;
+ items.push_back(std::move(work));
+
+ c2_status_t err = channel->mComponent->queue_nb(&items);
+
+ Mutexed<std::unique_ptr<InputBuffers>>::Locked buffers(channel->mInputBuffers);
+ (*buffers)->releaseBufferIndex(bufferId);
+
+ return (err == C2_OK) ? OK : UNKNOWN_ERROR;
+ }
+
+ void dispatchDataSpaceChanged(
+ int32_t dataSpace, int32_t aspects, int32_t pixelFormat) override {
+ // TODO
+ (void)dataSpace;
+ (void)aspects;
+ (void)pixelFormat;
+ }
+
+private:
+ std::weak_ptr<CCodecBufferChannel> mChannel;
+ std::map<int32_t, C2Handle *> mHandles;
+ int64_t mLastTimestamp;
+ std::weak_ptr<C2Allocator> mAllocator;
+};
+
CCodecBufferChannel::QueueGuard::QueueGuard(
CCodecBufferChannel::QueueSync &sync) : mSync(sync) {
std::unique_lock<std::mutex> l(mSync.mMutex);
@@ -601,10 +972,14 @@
if (mCrypto != nullptr && mDealer != nullptr && mHeapSeqNum >= 0) {
mCrypto->unsetHeap(mHeapSeqNum);
}
+ // TODO: is this the right place?
+ mInputClient->onRelease();
}
void CCodecBufferChannel::setComponent(const std::shared_ptr<C2Component> &component) {
mComponent = component;
+ mInputClient.reset(new InputBufferClient(shared_from_this()));
+
C2StreamFormatConfig::input inputFormat(0u);
C2StreamFormatConfig::output outputFormat(0u);
c2_status_t err = mComponent->intf()->query_vb(
@@ -638,6 +1013,10 @@
} else {
// TODO: error
}
+ // TODO: remove once we switch to proper buffer pool.
+ if (!graphic) {
+ *buffers = (*buffers)->toArrayMode();
+ }
}
{
@@ -652,6 +1031,18 @@
}
}
+status_t CCodecBufferChannel::setGraphicBufferSource(
+ const sp<GraphicBufferSource> &source) {
+ ALOGV("setGraphicBufferSource");
+ mInputClient.reset(new BufferQueueClient(source));
+
+ // TODO: proper color aspect & dataspace
+ android_dataspace dataSpace = HAL_DATASPACE_BT709;
+ // TODO: read settings properly from the interface
+ return source->configure(new C2ComponentWrapper(
+ shared_from_this()), dataSpace, 16, 1080, 1920, GRALLOC_USAGE_SW_READ_OFTEN);
+}
+
status_t CCodecBufferChannel::queueInputBuffer(const sp<MediaCodecBuffer> &buffer) {
QueueGuard guard(mSync);
if (!guard.isRunning()) {
@@ -665,16 +1056,16 @@
int32_t flags = 0;
int32_t tmp = 0;
if (buffer->meta()->findInt32("eos", &tmp) && tmp) {
- flags |= C2BufferPack::FLAG_END_OF_STREAM;
+ flags |= C2FrameData::FLAG_END_OF_STREAM;
ALOGV("input EOS");
}
if (buffer->meta()->findInt32("csd", &tmp) && tmp) {
- flags |= C2BufferPack::FLAG_CODEC_CONFIG;
+ flags |= C2FrameData::FLAG_CODEC_CONFIG;
}
std::unique_ptr<C2Work> work(new C2Work);
- work->input.flags = (C2BufferPack::flags_t)flags;
+ work->input.flags = (C2FrameData::flags_t)flags;
work->input.ordinal.timestamp = timeUs;
- work->input.ordinal.frame_index = mFrameIndex++;
+ work->input.ordinal.frameIndex = mFrameIndex++;
work->input.buffers.clear();
{
Mutexed<std::unique_ptr<InputBuffers>>::Locked buffers(mInputBuffers);
@@ -708,9 +1099,24 @@
return -ENOSYS;
}
+void CCodecBufferChannel::feedInputBufferIfAvailable() {
+ sp<MediaCodecBuffer> inBuffer;
+ size_t index;
+ {
+ Mutexed<std::unique_ptr<InputBuffers>>::Locked buffers(mInputBuffers);
+ if (!(*buffers)->requestNewBuffer(&index, &inBuffer)) {
+ ALOGW("no new buffer available");
+ inBuffer = nullptr;
+ }
+ }
+ ALOGV("new input index = %zu", index);
+ mInputClient->onInputBufferAvailable(index, inBuffer);
+}
+
status_t CCodecBufferChannel::renderOutputBuffer(
const sp<MediaCodecBuffer> &buffer, int64_t timestampNs) {
ALOGV("renderOutputBuffer");
+ feedInputBufferIfAvailable();
std::shared_ptr<C2Buffer> c2Buffer;
{
@@ -724,7 +1130,7 @@
return OK;
}
- std::list<C2ConstGraphicBlock> blocks = c2Buffer->data().graphicBlocks();
+ std::vector<C2ConstGraphicBlock> blocks = c2Buffer->data().graphicBlocks();
if (blocks.size() != 1u) {
ALOGE("# of graphic blocks expected to be 1, but %zu", blocks.size());
return UNKNOWN_ERROR;
@@ -780,7 +1186,9 @@
}
{
Mutexed<std::unique_ptr<OutputBuffers>>::Locked buffers(mOutputBuffers);
- (void)(*buffers)->releaseBuffer(buffer);
+ if((*buffers)->releaseBuffer(buffer)) {
+ feedInputBufferIfAvailable();
+ }
}
return OK;
}
@@ -832,13 +1240,15 @@
return;
}
}
- mCallback->onInputBufferAvailable(index, buffer);
+ mInputClient->onInputBufferAdded(index, buffer);
}
+ mInputClient->onStart();
}
void CCodecBufferChannel::stop() {
mSync.stop();
mFirstValidFrameIndex = mFrameIndex.load();
+ mInputClient->onStop();
}
void CCodecBufferChannel::flush(const std::list<std::unique_ptr<C2Work>> &flushedWork) {
@@ -852,106 +1262,110 @@
}
}
-void CCodecBufferChannel::onWorkDone(std::vector<std::unique_ptr<C2Work>> workItems) {
- for (const auto &work : workItems) {
- sp<MediaCodecBuffer> inBuffer;
- size_t index;
- {
- Mutexed<std::unique_ptr<InputBuffers>>::Locked buffers(mInputBuffers);
- if (!(*buffers)->requestNewBuffer(&index, &inBuffer)) {
- ALOGW("no new buffer available");
- inBuffer = nullptr;
- }
- }
- if (inBuffer != nullptr) {
- mCallback->onInputBufferAvailable(index, inBuffer);
- }
+void CCodecBufferChannel::onWorkDone(const std::unique_ptr<C2Work> &work) {
+ if (work->result != OK) {
+ ALOGE("work failed to complete: %d", work->result);
+ mOnError(work->result, ACTION_CODE_FATAL);
+ return;
+ }
- if (work->result != OK) {
- ALOGE("work failed to complete: %d", work->result);
- mOnError(work->result, ACTION_CODE_FATAL);
+ // NOTE: MediaCodec usage supposedly have only one worklet
+ if (work->worklets.size() != 1u) {
+ ALOGE("onWorkDone: incorrect number of worklets: %zu",
+ work->worklets.size());
+ mOnError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
+ return;
+ }
+
+ const std::unique_ptr<C2Worklet> &worklet = work->worklets.front();
+ if ((worklet->output.ordinal.frameIndex - mFirstValidFrameIndex.load()).peek() < 0) {
+ // Discard frames from previous generation.
+ return;
+ }
+ std::shared_ptr<C2Buffer> buffer;
+ // NOTE: MediaCodec usage supposedly have only one output stream.
+ if (worklet->output.buffers.size() > 1u) {
+ ALOGE("onWorkDone: incorrect number of output buffers: %zu",
+ worklet->output.buffers.size());
+ mOnError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
+ return;
+ } else if (worklet->output.buffers.size() == 1u) {
+ buffer = worklet->output.buffers[0];
+ if (!buffer) {
+ ALOGW("onWorkDone: nullptr found in buffers; ignored.");
+ }
+ }
+
+ const C2StreamCsdInfo::output *csdInfo = nullptr;
+ for (const std::unique_ptr<C2Param> &info : worklet->output.configUpdate) {
+ if (info->coreIndex() == C2StreamCsdInfo::output::CORE_INDEX) {
+ ALOGV("onWorkDone: csd found");
+ csdInfo = static_cast<const C2StreamCsdInfo::output *>(info.get());
+ }
+ }
+
+ int32_t flags = 0;
+ if (worklet->output.flags & C2FrameData::FLAG_END_OF_STREAM) {
+ flags |= MediaCodec::BUFFER_FLAG_EOS;
+ ALOGV("onWorkDone: output EOS");
+ }
+
+ sp<MediaCodecBuffer> outBuffer;
+ size_t index;
+ if (csdInfo != nullptr) {
+ Mutexed<std::unique_ptr<OutputBuffers>>::Locked buffers(mOutputBuffers);
+ if ((*buffers)->registerCsd(csdInfo, &index, &outBuffer)) {
+ outBuffer->meta()->setInt64("timeUs", worklet->output.ordinal.timestamp.peek());
+ outBuffer->meta()->setInt32("flags", flags | MediaCodec::BUFFER_FLAG_CODECCONFIG);
+ ALOGV("onWorkDone: csd index = %zu", index);
+
+ buffers.unlock();
+ mCallback->onOutputBufferAvailable(index, outBuffer);
+ buffers.lock();
+ } else {
+ ALOGE("onWorkDone: unable to register csd");
+ buffers.unlock();
+ mOnError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
+ buffers.lock();
return;
}
-
- // NOTE: MediaCodec usage supposedly have only one worklet
- if (work->worklets.size() != 1u) {
- ALOGE("incorrect number of worklets: %zu", work->worklets.size());
- mOnError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
- continue;
- }
-
- const std::unique_ptr<C2Worklet> &worklet = work->worklets.front();
- if (worklet->output.ordinal.frame_index < mFirstValidFrameIndex) {
- // Discard frames from previous generation.
- continue;
- }
- // NOTE: MediaCodec usage supposedly have only one output stream.
- if (worklet->output.buffers.size() != 1u) {
- ALOGE("incorrect number of output buffers: %zu", worklet->output.buffers.size());
- mOnError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
- continue;
- }
-
- const std::shared_ptr<C2Buffer> &buffer = worklet->output.buffers[0];
- const C2StreamCsdInfo::output *csdInfo = nullptr;
- if (buffer) {
- // TODO: transfer infos() into buffer metadata
- }
- for (const auto &info : worklet->output.infos) {
- if (info->coreIndex() == C2StreamCsdInfo::output::CORE_INDEX) {
- ALOGV("csd found");
- csdInfo = static_cast<const C2StreamCsdInfo::output *>(info.get());
- }
- }
-
- int32_t flags = 0;
- if (worklet->output.flags & C2BufferPack::FLAG_END_OF_STREAM) {
- flags |= MediaCodec::BUFFER_FLAG_EOS;
- ALOGV("output EOS");
- }
-
- sp<MediaCodecBuffer> outBuffer;
- if (csdInfo != nullptr) {
- Mutexed<std::unique_ptr<OutputBuffers>>::Locked buffers(mOutputBuffers);
- if ((*buffers)->registerCsd(csdInfo, &index, &outBuffer)) {
- outBuffer->meta()->setInt64("timeUs", worklet->output.ordinal.timestamp);
- outBuffer->meta()->setInt32("flags", flags | MediaCodec::BUFFER_FLAG_CODECCONFIG);
- ALOGV("csd index = %zu", index);
-
- buffers.unlock();
- mCallback->onOutputBufferAvailable(index, outBuffer);
- buffers.lock();
- } else {
- ALOGE("unable to register output buffer");
- buffers.unlock();
- mOnError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
- buffers.lock();
- continue;
- }
- }
-
- if (!buffer && !flags) {
- ALOGV("Not reporting output buffer");
- continue;
- }
-
- {
- Mutexed<std::unique_ptr<OutputBuffers>>::Locked buffers(mOutputBuffers);
- if (!(*buffers)->registerBuffer(buffer, &index, &outBuffer)) {
- ALOGE("unable to register output buffer");
-
- buffers.unlock();
- mOnError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
- buffers.lock();
- continue;
- }
- }
-
- outBuffer->meta()->setInt64("timeUs", worklet->output.ordinal.timestamp);
- outBuffer->meta()->setInt32("flags", flags);
- ALOGV("index = %zu", index);
- mCallback->onOutputBufferAvailable(index, outBuffer);
}
+
+ if (!buffer && !flags) {
+ ALOGV("onWorkDone: Not reporting output buffer");
+ return;
+ }
+
+ if (buffer) {
+ for (const std::shared_ptr<const C2Info> &info : buffer->info()) {
+ // TODO: properly translate these to metadata
+ switch (info->coreIndex().coreIndex()) {
+ case C2StreamPictureTypeMaskInfo::CORE_INDEX:
+ if (((C2StreamPictureTypeMaskInfo *)info.get())->value & C2PictureTypeKeyFrame) {
+ flags |= MediaCodec::BUFFER_FLAG_SYNCFRAME;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ {
+ Mutexed<std::unique_ptr<OutputBuffers>>::Locked buffers(mOutputBuffers);
+ if (!(*buffers)->registerBuffer(buffer, &index, &outBuffer)) {
+ ALOGE("onWorkDone: unable to register output buffer");
+ buffers.unlock();
+ mOnError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
+ buffers.lock();
+ return;
+ }
+ }
+
+ outBuffer->meta()->setInt64("timeUs", worklet->output.ordinal.timestamp.peek());
+ outBuffer->meta()->setInt32("flags", flags);
+ ALOGV("onWorkDone: out buffer index = %zu", index);
+ mCallback->onOutputBufferAvailable(index, outBuffer);
}
status_t CCodecBufferChannel::setSurface(const sp<Surface> &newSurface) {
diff --git a/media/libstagefright/MediaCodec.cpp b/media/libstagefright/MediaCodec.cpp
index 77d9ce4..559b108 100644
--- a/media/libstagefright/MediaCodec.cpp
+++ b/media/libstagefright/MediaCodec.cpp
@@ -51,7 +51,6 @@
#include <media/stagefright/MediaDefs.h>
#include <media/stagefright/MediaErrors.h>
#include <media/stagefright/MediaFilter.h>
-#include <media/stagefright/MetaData.h>
#include <media/stagefright/OMXClient.h>
#include <media/stagefright/PersistentSurface.h>
#include <media/stagefright/SurfaceUtils.h>
diff --git a/media/libstagefright/MediaCodecSource.cpp b/media/libstagefright/MediaCodecSource.cpp
index cf800b2..04d83af 100644
--- a/media/libstagefright/MediaCodecSource.cpp
+++ b/media/libstagefright/MediaCodecSource.cpp
@@ -23,6 +23,7 @@
#include <gui/IGraphicBufferProducer.h>
#include <gui/Surface.h>
#include <media/ICrypto.h>
+#include <media/MediaBufferHolder.h>
#include <media/MediaCodecBuffer.h>
#include <media/MediaSource.h>
#include <media/stagefright/foundation/ABuffer.h>
@@ -457,13 +458,6 @@
mGeneration(0) {
CHECK(mLooper != NULL);
- AString mime;
- CHECK(mOutputFormat->findString("mime", &mime));
-
- if (!strncasecmp("video/", mime.c_str(), 6)) {
- mIsVideo = true;
- }
-
if (!(mFlags & FLAG_USE_SURFACE_INPUT)) {
mPuller = new Puller(source);
}
@@ -487,6 +481,7 @@
}
status_t MediaCodecSource::initEncoder() {
+
mReflector = new AHandlerReflector<MediaCodecSource>(this);
mLooper->registerHandler(mReflector);
@@ -500,23 +495,12 @@
AString outputMIME;
CHECK(mOutputFormat->findString("mime", &outputMIME));
+ mIsVideo = outputMIME.startsWithIgnoreCase("video/");
- Vector<AString> matchingCodecs;
- MediaCodecList::findMatchingCodecs(
- outputMIME.c_str(), true /* encoder */,
- ((mFlags & FLAG_PREFER_SOFTWARE_CODEC) ? MediaCodecList::kPreferSoftwareCodecs : 0),
- &matchingCodecs);
-
+ AString name;
status_t err = NO_INIT;
- for (size_t ix = 0; ix < matchingCodecs.size(); ++ix) {
- mEncoder = MediaCodec::CreateByComponentName(
- mCodecLooper, matchingCodecs[ix]);
-
- if (mEncoder == NULL) {
- continue;
- }
-
- ALOGV("output format is '%s'", mOutputFormat->debugString(0).c_str());
+ if (mOutputFormat->findString("testing-name", &name)) {
+ mEncoder = MediaCodec::CreateByComponentName(mCodecLooper, name);
mEncoderActivityNotify = new AMessage(kWhatEncoderActivity, mReflector);
mEncoder->setCallback(mEncoderActivityNotify);
@@ -526,12 +510,38 @@
NULL /* nativeWindow */,
NULL /* crypto */,
MediaCodec::CONFIGURE_FLAG_ENCODE);
+ } else {
+ Vector<AString> matchingCodecs;
+ MediaCodecList::findMatchingCodecs(
+ outputMIME.c_str(), true /* encoder */,
+ ((mFlags & FLAG_PREFER_SOFTWARE_CODEC) ? MediaCodecList::kPreferSoftwareCodecs : 0),
+ &matchingCodecs);
- if (err == OK) {
- break;
+ for (size_t ix = 0; ix < matchingCodecs.size(); ++ix) {
+ mEncoder = MediaCodec::CreateByComponentName(
+ mCodecLooper, matchingCodecs[ix]);
+
+ if (mEncoder == NULL) {
+ continue;
+ }
+
+ ALOGV("output format is '%s'", mOutputFormat->debugString(0).c_str());
+
+ mEncoderActivityNotify = new AMessage(kWhatEncoderActivity, mReflector);
+ mEncoder->setCallback(mEncoderActivityNotify);
+
+ err = mEncoder->configure(
+ mOutputFormat,
+ NULL /* nativeWindow */,
+ NULL /* crypto */,
+ MediaCodec::CONFIGURE_FLAG_ENCODE);
+
+ if (err == OK) {
+ break;
+ }
+ mEncoder->release();
+ mEncoder = NULL;
}
- mEncoder->release();
- mEncoder = NULL;
}
if (err != OK) {
@@ -730,7 +740,8 @@
if (mIsVideo) {
// video encoder will release MediaBuffer when done
// with underlying data.
- inbuf->setMediaBufferBase(mbuf);
+ inbuf->meta()->setObject("mediaBufferHolder", new MediaBufferHolder(mbuf));
+ mbuf->release();
} else {
mbuf->release();
}
diff --git a/media/libstagefright/MediaExtractorFactory.cpp b/media/libstagefright/MediaExtractorFactory.cpp
index a777663..472c137 100644
--- a/media/libstagefright/MediaExtractorFactory.cpp
+++ b/media/libstagefright/MediaExtractorFactory.cpp
@@ -26,7 +26,6 @@
#include <media/stagefright/FileSource.h>
#include <media/stagefright/InterfaceUtils.h>
#include <media/stagefright/MediaExtractorFactory.h>
-#include <media/stagefright/MetaData.h>
#include <media/IMediaExtractor.h>
#include <media/IMediaExtractorService.h>
#include <cutils/properties.h>
@@ -38,8 +37,6 @@
namespace android {
-static const char *kSystemApkPath = "/system/app/MediaComponents/MediaComponents.apk";
-
// static
sp<IMediaExtractor> MediaExtractorFactory::Create(
const sp<DataSource> &source, const char *mime) {
@@ -146,9 +143,14 @@
MediaExtractor::ExtractorDef def;
void *libHandle;
String8 libPath;
+ String8 uuidString;
ExtractorPlugin(MediaExtractor::ExtractorDef definition, void *handle, String8 &path)
- : def(definition), libHandle(handle), libPath(path) { }
+ : def(definition), libHandle(handle), libPath(path) {
+ for (size_t i = 0; i < sizeof MediaExtractor::ExtractorDef::extractor_uuid; i++) {
+ uuidString.appendFormat("%02x", def.extractor_uuid.b[i]);
+ }
+ }
~ExtractorPlugin() {
if (libHandle != nullptr) {
ALOGV("closing handle for %s %d", libPath.c_str(), def.extractor_version);
@@ -241,7 +243,7 @@
}
//static
-void MediaExtractorFactory::RegisterExtractors(
+void MediaExtractorFactory::RegisterExtractorsInApk(
const char *apkPath, List<sp<ExtractorPlugin>> &pluginList) {
ALOGV("search for plugins at %s", apkPath);
ZipArchiveHandle zipHandle;
@@ -249,7 +251,8 @@
if (ret == 0) {
char abi[PROPERTY_VALUE_MAX];
property_get("ro.product.cpu.abi", abi, "arm64-v8a");
- ZipString prefix(String8::format("lib/%s/", abi).c_str());
+ String8 prefix8 = String8::format("lib/%s/", abi);
+ ZipString prefix(prefix8.c_str());
ZipString suffix("extractor.so");
void* cookie;
ret = StartIteration(zipHandle, &cookie, &prefix, &suffix);
@@ -259,6 +262,8 @@
while (Next(cookie, &entry, &name) == 0) {
String8 libPath = String8(apkPath) + "!/" +
String8(reinterpret_cast<const char*>(name.name), name.name_length);
+ // TODO: Open with a linker namespace so that it can be linked with sub-libraries
+ // within the apk instead of system libraries already loaded.
void *libHandle = dlopen(libPath.string(), RTLD_NOW | RTLD_LOCAL);
if (libHandle) {
MediaExtractor::GetExtractorDef getDef =
@@ -285,6 +290,38 @@
}
}
+//static
+void MediaExtractorFactory::RegisterExtractorsInSystem(
+ const char *libDirPath, List<sp<ExtractorPlugin>> &pluginList) {
+ ALOGV("search for plugins at %s", libDirPath);
+ DIR *libDir = opendir(libDirPath);
+ if (libDir) {
+ struct dirent* libEntry;
+ while ((libEntry = readdir(libDir))) {
+ String8 libPath = String8(libDirPath) + "/" + libEntry->d_name;
+ void *libHandle = dlopen(libPath.string(), RTLD_NOW | RTLD_LOCAL);
+ if (libHandle) {
+ MediaExtractor::GetExtractorDef getDef =
+ (MediaExtractor::GetExtractorDef) dlsym(libHandle, "GETEXTRACTORDEF");
+ if (getDef) {
+ ALOGV("registering sniffer for %s", libPath.string());
+ RegisterExtractor(
+ new ExtractorPlugin(getDef(), libHandle, libPath), pluginList);
+ } else {
+ ALOGW("%s does not contain sniffer", libPath.string());
+ dlclose(libHandle);
+ }
+ } else {
+ ALOGW("couldn't dlopen(%s) %s", libPath.string(), strerror(errno));
+ }
+ }
+
+ closedir(libDir);
+ } else {
+ ALOGE("couldn't opendir(%s)", libDirPath);
+ }
+}
+
// static
void MediaExtractorFactory::UpdateExtractors(const char *newUpdateApkPath) {
Mutex::Autolock autoLock(gPluginMutex);
@@ -297,14 +334,40 @@
std::shared_ptr<List<sp<ExtractorPlugin>>> newList(new List<sp<ExtractorPlugin>>());
- RegisterExtractors(kSystemApkPath, *newList);
+ RegisterExtractorsInSystem("/system/lib"
+#ifdef __LP64__
+ "64"
+#endif
+ "/extractors", *newList);
+
+ RegisterExtractorsInSystem("/vendor/lib"
+#ifdef __LP64__
+ "64"
+#endif
+ "/extractors", *newList);
if (newUpdateApkPath != nullptr) {
- RegisterExtractors(newUpdateApkPath, *newList);
+ RegisterExtractorsInApk(newUpdateApkPath, *newList);
}
gPlugins = newList;
gPluginsRegistered = true;
}
+status_t MediaExtractorFactory::dump(int fd, const Vector<String16>&) {
+ Mutex::Autolock autoLock(gPluginMutex);
+ String8 out;
+ out.append("Available extractors:\n");
+ for (auto it = gPlugins->begin(); it != gPlugins->end(); ++it) {
+ out.appendFormat(" %25s: uuid(%s), version(%u), path(%s)\n",
+ (*it)->def.extractor_name,
+ (*it)->uuidString.c_str(),
+ (*it)->def.extractor_version,
+ (*it)->libPath.c_str());
+ }
+ write(fd, out.string(), out.size());
+ return OK;
+}
+
+
} // namespace android
diff --git a/media/libstagefright/NuMediaExtractor.cpp b/media/libstagefright/NuMediaExtractor.cpp
index 17c9648..d96f7e0 100644
--- a/media/libstagefright/NuMediaExtractor.cpp
+++ b/media/libstagefright/NuMediaExtractor.cpp
@@ -660,6 +660,28 @@
return err;
}
+status_t NuMediaExtractor::getSampleSize(size_t *sampleSize) {
+ Mutex::Autolock autoLock(mLock);
+
+ ssize_t minIndex = fetchAllTrackSamples();
+
+ if (minIndex < 0) {
+ return ERROR_END_OF_STREAM;
+ }
+
+ TrackInfo *info = &mSelectedTracks.editItemAt(minIndex);
+ auto it = info->mSamples.begin();
+ *sampleSize = it->mBuffer->range_length();
+
+ if (info->mTrackFlags & kIsVorbis) {
+ // Each sample's data is suffixed by the number of page samples
+ // or -1 if not available.
+ *sampleSize += sizeof(int32_t);
+ }
+
+ return OK;
+}
+
status_t NuMediaExtractor::getSampleTrackIndex(size_t *trackIndex) {
Mutex::Autolock autoLock(mLock);
diff --git a/media/libstagefright/codec2/SimpleC2Component.cpp b/media/libstagefright/codec2/SimpleC2Component.cpp
index 4d75a31..c3ae00e 100644
--- a/media/libstagefright/codec2/SimpleC2Component.cpp
+++ b/media/libstagefright/codec2/SimpleC2Component.cpp
@@ -342,7 +342,7 @@
return;
}
}
- if (work->worklets_processed != 0u) {
+ if (work->workletsProcessed != 0u) {
Mutexed<ExecState>::Locked state(mExecState);
ALOGV("returning this work");
state->mListener->onWorkDone_nb(shared_from_this(), vec(work));
@@ -351,7 +351,7 @@
std::unique_ptr<C2Work> unexpected;
{
Mutexed<PendingWork>::Locked pending(mPendingWork);
- uint64_t frameIndex = work->input.ordinal.frame_index;
+ uint64_t frameIndex = work->input.ordinal.frameIndex.peeku();
if (pending->count(frameIndex) != 0) {
unexpected = std::move(pending->at(frameIndex));
pending->erase(frameIndex);
diff --git a/media/libstagefright/codec2/SimpleC2Interface.cpp b/media/libstagefright/codec2/SimpleC2Interface.cpp
index f9cab26..f082243 100644
--- a/media/libstagefright/codec2/SimpleC2Interface.cpp
+++ b/media/libstagefright/codec2/SimpleC2Interface.cpp
@@ -23,7 +23,7 @@
namespace android {
c2_status_t SimpleC2Interface::query_vb(
- const std::vector<C2Param* const> &stackParams,
+ const std::vector<C2Param*> &stackParams,
const std::vector<C2Param::Index> &heapParamIndices,
c2_blocking_t mayBlock,
std::vector<std::unique_ptr<C2Param>>* const heapParams) const {
diff --git a/media/libstagefright/codec2/include/C2.h b/media/libstagefright/codec2/include/C2.h
index fd99cce..442c89e 100644
--- a/media/libstagefright/codec2/include/C2.h
+++ b/media/libstagefright/codec2/include/C2.h
@@ -210,6 +210,219 @@
} \
DEFINE_OTHER_COMPARISON_OPERATORS(type)
+template<typename T, typename B>
+class C2_HIDE c2_cntr_t;
+
+/// \cond INTERNAL
+
+/// \defgroup utils_internal
+/// @{
+
+template<typename T>
+struct C2_HIDE _c2_cntr_compat_helper {
+ template<typename U, typename E=typename std::enable_if<std::is_integral<U>::value>::type>
+ __attribute__((no_sanitize("integer")))
+ inline static constexpr T get(const U &value) {
+ return T(value);
+ }
+
+ template<typename U, typename E=typename std::enable_if<(sizeof(U) >= sizeof(T))>::type>
+ __attribute__((no_sanitize("integer")))
+ inline static constexpr T get(const c2_cntr_t<U, void> &value) {
+ return T(value.mValue);
+ }
+};
+
+/// @}
+
+/// \endcond
+
+/**
+ * Integral counter type.
+ *
+ * This is basically an unsigned integral type that is NEVER checked for overflow/underflow - and
+ * comparison operators are redefined.
+ *
+ * \note Comparison of counter types is not fully transitive, e.g.
+ * it could be that a > b > c but a !> c.
+ * std::less<>, greater<>, less_equal<> and greater_equal<> specializations yield total ordering,
+ * but may not match semantic ordering of the values.
+ *
+ * Technically: counter types represent integer values: A * 2^N + value, where A can be arbitrary.
+ * This makes addition, subtraction, multiplication (as well as bitwise operations) well defined.
+ * However, division is in general not well defined, as the result may depend on A. This is also
+ * true for logical operators and boolean conversion.
+ *
+ * Even though well defined, bitwise operators are not implemented for counter types as they are not
+ * meaningful.
+ */
+template<typename T, typename B=typename std::enable_if<std::is_integral<T>::value && std::is_unsigned<T>::value>::type>
+class C2_HIDE c2_cntr_t {
+ using compat = _c2_cntr_compat_helper<T>;
+
+ T mValue;
+ constexpr static T HALF_RANGE = T(~0) ^ (T(~0) >> 1);
+
+ template<typename U>
+ friend struct _c2_cntr_compat_helper;
+public:
+
+ /**
+ * Default constructor. Initialized counter to 0.
+ */
+ inline constexpr c2_cntr_t() : mValue(T(0)) {}
+
+ /**
+ * Construct from a compatible type.
+ */
+ template<typename U>
+ inline constexpr c2_cntr_t(const U &value) : mValue(compat::get(value)) {}
+
+ /**
+ * Peek as underlying signed type.
+ */
+ __attribute__((no_sanitize("integer")))
+ inline constexpr typename std::make_signed<T>::type peek() const {
+ return static_cast<typename std::make_signed<T>::type>(mValue);
+ }
+
+ /**
+ * Peek as underlying unsigned type.
+ */
+ inline constexpr T peeku() const {
+ return mValue;
+ }
+
+ /**
+ * Peek as long long - e.g. for printing.
+ */
+ __attribute__((no_sanitize("integer")))
+ inline constexpr long long peekll() const {
+ return (long long)mValue;
+ }
+
+ /**
+ * Peek as unsigned long long - e.g. for printing.
+ */
+ __attribute__((no_sanitize("integer")))
+ inline constexpr unsigned long long peekull() const {
+ return (unsigned long long)mValue;
+ }
+
+ /**
+ * Convert to a smaller counter type. This is always safe.
+ */
+ template<typename U, typename E=typename std::enable_if<sizeof(U) < sizeof(T)>::type>
+ inline operator c2_cntr_t<U>() {
+ return c2_cntr_t<U>(mValue);
+ }
+
+ /**
+ * Arithmetic operators
+ */
+
+#define DEFINE_C2_CNTR_BINARY_OP(attrib, op, op_assign) \
+ template<typename U> \
+ attrib inline c2_cntr_t<T>& operator op_assign(const U &value) { \
+ mValue op_assign compat::get(value); \
+ return *this; \
+ } \
+ \
+ template<typename U, typename E=decltype(compat::get(U(0)))> \
+ attrib inline constexpr c2_cntr_t<T> operator op(const U &value) const { \
+ return c2_cntr_t<T>(mValue op compat::get(value)); \
+ } \
+ \
+ template<typename U, typename E=typename std::enable_if<sizeof(U) < sizeof(T)>::type> \
+ attrib inline constexpr c2_cntr_t<U> operator op(const c2_cntr_t<U> &value) const { \
+ return c2_cntr_t<U>(U(mValue) op value.peeku()); \
+ }
+
+#define DEFINE_C2_CNTR_UNARY_OP(attrib, op) \
+ attrib inline constexpr c2_cntr_t<T> operator op() const { \
+ return c2_cntr_t<T>(op mValue); \
+ }
+
+#define DEFINE_C2_CNTR_CREMENT_OP(attrib, op) \
+ attrib inline c2_cntr_t<T> &operator op() { \
+ op mValue; \
+ return *this; \
+ } \
+ attrib inline c2_cntr_t<T> operator op(int) { \
+ return c2_cntr_t<T, void>(mValue op); \
+ }
+
+ DEFINE_C2_CNTR_BINARY_OP(__attribute__((no_sanitize("integer"))), +, +=)
+ DEFINE_C2_CNTR_BINARY_OP(__attribute__((no_sanitize("integer"))), -, -=)
+ DEFINE_C2_CNTR_BINARY_OP(__attribute__((no_sanitize("integer"))), *, *=)
+
+ DEFINE_C2_CNTR_UNARY_OP(__attribute__((no_sanitize("integer"))), -)
+ DEFINE_C2_CNTR_UNARY_OP(__attribute__((no_sanitize("integer"))), +)
+
+ DEFINE_C2_CNTR_CREMENT_OP(__attribute__((no_sanitize("integer"))), ++)
+ DEFINE_C2_CNTR_CREMENT_OP(__attribute__((no_sanitize("integer"))), --)
+
+ template<typename U, typename E=typename std::enable_if<std::is_unsigned<U>::value>::type>
+ __attribute__((no_sanitize("integer")))
+ inline constexpr c2_cntr_t<T> operator<<(const U &value) const {
+ return c2_cntr_t<T>(mValue << value);
+ }
+
+ template<typename U, typename E=typename std::enable_if<std::is_unsigned<U>::value>::type>
+ __attribute__((no_sanitize("integer")))
+ inline c2_cntr_t<T> &operator<<=(const U &value) {
+ mValue <<= value;
+ return *this;
+ }
+
+ /**
+ * Comparison operators
+ */
+ __attribute__((no_sanitize("integer")))
+ inline constexpr bool operator<=(const c2_cntr_t<T> &other) const {
+ return T(other.mValue - mValue) < HALF_RANGE;
+ }
+
+ __attribute__((no_sanitize("integer")))
+ inline constexpr bool operator>=(const c2_cntr_t<T> &other) const {
+ return T(mValue - other.mValue) < HALF_RANGE;
+ }
+
+ inline constexpr bool operator==(const c2_cntr_t<T> &other) const {
+ return mValue == other.mValue;
+ }
+
+ inline constexpr bool operator!=(const c2_cntr_t<T> &other) const {
+ return !(*this == other);
+ }
+
+ inline constexpr bool operator<(const c2_cntr_t<T> &other) const {
+ return *this <= other && *this != other;
+ }
+
+ inline constexpr bool operator>(const c2_cntr_t<T> &other) const {
+ return *this >= other && *this != other;
+ }
+};
+
+template<typename U, typename T, typename E=typename std::enable_if<std::is_integral<U>::value>::type>
+inline constexpr c2_cntr_t<T> operator+(const U &a, const c2_cntr_t<T> &b) {
+ return b + a;
+}
+
+template<typename U, typename T, typename E=typename std::enable_if<std::is_integral<U>::value>::type>
+inline constexpr c2_cntr_t<T> operator-(const U &a, const c2_cntr_t<T> &b) {
+ return c2_cntr_t<T>(a) - b;
+}
+
+template<typename U, typename T, typename E=typename std::enable_if<std::is_integral<U>::value>::type>
+inline constexpr c2_cntr_t<T> operator*(const U &a, const c2_cntr_t<T> &b) {
+ return b * a;
+}
+
+typedef c2_cntr_t<uint32_t> c2_cntr32_t; /** 32-bit counter type */
+typedef c2_cntr_t<uint64_t> c2_cntr64_t; /** 64-bit counter type */
+
/// \cond INTERNAL
/// \defgroup utils_internal
@@ -326,4 +539,30 @@
} // namespace android
#endif
+#include <functional>
+template<typename T>
+struct std::less<::android::c2_cntr_t<T>> {
+ constexpr bool operator()(const ::android::c2_cntr_t<T> &lh, const ::android::c2_cntr_t<T> &rh) const {
+ return lh.peeku() < rh.peeku();
+ }
+};
+template<typename T>
+struct std::less_equal<::android::c2_cntr_t<T>> {
+ constexpr bool operator()(const ::android::c2_cntr_t<T> &lh, const ::android::c2_cntr_t<T> &rh) const {
+ return lh.peeku() <= rh.peeku();
+ }
+};
+template<typename T>
+struct std::greater<::android::c2_cntr_t<T>> {
+ constexpr bool operator()(const ::android::c2_cntr_t<T> &lh, const ::android::c2_cntr_t<T> &rh) const {
+ return lh.peeku() > rh.peeku();
+ }
+};
+template<typename T>
+struct std::greater_equal<::android::c2_cntr_t<T>> {
+ constexpr bool operator()(const ::android::c2_cntr_t<T> &lh, const ::android::c2_cntr_t<T> &rh) const {
+ return lh.peeku() >= rh.peeku();
+ }
+};
+
#endif // C2_H_
diff --git a/media/libstagefright/codec2/include/C2Buffer.h b/media/libstagefright/codec2/include/C2Buffer.h
index df9362c..2ca8222 100644
--- a/media/libstagefright/codec2/include/C2Buffer.h
+++ b/media/libstagefright/codec2/include/C2Buffer.h
@@ -284,6 +284,7 @@
/// @{
public:
inline uint32_t offset() const { return mOffset; }
+ inline uint32_t endOffset() const { return mOffset + mSize; }
inline uint32_t size() const { return mSize; }
protected:
@@ -311,6 +312,32 @@
/// @}
};
+class C2_HIDE _C2LinearCapacityBase : public _C2LinearCapacityAspect {
+public:
+ inline explicit _C2LinearCapacityBase(size_t capacity)
+ : _C2LinearCapacityAspect(c2_min(capacity, std::numeric_limits<uint32_t>::max())) {}
+};
+
+/**
+ * Utility class for safe range calculations.
+ */
+class C2LinearRange : public _C2LinearRangeAspect {
+public:
+ inline C2LinearRange(const _C2LinearCapacityBase &parent, size_t offset, size_t size)
+ : _C2LinearRangeAspect(&parent, offset, size) {}
+};
+
+/**
+ * Utility class for simple capacity and range construction.
+ */
+class C2LinearCapacity : public _C2LinearCapacityBase {
+public:
+ using _C2LinearCapacityBase::_C2LinearCapacityBase;
+ inline C2LinearRange range(size_t offset, size_t size) {
+ return C2LinearRange(*this, offset, size);
+ }
+};
+
/**
* Aspect for objects that have an editable linear range.
*
@@ -652,14 +679,14 @@
*
* \param size number of bytes to share
* \param fence fence to be used for the section
- * \param blocks list where the blocks of the section are appended to
+ * \param blocks vector where the blocks of the section are appended to
*
* \retval C2_OK the portion was successfully shared
* \retval C2_NO_MEMORY not enough memory to share the portion
* \retval C2_TIMED_OUT the operation timed out (unexpected)
* \retval C2_CORRUPTED some unknown error prevented sharing the data (unexpected)
*/
- c2_status_t share(size_t size, C2Fence fence, std::list<C2ConstLinearBlock> &blocks);
+ c2_status_t share(size_t size, C2Fence fence, std::vector<C2ConstLinearBlock> &blocks);
/**
* Returns the beginning offset of this segment from the start of this circular block.
@@ -1176,14 +1203,14 @@
* \return a constant list of const linear blocks of this buffer.
* \retval empty list if this buffer does not contain linear block(s).
*/
- const std::list<C2ConstLinearBlock> linearBlocks() const;
+ const std::vector<C2ConstLinearBlock> linearBlocks() const;
/**
* Gets the graphic blocks of this buffer.
* \return a constant list of const graphic blocks of this buffer.
* \retval empty list if this buffer does not contain graphic block(s).
*/
- const std::list<C2ConstGraphicBlock> graphicBlocks() const;
+ const std::vector<C2ConstGraphicBlock> graphicBlocks() const;
private:
class Impl;
@@ -1191,8 +1218,8 @@
protected:
// no public constructor
- explicit C2BufferData(const std::list<C2ConstLinearBlock> &blocks);
- explicit C2BufferData(const std::list<C2ConstGraphicBlock> &blocks);
+ explicit C2BufferData(const std::vector<C2ConstLinearBlock> &blocks);
+ explicit C2BufferData(const std::vector<C2ConstGraphicBlock> &blocks);
};
/**
@@ -1274,7 +1301,7 @@
*
* \return a constant list of info objects associated with this buffer.
*/
- const std::list<std::shared_ptr<const C2Info>> infos() const;
+ const std::vector<std::shared_ptr<const C2Info>> info() const;
/**
* Attaches (or updates) an (existing) metadata for this buffer.
@@ -1301,8 +1328,8 @@
protected:
// no public constructor
- explicit C2Buffer(const std::list<C2ConstLinearBlock> &blocks);
- explicit C2Buffer(const std::list<C2ConstGraphicBlock> &blocks);
+ explicit C2Buffer(const std::vector<C2ConstLinearBlock> &blocks);
+ explicit C2Buffer(const std::vector<C2ConstGraphicBlock> &blocks);
private:
class Impl;
diff --git a/media/libstagefright/codec2/include/C2Component.h b/media/libstagefright/codec2/include/C2Component.h
index 38d545e..e023db4 100644
--- a/media/libstagefright/codec2/include/C2Component.h
+++ b/media/libstagefright/codec2/include/C2Component.h
@@ -150,7 +150,7 @@
* (this error code is only allowed for interfaces connected to components)
*/
virtual c2_status_t query_vb(
- const std::vector<C2Param* const> &stackParams,
+ const std::vector<C2Param*> &stackParams,
const std::vector<C2Param::Index> &heapParamIndices,
c2_blocking_t mayBlock,
std::vector<std::unique_ptr<C2Param>>* const heapParams) const = 0;
@@ -211,7 +211,7 @@
* (this error code is only allowed for interfaces connected to components)
*/
virtual c2_status_t config_vb(
- const std::vector<C2Param* const> ¶ms,
+ const std::vector<C2Param*> ¶ms,
c2_blocking_t mayBlock,
std::vector<std::unique_ptr<C2SettingResult>>* const failures) = 0;
@@ -682,7 +682,7 @@
*/
virtual c2_status_t reset() { return C2_OK; }
- virtual c2_status_t parseFrame(C2BufferPack &frame);
+ virtual c2_status_t parseFrame(C2FrameData &frame);
virtual ~C2FrameInfoParser() = default;
};
@@ -850,7 +850,7 @@
* (unexpected)
*/
virtual c2_status_t query_sm(
- const std::vector<C2Param* const> &stackParams,
+ const std::vector<C2Param*> &stackParams,
const std::vector<C2Param::Index> &heapParamIndices,
std::vector<std::unique_ptr<C2Param>>* const heapParams) const = 0;
@@ -889,7 +889,7 @@
* (unexpected)
*/
virtual c2_status_t config_sm(
- const std::vector<C2Param* const> ¶ms,
+ const std::vector<C2Param*> ¶ms,
std::vector<std::unique_ptr<C2SettingResult>>* const failures) = 0;
// REFLECTION MECHANISM (USED FOR EXTENSION)
diff --git a/media/libstagefright/codec2/include/C2Config.h b/media/libstagefright/codec2/include/C2Config.h
index 83cb72c..2a2b9de 100644
--- a/media/libstagefright/codec2/include/C2Config.h
+++ b/media/libstagefright/codec2/include/C2Config.h
@@ -67,6 +67,7 @@
kParamIndexVideoSizeTuning,
kParamIndexCsd,
+ kParamIndexPictureTypeMask,
// video info
@@ -133,6 +134,12 @@
typedef C2StreamParam<C2Info, C2BlobValue, kParamIndexCsd> C2StreamCsdInfo;
+C2ENUM(C2PictureTypeMask, uint32_t,
+ C2PictureTypeKeyFrame = (1u << 0),
+)
+
+typedef C2StreamParam<C2Info, C2Uint32Value, kParamIndexPictureTypeMask> C2StreamPictureTypeMaskInfo;
+
/*
Component description fields:
diff --git a/media/libstagefright/codec2/include/C2Param.h b/media/libstagefright/codec2/include/C2Param.h
index 2a8c1b2..e2df62d 100644
--- a/media/libstagefright/codec2/include/C2Param.h
+++ b/media/libstagefright/codec2/include/C2Param.h
@@ -397,8 +397,8 @@
/// safe(r) type cast from pointer and size
inline static C2Param* From(void *addr, size_t len) {
- // _mSize must fit into size
- if (len < sizeof(_mSize) + offsetof(C2Param, _mSize)) {
+ // _mSize must fit into size, but really C2Param must also to be a valid param
+ if (len < sizeof(C2Param)) {
return nullptr;
}
// _mSize must match length
@@ -446,7 +446,7 @@
// if other is the same kind of (valid) param as this, copy it into this and return true.
// otherwise, do not copy anything, and return false.
inline bool updateFrom(const C2Param &other) {
- if (other._mSize == _mSize && other._mIndex == _mIndex && _mSize > 0) {
+ if (other._mSize <= _mSize && other._mIndex == _mIndex && _mSize > 0) {
memcpy(this, &other, _mSize);
return true;
}
@@ -620,6 +620,8 @@
#endif
private:
+ friend struct _C2ParamInspector;
+
uint32_t _mOffset; // offset of field
uint32_t _mSize; // size of field
};
@@ -719,30 +721,51 @@
DEFINE_OTHER_COMPARISON_OPERATORS(C2ParamField)
+protected:
+ inline C2ParamField(C2Param::Index index, uint32_t offset, uint32_t size)
+ : _mIndex(index), _mFieldId(offset, size) {}
+
private:
+ friend struct _C2ParamInspector;
+
C2Param::Index _mIndex; ///< parameter index
_C2FieldId _mFieldId; ///< field identifier
};
/**
+ * Structure uniquely specifying a field, an array element of a field, or a
+ * parameter in a configuration
+ */
+struct C2ParamOrField : public C2ParamField {
+//public:
+ template<typename S>
+ inline C2ParamOrField(S* param)
+ : C2ParamField(param->index(), 0u, param->size()) {}
+};
+
+/**
* A shared (union) representation of numeric values
*/
class C2Value {
public:
/// A union of supported primitive types.
union Primitive {
- int32_t i32; ///< int32_t value
- uint32_t u32; ///< uint32_t value
- int64_t i64; ///< int64_t value
- uint64_t u64; ///< uint64_t value
- float fp; ///< float value
+ int32_t i32; ///< int32_t value
+ uint32_t u32; ///< uint32_t value
+ c2_cntr32_t c32; ///< c2_cntr32_t value
+ int64_t i64; ///< int64_t value
+ uint64_t u64; ///< uint64_t value
+ c2_cntr64_t c64; ///< c2_cntr64_t value
+ float fp; ///< float value
// constructors - implicit
- Primitive(int32_t value) : i32(value) { }
- Primitive(uint32_t value) : u32(value) { }
- Primitive(int64_t value) : i64(value) { }
- Primitive(uint64_t value) : u64(value) { }
- Primitive(float value) : fp(value) { }
+ Primitive(int32_t value) : i32(value) { }
+ Primitive(uint32_t value) : u32(value) { }
+ Primitive(c2_cntr32_t value) : c32(value) { }
+ Primitive(int64_t value) : i64(value) { }
+ Primitive(uint64_t value) : u64(value) { }
+ Primitive(c2_cntr64_t value) : c64(value) { }
+ Primitive(float value) : fp(value) { }
Primitive() : u64(0) { }
@@ -755,8 +778,10 @@
NO_INIT,
INT32,
UINT32,
+ CNTR32,
INT64,
UINT64,
+ CNTR64,
FLOAT,
};
@@ -788,12 +813,16 @@
template<> inline const int64_t &C2Value::Primitive::ref<int64_t>() const { return i64; }
template<> inline const uint32_t &C2Value::Primitive::ref<uint32_t>() const { return u32; }
template<> inline const uint64_t &C2Value::Primitive::ref<uint64_t>() const { return u64; }
+template<> inline const c2_cntr32_t &C2Value::Primitive::ref<c2_cntr32_t>() const { return c32; }
+template<> inline const c2_cntr64_t &C2Value::Primitive::ref<c2_cntr64_t>() const { return c64; }
template<> inline const float &C2Value::Primitive::ref<float>() const { return fp; }
template<> constexpr C2Value::type_t C2Value::typeFor<int32_t>() { return INT32; }
template<> constexpr C2Value::type_t C2Value::typeFor<int64_t>() { return INT64; }
template<> constexpr C2Value::type_t C2Value::typeFor<uint32_t>() { return UINT32; }
template<> constexpr C2Value::type_t C2Value::typeFor<uint64_t>() { return UINT64; }
+template<> constexpr C2Value::type_t C2Value::typeFor<c2_cntr32_t>() { return CNTR32; }
+template<> constexpr C2Value::type_t C2Value::typeFor<c2_cntr64_t>() { return CNTR64; }
template<> constexpr C2Value::type_t C2Value::typeFor<float>() { return FLOAT; }
/**
@@ -813,8 +842,10 @@
// primitive types
INT32 = C2Value::INT32, ///< 32-bit signed integer
UINT32 = C2Value::UINT32, ///< 32-bit unsigned integer
+ CNTR32 = C2Value::CNTR32, ///< 32-bit counter
INT64 = C2Value::INT64, ///< 64-bit signed integer
UINT64 = C2Value::UINT64, ///< 64-bit signed integer
+ CNTR64 = C2Value::CNTR64, ///< 64-bit counter
FLOAT = C2Value::FLOAT, ///< 32-bit floating point
// array types
@@ -899,13 +930,15 @@
// 2) this is at parameter granularity.
// type resolution
- inline static type_t getType(int32_t*) { return INT32; }
- inline static type_t getType(uint32_t*) { return UINT32; }
- inline static type_t getType(int64_t*) { return INT64; }
- inline static type_t getType(uint64_t*) { return UINT64; }
- inline static type_t getType(float*) { return FLOAT; }
- inline static type_t getType(char*) { return STRING; }
- inline static type_t getType(uint8_t*) { return BLOB; }
+ inline static type_t getType(int32_t*) { return INT32; }
+ inline static type_t getType(uint32_t*) { return UINT32; }
+ inline static type_t getType(c2_cntr32_t*) { return CNTR32; }
+ inline static type_t getType(int64_t*) { return INT64; }
+ inline static type_t getType(uint64_t*) { return UINT64; }
+ inline static type_t getType(c2_cntr64_t*) { return CNTR64; }
+ inline static type_t getType(float*) { return FLOAT; }
+ inline static type_t getType(char*) { return STRING; }
+ inline static type_t getType(uint8_t*) { return BLOB; }
template<typename T,
class=typename std::enable_if<std::is_enum<T>::value>::type>
@@ -932,8 +965,10 @@
// non-enumerated integral types.
DEFINE_NO_NAMED_VALUES_FOR(int32_t)
DEFINE_NO_NAMED_VALUES_FOR(uint32_t)
+DEFINE_NO_NAMED_VALUES_FOR(c2_cntr32_t)
DEFINE_NO_NAMED_VALUES_FOR(int64_t)
DEFINE_NO_NAMED_VALUES_FOR(uint64_t)
+DEFINE_NO_NAMED_VALUES_FOR(c2_cntr64_t)
DEFINE_NO_NAMED_VALUES_FOR(uint8_t)
DEFINE_NO_NAMED_VALUES_FOR(char)
DEFINE_NO_NAMED_VALUES_FOR(float)
diff --git a/media/libstagefright/codec2/include/C2Work.h b/media/libstagefright/codec2/include/C2Work.h
index 105cf81..58a9174 100644
--- a/media/libstagefright/codec2/include/C2Work.h
+++ b/media/libstagefright/codec2/include/C2Work.h
@@ -83,32 +83,64 @@
kParamIndexWorkOrdinal,
};
+/**
+ * Information for ordering work items on a component port.
+ */
struct C2WorkOrdinalStruct {
- uint64_t timestamp;
- uint64_t frame_index; // submission ordinal on the initial component
- uint64_t custom_ordinal; // can be given by the component, e.g. decode order
+//public:
+ c2_cntr64_t timestamp; /** frame timestamp in microseconds */
+ c2_cntr64_t frameIndex; /** submission ordinal on the initial component */
+ c2_cntr64_t customOrdinal; /** can be given by the component, e.g. decode order */
DEFINE_AND_DESCRIBE_C2STRUCT(WorkOrdinal)
C2FIELD(timestamp, "timestamp")
- C2FIELD(frame_index, "frame-index")
- C2FIELD(custom_ordinal, "custom-ordinal")
+ C2FIELD(frameIndex, "frame-index")
+ C2FIELD(customOrdinal, "custom-ordinal")
};
-struct C2BufferPack {
+/**
+ * This structure represents a Codec 2.0 frame with its metadata.
+ *
+ * A frame basically consists of an ordered sets of buffers, configuration changes and info buffers
+ * along with some non-configuration metadata.
+ */
+struct C2FrameData {
//public:
enum flags_t : uint32_t {
- FLAG_CODEC_CONFIG = (1 << 0),
- FLAG_DROP_FRAME = (1 << 1),
- FLAG_END_OF_STREAM = (1 << 2),
+ /**
+ * For input frames: no output frame shall be generated when processing this frame, but
+ * metadata shall still be processed.
+ * For output frames: this frame shall be discarded and but metadata is still valid.
+ */
+ FLAG_DROP_FRAME = (1 << 0),
+ /**
+ * This frame is the last frame of the current stream. Further frames are part of a new
+ * stream.
+ */
+ FLAG_END_OF_STREAM = (1 << 1),
+ /**
+ * This frame shall be discarded with its metadata.
+ * This flag is only set by components - e.g. as a response to the flush command.
+ */
+ FLAG_DISCARD_FRAME = (1 << 2),
+ /**
+ * This frame contains only codec-specific configuration data, and no actual access unit.
+ *
+ * \deprecated pass codec configuration with using the \todo codec-specific configuration
+ * info together with the access unit.
+ */
+ FLAG_CODEC_CONFIG = (1u << 31),
};
+ /**
+ * Frame flags */
flags_t flags;
C2WorkOrdinalStruct ordinal;
std::vector<std::shared_ptr<C2Buffer>> buffers;
//< for initial work item, these may also come from the parser - if provided
//< for output buffers, these are the responses to requestedInfos
- std::list<std::unique_ptr<C2Info>> infos;
- std::list<std::shared_ptr<C2InfoBuffer>> infoBuffers;
+ std::vector<std::unique_ptr<C2Param>> configUpdate;
+ std::vector<std::shared_ptr<C2InfoBuffer>> infoBuffers;
};
struct C2Worklet {
@@ -116,59 +148,61 @@
// IN
c2_node_id_t component;
- std::list<std::unique_ptr<C2Param>> tunings; //< tunings to be applied before processing this
- // worklet
- std::list<C2Param::Type> requestedInfos;
- std::vector<std::shared_ptr<C2BlockPool>> allocators; //< This vector shall be the same size as
- //< output.buffers. \deprecated
+ /** Configuration changes to be applied before processing this worklet. */
+ std::vector<std::unique_ptr<C2Tuning>> tunings;
+ std::vector<std::unique_ptr<C2SettingResult>> failures;
// OUT
- C2BufferPack output;
- std::list<std::unique_ptr<C2SettingResult>> failures;
+ C2FrameData output;
};
/**
+ * Information about partial work-chains not part of the current work items.
+ *
+ * To be defined later.
+ */
+struct C2WorkChainInfo;
+
+/**
* This structure holds information about all a single work item.
*
* This structure shall be passed by the client to the component for the first worklet. As such,
* worklets must not be empty. The ownership of this object is passed.
- *
- * input:
- * The input data to be processed. This is provided by the client with ownership. When the work
- * is returned, the input buffer-pack's buffer vector shall contain nullptrs.
- *
- * worklets:
- * The chain of components and associated allocators, tunings and info requests that the data
- * must pass through. If this has more than a single element, the tunnels between successive
- * components of the worklet chain must have been (successfully) pre-registered at the time
- * the work is submitted. Allocating the output buffers in the worklets is the responsibility
- * of each component. Upon work submission, each output buffer-pack shall be an appropriately
- * sized vector containing nullptrs. When the work is completed/returned to the client,
- *
- * worklets_processed:
- * It shall be initialized to 0 by the client when the work is submitted.
- * It shall contain the number of worklets that were successfully processed when the work is
- * returned. If this is less then the number of worklets, result must not be success.
- * It must be in the range of [0, worklets.size()].
- *
- * result:
- * The final outcome of the work. If 0 when work is returned, it is assumed that all worklets
- * have been processed.
*/
struct C2Work {
//public:
- // pre-chain infos (for portions of a tunneling chain that happend before this work-chain for
- // this work item - due to framework facilitated (non-tunneled) work-chaining)
- std::list<std::pair<std::unique_ptr<C2PortMimeConfig>, std::unique_ptr<C2Info>>> preChainInfos;
- std::list<std::pair<std::unique_ptr<C2PortMimeConfig>, std::unique_ptr<C2Buffer>>> preChainInfoBlobs;
+ /// additional work chain info not part of this work
+ std::shared_ptr<C2WorkChainInfo> chainInfo;
- C2BufferPack input;
+ /// The input data to be processed as part of this work/work-chain. This is provided by the
+ /// client with ownership. When the work is returned (via onWorkDone), the input buffer-pack's
+ /// buffer vector shall contain nullptrs.
+ C2FrameData input;
+
+ /// The chain of components, tunings (including output buffer pool IDs) and info requests that the
+ /// data must pass through. If this has more than a single element, the tunnels between successive
+ /// components of the worklet chain must have been (successfully) pre-registered at the time that
+ /// the work is submitted. Allocating the output buffers in the worklets is the responsibility of
+ /// each component. Upon work submission, each output buffer-pack shall be an appropriately sized
+ /// vector containing nullptrs. When the work is completed/returned to the client, output buffers
+ /// pointers from all but the final worklet shall be nullptrs.
std::list<std::unique_ptr<C2Worklet>> worklets;
- uint32_t worklets_processed;
+ /// Number of worklets successfully processed in this chain. This shall be initialized to 0 by the
+ /// client when the work is submitted. It shall contain the number of worklets that were
+ /// successfully processed when the work is returned to the client. If this is less then the number
+ /// of worklets, result must not be success. It must be in the range of [0, worklets.size()].
+ uint32_t workletsProcessed;
+
+ /// The final outcome of the work (corresponding to the current workletsProcessed). If 0 when
+ /// work is returned, it is assumed that all worklets have been processed.
c2_status_t result;
};
+/**
+ * Information about a future work to be submitted to the component. The information is used to
+ * reserve the work for work ordering purposes.
+ */
struct C2WorkOutline {
//public:
C2WorkOrdinalStruct ordinal;
diff --git a/media/libstagefright/codec2/include/SimpleC2Interface.h b/media/libstagefright/codec2/include/SimpleC2Interface.h
index 3796b0b..b934f12 100644
--- a/media/libstagefright/codec2/include/SimpleC2Interface.h
+++ b/media/libstagefright/codec2/include/SimpleC2Interface.h
@@ -59,12 +59,12 @@
inline C2String getName() const override { return mName; }
inline c2_node_id_t getId() const override { return mId; }
c2_status_t query_vb(
- const std::vector<C2Param* const> &stackParams,
+ const std::vector<C2Param*> &stackParams,
const std::vector<C2Param::Index> &heapParamIndices,
c2_blocking_t mayBlock,
std::vector<std::unique_ptr<C2Param>>* const heapParams) const override;
inline c2_status_t config_vb(
- const std::vector<C2Param* const> &,
+ const std::vector<C2Param*> &,
c2_blocking_t,
std::vector<std::unique_ptr<C2SettingResult>>* const) override {
return C2_OMITTED;
diff --git a/media/libstagefright/codec2/tests/C2ComponentInterface_test.cpp b/media/libstagefright/codec2/tests/C2ComponentInterface_test.cpp
index 339f927..f50af81 100644
--- a/media/libstagefright/codec2/tests/C2ComponentInterface_test.cpp
+++ b/media/libstagefright/codec2/tests/C2ComponentInterface_test.cpp
@@ -137,7 +137,7 @@
template <typename T> void queryUnsupportedParam();
// Execute an interface's config_vb(). |T| is a single parameter type, not std::vector.
- // config() creates std::vector<C2Param *const> {p} and passes it to config_vb().
+ // config() creates std::vector<C2Param *> {p} and passes it to config_vb().
template <typename T>
c2_status_t
config(T *const p,
@@ -195,7 +195,7 @@
} while (false)
template <typename T> c2_status_t C2CompIntfTest::queryOnStack(T *const p) {
- std::vector<C2Param *const> stackParams{p};
+ std::vector<C2Param*> stackParams{p};
return mIntf->query_vb(stackParams, {}, C2_DONT_BLOCK, nullptr);
}
@@ -260,7 +260,7 @@
template <typename T>
c2_status_t C2CompIntfTest::config(
T *const p, std::vector<std::unique_ptr<C2SettingResult>> *const failures) {
- std::vector<C2Param *const> params{p};
+ std::vector<C2Param*> params{p};
return mIntf->config_vb(params, C2_DONT_BLOCK, failures);
}
@@ -276,7 +276,7 @@
void C2CompIntfTest::configReadOnlyParam(const T &newParam) {
std::unique_ptr<T> p = makeParamFrom(newParam);
- std::vector<C2Param *const> params{p.get()};
+ std::vector<C2Param*> params{p.get()};
std::vector<std::unique_ptr<C2SettingResult>> failures;
// config_vb should be failed because a parameter is read-only.
@@ -289,7 +289,7 @@
void C2CompIntfTest::configWritableParamValidValue(const T &newParam, c2_status_t *configResult) {
std::unique_ptr<T> p = makeParamFrom(newParam);
- std::vector<C2Param *const> params{p.get()};
+ std::vector<C2Param*> params{p.get()};
std::vector<std::unique_ptr<C2SettingResult>> failures;
// In most cases, config_vb return C2_OK and the parameter's value should be changed
// to |newParam|, which is confirmed in a caller of configWritableParamValueValue().
@@ -312,7 +312,7 @@
void C2CompIntfTest::configWritableParamInvalidValue(const T &newParam) {
std::unique_ptr<T> p = makeParamFrom(newParam);
- std::vector<C2Param *const> params{p.get()};
+ std::vector<C2Param*> params{p.get()};
std::vector<std::unique_ptr<C2SettingResult>> failures;
// Although a parameter is writable, config_vb should be failed,
// because a new value is invalid.
diff --git a/media/libstagefright/codec2/tests/C2Param_test.cpp b/media/libstagefright/codec2/tests/C2Param_test.cpp
index 8ebc584..d186292 100644
--- a/media/libstagefright/codec2/tests/C2Param_test.cpp
+++ b/media/libstagefright/codec2/tests/C2Param_test.cpp
@@ -2450,7 +2450,7 @@
}
virtual c2_status_t config_vb(
- const std::vector<C2Param* const> ¶ms,
+ const std::vector<C2Param*> ¶ms,
c2_blocking_t mayBlock,
std::vector<std::unique_ptr<C2SettingResult>>* const failures) override {
(void)params;
@@ -2465,7 +2465,7 @@
}
virtual c2_status_t query_vb(
- const std::vector<C2Param* const> &stackParams,
+ const std::vector<C2Param*> &stackParams,
const std::vector<C2Param::Index> &heapParamIndices,
c2_blocking_t mayBlock,
std::vector<std::unique_ptr<C2Param>>* const heapParams) const override {
diff --git a/media/libstagefright/codec2/tests/C2_test.cpp b/media/libstagefright/codec2/tests/C2_test.cpp
index 92a3d91..46f545f 100644
--- a/media/libstagefright/codec2/tests/C2_test.cpp
+++ b/media/libstagefright/codec2/tests/C2_test.cpp
@@ -75,4 +75,88 @@
static_assert(c2_const_checker<max_u32_u64>::num() == 3, "should be 3");
static_assert(c2_const_checker<max_u32_u8>::num() == 0x7fffffff, "should be 0x7fffffff");
+/* ======================================= COUNTER TESTS ======================================= */
+
+void c2_cntr_static_test() {
+ // sanity checks for construction/assignment
+ constexpr c2_cntr32_t c32_a(123);
+ constexpr c2_cntr64_t c64_a(-456);
+ c2_cntr32_t c32_b __unused = c64_a;
+ // c32_b = 64.; // DISALLOWED
+ // c2_cntr64_t c64_b = c32_a; // DISALLOWED
+
+ // sanity checks for some constexpr operators
+ static_assert(std::is_same<decltype(c32_a + c64_a), decltype(c64_a + c32_a)>::value, "+ should result same type");
+ static_assert(c32_a + c64_a == c2_cntr32_t(-333), "123 + -456 = -333");
+ static_assert(c32_a + c32_a == c2_cntr32_t(246), "123 + 123 = 246");
+ static_assert(c64_a + c32_a == c2_cntr32_t(-333), "-456 + 123 = 579");
+ static_assert(std::is_same<decltype(c32_a + 1), decltype(1 + c32_a)>::value, "+ should result same type");
+ static_assert(c32_a + 456 == c2_cntr32_t(579), "123 + 456 = 579");
+ static_assert(456 + c64_a == c2_cntr64_t(0), "456 + -456 = 0");
+ static_assert(std::is_same<decltype(c32_a - c64_a), decltype(c64_a - c32_a)>::value, "- should result same type");
+ static_assert(c32_a - c64_a == c2_cntr32_t(579), "123 - -456 = 579");
+ static_assert(c32_a - c32_a == c2_cntr32_t(0), "123 - 123 = 0");
+ static_assert(c64_a - c32_a == c2_cntr32_t(-579), "-456 - 123 = -579");
+ static_assert(std::is_same<decltype(c32_a - 1), decltype(1 - c32_a)>::value, "- should result same type");
+ static_assert(c32_a - 456 == c2_cntr32_t(-333), "123 - 456 = -333");
+ static_assert(456 - c64_a == c2_cntr64_t(912), "456 - -456 = 912");
+ static_assert(std::is_same<decltype(c32_a * c64_a), decltype(c64_a * c32_a)>::value, "* should result same type");
+ static_assert(c32_a * c64_a == c2_cntr32_t(-56088), "123 * -456 = -56088");
+ static_assert(c32_a * c32_a == c2_cntr32_t(15129), "123 * 123 = 15129");
+ static_assert(c64_a * c32_a == c2_cntr32_t(-56088), "-456 * 123 = -56088");
+ static_assert(std::is_same<decltype(c32_a * 1), decltype(1 * c32_a)>::value, "* should result same type");
+ static_assert(c32_a * 456 == c2_cntr32_t(56088), "123 * 456 = 56088");
+ static_assert(456 * c64_a == c2_cntr64_t(-207936), "456 * -456 = -207936");
+
+ static_assert((c32_a << 26u) == c2_cntr32_t(0xEC000000), "123 << 26 = 0xEC000000");
+
+ // sanity checks for unary operators
+ static_assert(c2_cntr32_t(1) == +c2_cntr32_t(1), "1 == +1");
+ static_assert(c2_cntr32_t(1) == -c2_cntr32_t(-1), "1 == --1");
+
+ // sanity checks for comparison
+ using c8_t = c2_cntr_t<uint8_t>;
+ static_assert(c8_t(-0x80) > c8_t(0x7f), "80 > 7F");
+ static_assert(c8_t(-0x80) >= c8_t(0x7f), "80 >= 7F");
+ static_assert(c8_t(0x7f) > c8_t(0x7e), "7F > 7E");
+ static_assert(c8_t(0x7f) >= c8_t(0x7e), "7F >= 7E");
+ static_assert(!(c8_t(-0x80) > c8_t(0)), "80 !> 00");
+ static_assert(!(c8_t(-0x80) >= c8_t(0)), "80 !>= 00");
+ static_assert(!(c8_t(-0x80) > c8_t(-0x80)), "80 !> 80");
+ static_assert(c8_t(-0x80) >= c8_t(-0x80), "80 >= 80");
+
+ static_assert(c8_t(-0x80) == c8_t(0x80), "80 == 80");
+ static_assert(!(c8_t(-0x80) == c8_t(0)), "80 != 0");
+ static_assert(c8_t(-0x80) != c8_t(0x7f), "80 != 7F");
+ static_assert(!(c8_t(0x7f) != c8_t(0x7f)), "80 != 7F");
+
+ static_assert(c8_t(0x7f) < c8_t(-0x80), "7F < 80");
+ static_assert(c8_t(0x7f) <= c8_t(-0x80), "7F < 80");
+ static_assert(c8_t(0x7e) < c8_t(0x7f), "7E < 7F");
+ static_assert(c8_t(0x7e) <= c8_t(0x7f), "7E < 7F");
+ static_assert(!(c8_t(-0x40) < c8_t(0x40)), "-40 !< 40");
+ static_assert(!(c8_t(-0x40) <= c8_t(0x40)), "-40 !<= 40");
+ static_assert(!(c8_t(-0x40) < c8_t(-0x40)), "-40 !< -40");
+ static_assert(c8_t(-0x40) <= c8_t(-0x40), "-40 <= -40");
+
+ static_assert(c2_cntr32_t(-0x7fffffff - 1) > c2_cntr32_t(0x7fffffff), "80 > 7F");
+ static_assert(!(c2_cntr32_t(-0x7fffffff - 1) > c2_cntr32_t(0)), "80 !> 00");
+ static_assert(c2_cntr32_t(1) == c2_cntr32_t(c2_cntr64_t(0x100000001ul)), "1 == 1");
+}
+
+class C2Test : public ::testing::Test {
+};
+
+TEST_F(C2Test, CounterTest) {
+ c2_cntr32_t c32_a(123);
+ c2_cntr64_t c64_a(-456);
+ EXPECT_EQ(c32_a += 3, c2_cntr32_t(126));
+ EXPECT_EQ(c32_a += c64_a, c2_cntr32_t(-330));
+ EXPECT_EQ(c32_a <<= 2u, c2_cntr32_t(-1320));
+ EXPECT_EQ(c64_a *= 3, c2_cntr64_t(-1368));
+ EXPECT_EQ(c32_a -= c64_a, c2_cntr32_t(48));
+ EXPECT_EQ(c32_a -= 40, c2_cntr32_t(8));
+ EXPECT_EQ(c32_a *= c32_a, c2_cntr32_t(64));
+}
+
} // namespace android
diff --git a/media/libstagefright/codec2/tests/vndk/C2BufferTest.cpp b/media/libstagefright/codec2/tests/vndk/C2BufferTest.cpp
index f6e6478..a310717 100644
--- a/media/libstagefright/codec2/tests/vndk/C2BufferTest.cpp
+++ b/media/libstagefright/codec2/tests/vndk/C2BufferTest.cpp
@@ -359,14 +359,14 @@
class BufferData : public C2BufferData {
public:
- explicit BufferData(const std::list<C2ConstLinearBlock> &blocks) : C2BufferData(blocks) {}
- explicit BufferData(const std::list<C2ConstGraphicBlock> &blocks) : C2BufferData(blocks) {}
+ explicit BufferData(const std::vector<C2ConstLinearBlock> &blocks) : C2BufferData(blocks) {}
+ explicit BufferData(const std::vector<C2ConstGraphicBlock> &blocks) : C2BufferData(blocks) {}
};
class Buffer : public C2Buffer {
public:
- explicit Buffer(const std::list<C2ConstLinearBlock> &blocks) : C2Buffer(blocks) {}
- explicit Buffer(const std::list<C2ConstGraphicBlock> &blocks) : C2Buffer(blocks) {}
+ explicit Buffer(const std::vector<C2ConstLinearBlock> &blocks) : C2Buffer(blocks) {}
+ explicit Buffer(const std::vector<C2ConstGraphicBlock> &blocks) : C2Buffer(blocks) {}
};
TEST_F(C2BufferTest, BufferDataTest) {
@@ -487,45 +487,45 @@
std::shared_ptr<C2Info> info1(new C2Number1Info(1));
std::shared_ptr<C2Info> info2(new C2Number2Info(2));
buffer.reset(new Buffer( { block->share(0, kCapacity, C2Fence()) }));
- EXPECT_TRUE(buffer->infos().empty());
+ EXPECT_TRUE(buffer->info().empty());
EXPECT_FALSE(buffer->hasInfo(info1->type()));
EXPECT_FALSE(buffer->hasInfo(info2->type()));
ASSERT_EQ(C2_OK, buffer->setInfo(info1));
- EXPECT_EQ(1u, buffer->infos().size());
- EXPECT_EQ(*info1, *buffer->infos().front());
+ EXPECT_EQ(1u, buffer->info().size());
+ EXPECT_EQ(*info1, *buffer->info().front());
EXPECT_TRUE(buffer->hasInfo(info1->type()));
EXPECT_FALSE(buffer->hasInfo(info2->type()));
ASSERT_EQ(C2_OK, buffer->setInfo(info2));
- EXPECT_EQ(2u, buffer->infos().size());
+ EXPECT_EQ(2u, buffer->info().size());
EXPECT_TRUE(buffer->hasInfo(info1->type()));
EXPECT_TRUE(buffer->hasInfo(info2->type()));
std::shared_ptr<C2Info> removed = buffer->removeInfo(info1->type());
ASSERT_TRUE(removed);
EXPECT_EQ(*removed, *info1);
- EXPECT_EQ(1u, buffer->infos().size());
- EXPECT_EQ(*info2, *buffer->infos().front());
+ EXPECT_EQ(1u, buffer->info().size());
+ EXPECT_EQ(*info2, *buffer->info().front());
EXPECT_FALSE(buffer->hasInfo(info1->type()));
EXPECT_TRUE(buffer->hasInfo(info2->type()));
removed = buffer->removeInfo(info1->type());
ASSERT_FALSE(removed);
- EXPECT_EQ(1u, buffer->infos().size());
+ EXPECT_EQ(1u, buffer->info().size());
EXPECT_FALSE(buffer->hasInfo(info1->type()));
EXPECT_TRUE(buffer->hasInfo(info2->type()));
std::shared_ptr<C2Info> info3(new C2Number2Info(3));
ASSERT_EQ(C2_OK, buffer->setInfo(info3));
- EXPECT_EQ(1u, buffer->infos().size());
+ EXPECT_EQ(1u, buffer->info().size());
EXPECT_FALSE(buffer->hasInfo(info1->type()));
EXPECT_TRUE(buffer->hasInfo(info2->type()));
removed = buffer->removeInfo(info2->type());
ASSERT_TRUE(removed);
EXPECT_EQ(*info3, *removed);
- EXPECT_TRUE(buffer->infos().empty());
+ EXPECT_TRUE(buffer->info().empty());
EXPECT_FALSE(buffer->hasInfo(info1->type()));
EXPECT_FALSE(buffer->hasInfo(info2->type()));
}
diff --git a/media/libstagefright/codec2/vndk/C2AllocatorGralloc.cpp b/media/libstagefright/codec2/vndk/C2AllocatorGralloc.cpp
index 18db3e9..a5ea511 100644
--- a/media/libstagefright/codec2/vndk/C2AllocatorGralloc.cpp
+++ b/media/libstagefright/codec2/vndk/C2AllocatorGralloc.cpp
@@ -38,6 +38,15 @@
using ::android::hardware::hidl_handle;
using ::android::hardware::hidl_vec;
+namespace {
+
+struct BufferDescriptorInfo {
+ IMapper::BufferDescriptorInfo mapperInfo;
+ uint32_t stride;
+};
+
+}
+
/* ===================================== GRALLOC ALLOCATION ==================================== */
static c2_status_t maperr2error(Error maperr) {
switch (maperr) {
@@ -73,6 +82,7 @@
uint32_t format;
uint32_t usage_lo;
uint32_t usage_hi;
+ uint32_t stride;
uint32_t magic;
};
@@ -109,13 +119,16 @@
static C2HandleGralloc* WrapNativeHandle(
const native_handle_t *const handle,
- uint32_t width, uint32_t height, uint32_t format, uint64_t usage) {
+ uint32_t width, uint32_t height, uint32_t format, uint64_t usage, uint32_t stride) {
//CHECK(handle != nullptr);
if (native_handle_is_invalid(handle) ||
handle->numInts > int((INT_MAX - handle->version) / sizeof(int)) - NUM_INTS - handle->numFds) {
return nullptr;
}
- ExtraData xd = { width, height, format, uint32_t(usage & 0xFFFFFFFF), uint32_t(usage >> 32), MAGIC };
+ ExtraData xd = {
+ width, height, format, uint32_t(usage & 0xFFFFFFFF), uint32_t(usage >> 32),
+ stride, MAGIC
+ };
native_handle_t *res = native_handle_create(handle->numFds, handle->numInts + NUM_INTS);
if (res != nullptr) {
memcpy(&res->data, &handle->data, sizeof(int) * (handle->numFds + handle->numInts));
@@ -138,7 +151,8 @@
static const C2HandleGralloc* Import(
const C2Handle *const handle,
- uint32_t *width, uint32_t *height, uint32_t *format, uint64_t *usage) {
+ uint32_t *width, uint32_t *height, uint32_t *format,
+ uint64_t *usage, uint32_t *stride) {
const ExtraData *xd = getExtraData(handle);
if (xd == nullptr) {
return nullptr;
@@ -147,15 +161,22 @@
*height = xd->height;
*format = xd->format;
*usage = xd->usage_lo | (uint64_t(xd->usage_hi) << 32);
+ *stride = xd->stride;
return reinterpret_cast<const C2HandleGralloc *>(handle);
}
};
-native_handle_t* UnwrapNativeCodec2GrallocHandle(const C2Handle *const handle) {
+native_handle_t *UnwrapNativeCodec2GrallocHandle(const C2Handle *const handle) {
return C2HandleGralloc::UnwrapNativeHandle(handle);
}
+C2Handle *WrapNativeCodec2GrallocHandle(
+ const native_handle_t *const handle,
+ uint32_t width, uint32_t height, uint32_t format, uint64_t usage, uint32_t stride) {
+ return C2HandleGralloc::WrapNativeHandle(handle, width, height, format, usage, stride);
+}
+
class C2AllocationGralloc : public C2GraphicAllocation {
public:
virtual ~C2AllocationGralloc() override;
@@ -171,7 +192,7 @@
// internal methods
// |handle| will be moved.
C2AllocationGralloc(
- const IMapper::BufferDescriptorInfo &info,
+ const BufferDescriptorInfo &info,
const sp<IMapper> &mapper,
hidl_handle &hidlHandle,
const C2HandleGralloc *const handle);
@@ -179,7 +200,7 @@
c2_status_t status() const;
private:
- const IMapper::BufferDescriptorInfo mInfo;
+ const BufferDescriptorInfo mInfo;
const sp<IMapper> mMapper;
const hidl_handle mHidlHandle;
const C2HandleGralloc *mHandle;
@@ -189,11 +210,11 @@
};
C2AllocationGralloc::C2AllocationGralloc(
- const IMapper::BufferDescriptorInfo &info,
+ const BufferDescriptorInfo &info,
const sp<IMapper> &mapper,
hidl_handle &hidlHandle,
const C2HandleGralloc *const handle)
- : C2GraphicAllocation(info.width, info.height),
+ : C2GraphicAllocation(info.mapperInfo.width, info.mapperInfo.height),
mInfo(info),
mMapper(mapper),
mHidlHandle(std::move(hidlHandle)),
@@ -241,83 +262,133 @@
return C2_CORRUPTED;
}
mLockedHandle = C2HandleGralloc::WrapNativeHandle(
- mBuffer, mInfo.width, mInfo.height, (uint32_t)mInfo.format, mInfo.usage);
+ mBuffer, mInfo.mapperInfo.width, mInfo.mapperInfo.height,
+ (uint32_t)mInfo.mapperInfo.format, mInfo.mapperInfo.usage, mInfo.stride);
}
- if (mInfo.format == PixelFormat::YCBCR_420_888 || mInfo.format == PixelFormat::YV12) {
- YCbCrLayout ycbcrLayout;
- mMapper->lockYCbCr(
- const_cast<native_handle_t *>(mBuffer),
- BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN,
- { (int32_t)rect.left, (int32_t)rect.top, (int32_t)rect.width, (int32_t)rect.height },
- // TODO: fence
- hidl_handle(),
- [&err, &ycbcrLayout](const auto &maperr, const auto &mapLayout) {
- err = maperr2error(maperr);
- if (err == C2_OK) {
- ycbcrLayout = mapLayout;
- }
- });
- if (err != C2_OK) {
- return err;
+ switch (mInfo.mapperInfo.format) {
+ case PixelFormat::YCBCR_420_888:
+ case PixelFormat::YV12: {
+ YCbCrLayout ycbcrLayout;
+ mMapper->lockYCbCr(
+ const_cast<native_handle_t *>(mBuffer),
+ BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN,
+ { (int32_t)rect.left, (int32_t)rect.top, (int32_t)rect.width, (int32_t)rect.height },
+ // TODO: fence
+ hidl_handle(),
+ [&err, &ycbcrLayout](const auto &maperr, const auto &mapLayout) {
+ err = maperr2error(maperr);
+ if (err == C2_OK) {
+ ycbcrLayout = mapLayout;
+ }
+ });
+ if (err != C2_OK) {
+ return err;
+ }
+ addr[C2PlanarLayout::PLANE_Y] = (uint8_t *)ycbcrLayout.y;
+ addr[C2PlanarLayout::PLANE_U] = (uint8_t *)ycbcrLayout.cb;
+ addr[C2PlanarLayout::PLANE_V] = (uint8_t *)ycbcrLayout.cr;
+ layout->type = C2PlanarLayout::TYPE_YUV;
+ layout->numPlanes = 3;
+ layout->planes[C2PlanarLayout::PLANE_Y] = {
+ C2PlaneInfo::CHANNEL_Y, // channel
+ 1, // colInc
+ (int32_t)ycbcrLayout.yStride, // rowInc
+ 1, // mColSampling
+ 1, // mRowSampling
+ 8, // allocatedDepth
+ 8, // bitDepth
+ 0, // rightShift
+ C2PlaneInfo::NATIVE, // endianness
+ };
+ layout->planes[C2PlanarLayout::PLANE_U] = {
+ C2PlaneInfo::CHANNEL_CB, // channel
+ (int32_t)ycbcrLayout.chromaStep, // colInc
+ (int32_t)ycbcrLayout.cStride, // rowInc
+ 2, // mColSampling
+ 2, // mRowSampling
+ 8, // allocatedDepth
+ 8, // bitDepth
+ 0, // rightShift
+ C2PlaneInfo::NATIVE, // endianness
+ };
+ layout->planes[C2PlanarLayout::PLANE_V] = {
+ C2PlaneInfo::CHANNEL_CR, // channel
+ (int32_t)ycbcrLayout.chromaStep, // colInc
+ (int32_t)ycbcrLayout.cStride, // rowInc
+ 2, // mColSampling
+ 2, // mRowSampling
+ 8, // allocatedDepth
+ 8, // bitDepth
+ 0, // rightShift
+ C2PlaneInfo::NATIVE, // endianness
+ };
+ break;
}
- addr[C2PlanarLayout::PLANE_Y] = (uint8_t *)ycbcrLayout.y;
- addr[C2PlanarLayout::PLANE_U] = (uint8_t *)ycbcrLayout.cb;
- addr[C2PlanarLayout::PLANE_V] = (uint8_t *)ycbcrLayout.cr;
- layout->type = C2PlanarLayout::TYPE_YUV;
- layout->numPlanes = 3;
- layout->planes[C2PlanarLayout::PLANE_Y] = {
- C2PlaneInfo::CHANNEL_Y, // channel
- 1, // colInc
- (int32_t)ycbcrLayout.yStride, // rowInc
- 1, // mColSampling
- 1, // mRowSampling
- 8, // allocatedDepth
- 8, // bitDepth
- 0, // rightShift
- C2PlaneInfo::NATIVE, // endianness
- };
- layout->planes[C2PlanarLayout::PLANE_U] = {
- C2PlaneInfo::CHANNEL_CB, // channel
- (int32_t)ycbcrLayout.chromaStep, // colInc
- (int32_t)ycbcrLayout.cStride, // rowInc
- 2, // mColSampling
- 2, // mRowSampling
- 8, // allocatedDepth
- 8, // bitDepth
- 0, // rightShift
- C2PlaneInfo::NATIVE, // endianness
- };
- layout->planes[C2PlanarLayout::PLANE_V] = {
- C2PlaneInfo::CHANNEL_CR, // channel
- (int32_t)ycbcrLayout.chromaStep, // colInc
- (int32_t)ycbcrLayout.cStride, // rowInc
- 2, // mColSampling
- 2, // mRowSampling
- 8, // allocatedDepth
- 8, // bitDepth
- 0, // rightShift
- C2PlaneInfo::NATIVE, // endianness
- };
- } else {
- void *pointer = nullptr;
- mMapper->lock(
- const_cast<native_handle_t *>(mBuffer),
- BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN,
- { (int32_t)rect.left, (int32_t)rect.top, (int32_t)rect.width, (int32_t)rect.height },
- // TODO: fence
- hidl_handle(),
- [&err, &pointer](const auto &maperr, const auto &mapPointer) {
- err = maperr2error(maperr);
- if (err == C2_OK) {
- pointer = mapPointer;
- }
- });
- if (err != C2_OK) {
- return err;
+
+ case PixelFormat::RGBA_8888:
+ // TODO: alpha channel
+ // fall-through
+ case PixelFormat::RGBX_8888: {
+ void *pointer = nullptr;
+ mMapper->lock(
+ const_cast<native_handle_t *>(mBuffer),
+ BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN,
+ { (int32_t)rect.left, (int32_t)rect.top, (int32_t)rect.width, (int32_t)rect.height },
+ // TODO: fence
+ hidl_handle(),
+ [&err, &pointer](const auto &maperr, const auto &mapPointer) {
+ err = maperr2error(maperr);
+ if (err == C2_OK) {
+ pointer = mapPointer;
+ }
+ });
+ if (err != C2_OK) {
+ return err;
+ }
+ addr[C2PlanarLayout::PLANE_R] = (uint8_t *)pointer;
+ addr[C2PlanarLayout::PLANE_G] = (uint8_t *)pointer + 1;
+ addr[C2PlanarLayout::PLANE_B] = (uint8_t *)pointer + 2;
+ layout->type = C2PlanarLayout::TYPE_RGB;
+ layout->numPlanes = 3;
+ layout->planes[C2PlanarLayout::PLANE_R] = {
+ C2PlaneInfo::CHANNEL_R, // channel
+ 4, // colInc
+ 4 * (int32_t)mInfo.stride, // rowInc
+ 1, // mColSampling
+ 1, // mRowSampling
+ 8, // allocatedDepth
+ 8, // bitDepth
+ 0, // rightShift
+ C2PlaneInfo::NATIVE, // endianness
+ };
+ layout->planes[C2PlanarLayout::PLANE_G] = {
+ C2PlaneInfo::CHANNEL_G, // channel
+ 4, // colInc
+ 4 * (int32_t)mInfo.stride, // rowInc
+ 1, // mColSampling
+ 1, // mRowSampling
+ 8, // allocatedDepth
+ 8, // bitDepth
+ 0, // rightShift
+ C2PlaneInfo::NATIVE, // endianness
+ };
+ layout->planes[C2PlanarLayout::PLANE_B] = {
+ C2PlaneInfo::CHANNEL_B, // channel
+ 4, // colInc
+ 4 * (int32_t)mInfo.stride, // rowInc
+ 1, // mColSampling
+ 1, // mRowSampling
+ 8, // allocatedDepth
+ 8, // bitDepth
+ 0, // rightShift
+ C2PlaneInfo::NATIVE, // endianness
+ };
+ break;
}
- // TODO
- return C2_OMITTED;
+ default: {
+ return C2_OMITTED;
+ }
}
mLocked = true;
@@ -396,17 +467,20 @@
// TODO: buffer usage should be determined according to |usage|
(void) usage;
- IMapper::BufferDescriptorInfo info = {
- width,
- height,
- 1u, // layerCount
- (PixelFormat)format,
- BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN,
+ BufferDescriptorInfo info = {
+ {
+ width,
+ height,
+ 1u, // layerCount
+ (PixelFormat)format,
+ BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN,
+ },
+ 0u, // stride placeholder
};
c2_status_t err = C2_OK;
BufferDescriptor desc;
mMapper->createDescriptor(
- info, [&err, &desc](const auto &maperr, const auto &descriptor) {
+ info.mapperInfo, [&err, &desc](const auto &maperr, const auto &descriptor) {
err = maperr2error(maperr);
if (err == C2_OK) {
desc = descriptor;
@@ -421,8 +495,7 @@
mAllocator->allocate(
desc,
1u,
- [&err, &buffer](const auto &maperr, const auto &stride, auto &buffers) {
- (void) stride;
+ [&err, &buffer, &info](const auto &maperr, const auto &stride, auto &buffers) {
err = maperr2error(maperr);
if (err != C2_OK) {
return;
@@ -431,6 +504,7 @@
err = C2_CORRUPTED;
return;
}
+ info.stride = stride;
buffer = std::move(buffers[0]);
});
if (err != C2_OK) {
@@ -442,18 +516,20 @@
info, mMapper, buffer,
C2HandleGralloc::WrapNativeHandle(
buffer.getNativeHandle(),
- info.width, info.height, (uint32_t)info.format, info.usage)));
+ info.mapperInfo.width, info.mapperInfo.height,
+ (uint32_t)info.mapperInfo.format, info.mapperInfo.usage, info.stride)));
return C2_OK;
}
c2_status_t C2AllocatorGralloc::Impl::priorGraphicAllocation(
const C2Handle *handle,
std::shared_ptr<C2GraphicAllocation> *allocation) {
- IMapper::BufferDescriptorInfo info;
- info.layerCount = 1u;
+ BufferDescriptorInfo info;
+ info.mapperInfo.layerCount = 1u;
const C2HandleGralloc *grallocHandle = C2HandleGralloc::Import(
handle,
- &info.width, &info.height, (uint32_t *)&info.format, (uint64_t *)&info.usage);
+ &info.mapperInfo.width, &info.mapperInfo.height,
+ (uint32_t *)&info.mapperInfo.format, (uint64_t *)&info.mapperInfo.usage, &info.stride);
if (grallocHandle == nullptr) {
return C2_BAD_VALUE;
}
@@ -461,7 +537,7 @@
hidl_handle hidlHandle = C2HandleGralloc::UnwrapNativeHandle(grallocHandle);
allocation->reset(new C2AllocationGralloc(info, mMapper, hidlHandle, grallocHandle));
- return C2_OMITTED;
+ return C2_OK;
}
C2AllocatorGralloc::C2AllocatorGralloc() : mImpl(new Impl) {}
diff --git a/media/libstagefright/codec2/vndk/C2Buffer.cpp b/media/libstagefright/codec2/vndk/C2Buffer.cpp
index 65a271e..4ab3e05 100644
--- a/media/libstagefright/codec2/vndk/C2Buffer.cpp
+++ b/media/libstagefright/codec2/vndk/C2Buffer.cpp
@@ -602,44 +602,44 @@
class C2BufferData::Impl {
public:
- explicit Impl(const std::list<C2ConstLinearBlock> &blocks)
+ explicit Impl(const std::vector<C2ConstLinearBlock> &blocks)
: mType(blocks.size() == 1 ? LINEAR : LINEAR_CHUNKS),
mLinearBlocks(blocks) {
}
- explicit Impl(const std::list<C2ConstGraphicBlock> &blocks)
+ explicit Impl(const std::vector<C2ConstGraphicBlock> &blocks)
: mType(blocks.size() == 1 ? GRAPHIC : GRAPHIC_CHUNKS),
mGraphicBlocks(blocks) {
}
Type type() const { return mType; }
- const std::list<C2ConstLinearBlock> &linearBlocks() const { return mLinearBlocks; }
- const std::list<C2ConstGraphicBlock> &graphicBlocks() const { return mGraphicBlocks; }
+ const std::vector<C2ConstLinearBlock> &linearBlocks() const { return mLinearBlocks; }
+ const std::vector<C2ConstGraphicBlock> &graphicBlocks() const { return mGraphicBlocks; }
private:
Type mType;
- std::list<C2ConstLinearBlock> mLinearBlocks;
- std::list<C2ConstGraphicBlock> mGraphicBlocks;
+ std::vector<C2ConstLinearBlock> mLinearBlocks;
+ std::vector<C2ConstGraphicBlock> mGraphicBlocks;
};
-C2BufferData::C2BufferData(const std::list<C2ConstLinearBlock> &blocks) : mImpl(new Impl(blocks)) {}
-C2BufferData::C2BufferData(const std::list<C2ConstGraphicBlock> &blocks) : mImpl(new Impl(blocks)) {}
+C2BufferData::C2BufferData(const std::vector<C2ConstLinearBlock> &blocks) : mImpl(new Impl(blocks)) {}
+C2BufferData::C2BufferData(const std::vector<C2ConstGraphicBlock> &blocks) : mImpl(new Impl(blocks)) {}
C2BufferData::Type C2BufferData::type() const { return mImpl->type(); }
-const std::list<C2ConstLinearBlock> C2BufferData::linearBlocks() const {
+const std::vector<C2ConstLinearBlock> C2BufferData::linearBlocks() const {
return mImpl->linearBlocks();
}
-const std::list<C2ConstGraphicBlock> C2BufferData::graphicBlocks() const {
+const std::vector<C2ConstGraphicBlock> C2BufferData::graphicBlocks() const {
return mImpl->graphicBlocks();
}
class C2Buffer::Impl {
public:
- Impl(C2Buffer *thiz, const std::list<C2ConstLinearBlock> &blocks)
+ Impl(C2Buffer *thiz, const std::vector<C2ConstLinearBlock> &blocks)
: mThis(thiz), mData(blocks) {}
- Impl(C2Buffer *thiz, const std::list<C2ConstGraphicBlock> &blocks)
+ Impl(C2Buffer *thiz, const std::vector<C2ConstGraphicBlock> &blocks)
: mThis(thiz), mData(blocks) {}
~Impl() {
@@ -676,8 +676,8 @@
return C2_OK;
}
- std::list<std::shared_ptr<const C2Info>> infos() const {
- std::list<std::shared_ptr<const C2Info>> result(mInfos.size());
+ std::vector<std::shared_ptr<const C2Info>> info() const {
+ std::vector<std::shared_ptr<const C2Info>> result(mInfos.size());
std::transform(
mInfos.begin(), mInfos.end(), result.begin(),
[] (const auto &elem) { return elem.second; });
@@ -712,10 +712,10 @@
std::list<std::pair<OnDestroyNotify, void *>> mNotify;
};
-C2Buffer::C2Buffer(const std::list<C2ConstLinearBlock> &blocks)
+C2Buffer::C2Buffer(const std::vector<C2ConstLinearBlock> &blocks)
: mImpl(new Impl(this, blocks)) {}
-C2Buffer::C2Buffer(const std::list<C2ConstGraphicBlock> &blocks)
+C2Buffer::C2Buffer(const std::vector<C2ConstGraphicBlock> &blocks)
: mImpl(new Impl(this, blocks)) {}
const C2BufferData C2Buffer::data() const { return mImpl->data(); }
@@ -728,8 +728,8 @@
return mImpl->unregisterOnDestroyNotify(onDestroyNotify, arg);
}
-const std::list<std::shared_ptr<const C2Info>> C2Buffer::infos() const {
- return mImpl->infos();
+const std::vector<std::shared_ptr<const C2Info>> C2Buffer::info() const {
+ return mImpl->info();
}
c2_status_t C2Buffer::setInfo(const std::shared_ptr<C2Info> &info) {
diff --git a/media/libstagefright/codec2/vndk/C2ParamInternal.h b/media/libstagefright/codec2/vndk/C2ParamInternal.h
new file mode 100644
index 0000000..0f3812a
--- /dev/null
+++ b/media/libstagefright/codec2/vndk/C2ParamInternal.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_STAGEFRIGHT_C2PARAM_INTERNAL_H_
+#define ANDROID_STAGEFRIGHT_C2PARAM_INTERNAL_H_
+
+#include <C2Param.h>
+
+namespace android {
+
+struct C2_HIDE _C2ParamInspector {
+ inline static uint32_t getIndex(const C2ParamField &pf) {
+ return pf._mIndex;
+ }
+
+ inline static uint32_t getOffset(const C2ParamField &pf) {
+ return pf._mFieldId._mOffset;
+ }
+
+ inline static uint32_t getSize(const C2ParamField &pf) {
+ return pf._mFieldId._mSize;
+ }
+
+ inline static
+ C2ParamField CreateParamField(C2Param::Index index, uint32_t offset, uint32_t size) {
+ return C2ParamField(index, offset, size);
+ }
+};
+
+}
+
+#endif // ANDROID_STAGEFRIGHT_C2PARAM_INTERNAL_H_
+
diff --git a/media/libstagefright/codec2/vndk/C2Store.cpp b/media/libstagefright/codec2/vndk/C2Store.cpp
index eb72d17..496cbe1 100644
--- a/media/libstagefright/codec2/vndk/C2Store.cpp
+++ b/media/libstagefright/codec2/vndk/C2Store.cpp
@@ -167,7 +167,7 @@
virtual c2_status_t querySupportedParams_nb(
std::vector<std::shared_ptr<C2ParamDescriptor>> *const params) const override;
virtual c2_status_t query_sm(
- const std::vector<C2Param *const> &stackParams,
+ const std::vector<C2Param*> &stackParams,
const std::vector<C2Param::Index> &heapParamIndices,
std::vector<std::unique_ptr<C2Param>> *const heapParams) const override;
virtual c2_status_t createInterface(
@@ -177,7 +177,7 @@
virtual c2_status_t copyBuffer(
std::shared_ptr<C2GraphicBuffer> src, std::shared_ptr<C2GraphicBuffer> dst) override;
virtual c2_status_t config_sm(
- const std::vector<C2Param *const> ¶ms,
+ const std::vector<C2Param*> ¶ms,
std::vector<std::unique_ptr<C2SettingResult>> *const failures) override;
C2PlatformComponentStore();
@@ -404,7 +404,9 @@
C2PlatformComponentStore::C2PlatformComponentStore() {
// TODO: move this also into a .so so it can be updated
mComponents.emplace("c2.google.avc.decoder", "libstagefright_soft_c2avcdec.so");
+ mComponents.emplace("c2.google.avc.encoder", "libstagefright_soft_c2avcenc.so");
mComponents.emplace("c2.google.aac.decoder", "libstagefright_soft_c2aacdec.so");
+ mComponents.emplace("c2.google.aac.encoder", "libstagefright_soft_c2aacenc.so");
}
c2_status_t C2PlatformComponentStore::copyBuffer(
@@ -415,7 +417,7 @@
}
c2_status_t C2PlatformComponentStore::query_sm(
- const std::vector<C2Param *const> &stackParams,
+ const std::vector<C2Param*> &stackParams,
const std::vector<C2Param::Index> &heapParamIndices,
std::vector<std::unique_ptr<C2Param>> *const heapParams) const {
// there are no supported configs
@@ -424,7 +426,7 @@
}
c2_status_t C2PlatformComponentStore::config_sm(
- const std::vector<C2Param *const> ¶ms,
+ const std::vector<C2Param*> ¶ms,
std::vector<std::unique_ptr<C2SettingResult>> *const failures) {
// there are no supported configs
(void)failures;
diff --git a/media/libstagefright/codec2/vndk/include/C2AllocatorGralloc.h b/media/libstagefright/codec2/vndk/include/C2AllocatorGralloc.h
index 5311747..56fa317 100644
--- a/media/libstagefright/codec2/vndk/include/C2AllocatorGralloc.h
+++ b/media/libstagefright/codec2/vndk/include/C2AllocatorGralloc.h
@@ -1,4 +1,3 @@
-
/*
* Copyright (C) 2016 The Android Open Source Project
*
@@ -32,7 +31,17 @@
*
* @return a new NON-OWNING native handle that must be deleted using native_handle_delete.
*/
-native_handle_t*UnwrapNativeCodec2GrallocHandle(const C2Handle *const handle);
+native_handle_t *UnwrapNativeCodec2GrallocHandle(const C2Handle *const handle);
+
+/**
+ * Wrap the gralloc handle and metadata into Codec2 handle recognized by
+ * C2AllocatorGralloc.
+ *
+ * @return a new NON-OWNING C2Handle that must be deleted using native_handle_delete.
+ */
+C2Handle *WrapNativeCodec2GrallocHandle(
+ const native_handle_t *const handle,
+ uint32_t width, uint32_t height, uint32_t format, uint64_t usage, uint32_t stride);
class C2AllocatorGralloc : public C2Allocator {
public:
diff --git a/media/libstagefright/codec2/vndk/include/util/C2ParamUtils.h b/media/libstagefright/codec2/vndk/include/util/C2ParamUtils.h
index 81c5495..3168248 100644
--- a/media/libstagefright/codec2/vndk/include/util/C2ParamUtils.h
+++ b/media/libstagefright/codec2/vndk/include/util/C2ParamUtils.h
@@ -59,6 +59,7 @@
/// \endcond
+#undef DEFINE_C2_ENUM_VALUE_AUTO_HELPER
#define DEFINE_C2_ENUM_VALUE_AUTO_HELPER(name, type, prefix, ...) \
template<> C2FieldDescriptor::named_values_type C2FieldDescriptor::namedValuesFor(const name &r __unused) { \
return C2ParamUtils::sanitizeEnumValues( \
@@ -67,6 +68,7 @@
prefix); \
}
+#undef DEFINE_C2_ENUM_VALUE_CUSTOM_HELPER
#define DEFINE_C2_ENUM_VALUE_CUSTOM_HELPER(name, type, names, ...) \
template<> C2FieldDescriptor::named_values_type C2FieldDescriptor::namedValuesFor(const name &r __unused) { \
return C2ParamUtils::customEnumValues( \
@@ -260,6 +262,21 @@
}
return namedValues;
}
+
+ /// safe(r) parsing from parameter blob
+ static
+ C2Param *ParseFirst(const uint8_t *blob, size_t size) {
+ // _mSize must fit into size, but really C2Param must also to be a valid param
+ if (size < sizeof(C2Param)) {
+ return nullptr;
+ }
+ // _mSize must match length
+ C2Param *param = (C2Param*)blob;
+ if (param->size() > size) {
+ return nullptr;
+ }
+ return param;
+ }
};
/* ---------------------------- UTILITIES FOR PARAMETER REFLECTION ---------------------------- */
diff --git a/media/libstagefright/codecs/aacdec/C2SoftAac.cpp b/media/libstagefright/codecs/aacdec/C2SoftAac.cpp
index 390f36c..b57c2aa 100644
--- a/media/libstagefright/codecs/aacdec/C2SoftAac.cpp
+++ b/media/libstagefright/codecs/aacdec/C2SoftAac.cpp
@@ -316,9 +316,9 @@
work->worklets.front()->output.buffers.clear();
work->worklets.front()->output.buffers.push_back(buffer);
work->worklets.front()->output.ordinal = work->input.ordinal;
- work->worklets_processed = 1u;
+ work->workletsProcessed = 1u;
};
- if (work && work->input.ordinal.frame_index == outInfo.frameIndex) {
+ if (work && work->input.ordinal.frameIndex == c2_cntr64_t(outInfo.frameIndex)) {
fillWork(work);
} else {
finish(outInfo.frameIndex, fillWork);
@@ -332,7 +332,7 @@
void C2SoftAac::process(
const std::unique_ptr<C2Work> &work,
const std::shared_ptr<C2BlockPool> &pool) {
- work->worklets_processed = 0u;
+ work->workletsProcessed = 0u;
if (mSignalledError) {
return;
}
@@ -346,8 +346,8 @@
size_t offset = 0u;
size_t size = view.capacity();
- bool eos = (work->input.flags & C2BufferPack::FLAG_END_OF_STREAM) != 0;
- bool codecConfig = (work->input.flags & C2BufferPack::FLAG_CODEC_CONFIG) != 0;
+ bool eos = (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0;
+ bool codecConfig = (work->input.flags & C2FrameData::FLAG_CODEC_CONFIG) != 0;
//TODO
#if 0
@@ -375,14 +375,13 @@
work->worklets.front()->output.ordinal = work->input.ordinal;
work->worklets.front()->output.buffers.clear();
- work->worklets.front()->output.buffers.push_back(nullptr);
return;
}
Info inInfo;
- inInfo.frameIndex = work->input.ordinal.frame_index;
- inInfo.timestamp = work->input.ordinal.timestamp;
+ inInfo.frameIndex = work->input.ordinal.frameIndex.peeku();
+ inInfo.timestamp = work->input.ordinal.timestamp.peeku();
inInfo.bufferSize = size;
inInfo.decodedSizes.clear();
while (size > 0u) {
@@ -602,15 +601,14 @@
auto fillEmptyWork = [](const std::unique_ptr<C2Work> &work) {
work->worklets.front()->output.flags = work->input.flags;
work->worklets.front()->output.buffers.clear();
- work->worklets.front()->output.buffers.emplace_back(nullptr);
work->worklets.front()->output.ordinal = work->input.ordinal;
- work->worklets_processed = 1u;
+ work->workletsProcessed = 1u;
};
while (mBuffersInfo.size() > 1u) {
finish(mBuffersInfo.front().frameIndex, fillEmptyWork);
mBuffersInfo.pop_front();
}
- if (work->worklets_processed == 0u) {
+ if (work->workletsProcessed == 0u) {
fillEmptyWork(work);
}
mBuffersInfo.clear();
diff --git a/media/libstagefright/codecs/aacenc/Android.bp b/media/libstagefright/codecs/aacenc/Android.bp
index 9342351..4caef92 100644
--- a/media/libstagefright/codecs/aacenc/Android.bp
+++ b/media/libstagefright/codecs/aacenc/Android.bp
@@ -1,4 +1,42 @@
cc_library_shared {
+ name: "libstagefright_soft_c2aacenc",
+// vendor_available: true,
+// vndk: {
+// enabled: true,
+// },
+
+ srcs: ["C2SoftAacEnc.cpp"],
+
+ cflags: ["-Werror"],
+
+ sanitize: {
+ misc_undefined: [
+ "signed-integer-overflow",
+ "unsigned-integer-overflow",
+ ],
+ cfi: true,
+ diag: {
+ cfi: true,
+ },
+ },
+
+ static_libs: [
+ "libFraunhoferAAC",
+ "libstagefright_codec2_vndk"
+ ],
+
+ shared_libs: [
+ "libcutils",
+ "libion",
+ "liblog",
+ "libstagefright_codec2",
+ "libstagefright_foundation",
+ "libstagefright_simple_c2component",
+ "libutils",
+ ],
+}
+
+cc_library_shared {
name: "libstagefright_soft_aacenc",
vendor_available: true,
vndk: {
diff --git a/media/libstagefright/codecs/aacenc/C2SoftAacEnc.cpp b/media/libstagefright/codecs/aacenc/C2SoftAacEnc.cpp
new file mode 100644
index 0000000..6f1b325
--- /dev/null
+++ b/media/libstagefright/codecs/aacenc/C2SoftAacEnc.cpp
@@ -0,0 +1,414 @@
+/*
+ * Copyright (C) 2012 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 "C2SoftAacEnc"
+#include <utils/Log.h>
+
+#include <inttypes.h>
+
+#include <C2PlatformSupport.h>
+#include <SimpleC2Interface.h>
+#include <media/stagefright/foundation/hexdump.h>
+
+#include "C2SoftAacEnc.h"
+
+namespace android {
+
+C2SoftAacEnc::C2SoftAacEnc(
+ const char *name,
+ c2_node_id_t id)
+ : SimpleC2Component(
+ SimpleC2Interface::Builder(name, id)
+ .inputFormat(C2FormatAudio)
+ .outputFormat(C2FormatCompressed)
+ .build()),
+ mAACEncoder(NULL),
+ mNumChannels(1),
+ mSampleRate(44100),
+ mBitRate(64000),
+ mSBRMode(-1),
+ mSBRRatio(0),
+ mAACProfile(AOT_AAC_LC),
+ mNumBytesPerInputFrame(0u),
+ mOutBufferSize(0u),
+ mSentCodecSpecificData(false),
+ mInputSize(0),
+ mInputTimeUs(-1ll),
+ mSignalledError(false) {
+}
+
+C2SoftAacEnc::~C2SoftAacEnc() {
+ onReset();
+}
+
+c2_status_t C2SoftAacEnc::onInit() {
+ status_t err = initEncoder();
+ return err == OK ? C2_OK : C2_CORRUPTED;
+}
+
+status_t C2SoftAacEnc::initEncoder() {
+ if (AACENC_OK != aacEncOpen(&mAACEncoder, 0, 0)) {
+ ALOGE("Failed to init AAC encoder");
+ return UNKNOWN_ERROR;
+ }
+ return setAudioParams();
+}
+
+c2_status_t C2SoftAacEnc::onStop() {
+ mSentCodecSpecificData = false;
+ mInputSize = 0u;
+ mInputTimeUs = -1ll;
+ mSignalledError = false;
+ return C2_OK;
+}
+
+void C2SoftAacEnc::onReset() {
+ (void)onStop();
+ aacEncClose(&mAACEncoder);
+}
+
+void C2SoftAacEnc::onRelease() {
+ // no-op
+}
+
+c2_status_t C2SoftAacEnc::onFlush_sm() {
+ mSentCodecSpecificData = false;
+ mInputSize = 0u;
+ return C2_OK;
+}
+
+static CHANNEL_MODE getChannelMode(uint32_t nChannels) {
+ CHANNEL_MODE chMode = MODE_INVALID;
+ switch (nChannels) {
+ case 1: chMode = MODE_1; break;
+ case 2: chMode = MODE_2; break;
+ case 3: chMode = MODE_1_2; break;
+ case 4: chMode = MODE_1_2_1; break;
+ case 5: chMode = MODE_1_2_2; break;
+ case 6: chMode = MODE_1_2_2_1; break;
+ default: chMode = MODE_INVALID;
+ }
+ return chMode;
+}
+
+//static AUDIO_OBJECT_TYPE getAOTFromProfile(OMX_U32 profile) {
+// if (profile == OMX_AUDIO_AACObjectLC) {
+// return AOT_AAC_LC;
+// } else if (profile == OMX_AUDIO_AACObjectHE) {
+// return AOT_SBR;
+// } else if (profile == OMX_AUDIO_AACObjectHE_PS) {
+// return AOT_PS;
+// } else if (profile == OMX_AUDIO_AACObjectLD) {
+// return AOT_ER_AAC_LD;
+// } else if (profile == OMX_AUDIO_AACObjectELD) {
+// return AOT_ER_AAC_ELD;
+// } else {
+// ALOGW("Unsupported AAC profile - defaulting to AAC-LC");
+// return AOT_AAC_LC;
+// }
+//}
+
+status_t C2SoftAacEnc::setAudioParams() {
+ // We call this whenever sample rate, number of channels, bitrate or SBR mode change
+ // in reponse to setParameter calls.
+
+ ALOGV("setAudioParams: %u Hz, %u channels, %u bps, %i sbr mode, %i sbr ratio",
+ mSampleRate, mNumChannels, mBitRate, mSBRMode, mSBRRatio);
+
+ if (AACENC_OK != aacEncoder_SetParam(mAACEncoder, AACENC_AOT, mAACProfile)) {
+ ALOGE("Failed to set AAC encoder parameters");
+ return UNKNOWN_ERROR;
+ }
+
+ if (AACENC_OK != aacEncoder_SetParam(mAACEncoder, AACENC_SAMPLERATE, mSampleRate)) {
+ ALOGE("Failed to set AAC encoder parameters");
+ return UNKNOWN_ERROR;
+ }
+ if (AACENC_OK != aacEncoder_SetParam(mAACEncoder, AACENC_BITRATE, mBitRate)) {
+ ALOGE("Failed to set AAC encoder parameters");
+ return UNKNOWN_ERROR;
+ }
+ if (AACENC_OK != aacEncoder_SetParam(mAACEncoder, AACENC_CHANNELMODE,
+ getChannelMode(mNumChannels))) {
+ ALOGE("Failed to set AAC encoder parameters");
+ return UNKNOWN_ERROR;
+ }
+ if (AACENC_OK != aacEncoder_SetParam(mAACEncoder, AACENC_TRANSMUX, TT_MP4_RAW)) {
+ ALOGE("Failed to set AAC encoder parameters");
+ return UNKNOWN_ERROR;
+ }
+
+ if (mSBRMode != -1 && mAACProfile == AOT_ER_AAC_ELD) {
+ if (AACENC_OK != aacEncoder_SetParam(mAACEncoder, AACENC_SBR_MODE, mSBRMode)) {
+ ALOGE("Failed to set AAC encoder parameters");
+ return UNKNOWN_ERROR;
+ }
+ }
+
+ /* SBR ratio parameter configurations:
+ 0: Default configuration wherein SBR ratio is configured depending on audio object type by
+ the FDK.
+ 1: Downsampled SBR (default for ELD)
+ 2: Dualrate SBR (default for HE-AAC)
+ */
+ if (AACENC_OK != aacEncoder_SetParam(mAACEncoder, AACENC_SBR_RATIO, mSBRRatio)) {
+ ALOGE("Failed to set AAC encoder parameters");
+ return UNKNOWN_ERROR;
+ }
+
+ return OK;
+}
+
+void C2SoftAacEnc::process(
+ const std::unique_ptr<C2Work> &work,
+ const std::shared_ptr<C2BlockPool> &pool) {
+ work->workletsProcessed = 0u;
+
+ if (mSignalledError) {
+ return;
+ }
+ bool eos = (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0;
+
+ if (!mSentCodecSpecificData) {
+ // The very first thing we want to output is the codec specific
+ // data.
+
+ if (AACENC_OK != aacEncEncode(mAACEncoder, NULL, NULL, NULL, NULL)) {
+ ALOGE("Unable to initialize encoder for profile / sample-rate / bit-rate / channels");
+ // TODO: notify(OMX_EventError, OMX_ErrorUndefined, 0, NULL);
+ mSignalledError = true;
+ return;
+ }
+
+ uint32_t actualBitRate = aacEncoder_GetParam(mAACEncoder, AACENC_BITRATE);
+ if (mBitRate != actualBitRate) {
+ ALOGW("Requested bitrate %u unsupported, using %u", mBitRate, actualBitRate);
+ }
+
+ AACENC_InfoStruct encInfo;
+ if (AACENC_OK != aacEncInfo(mAACEncoder, &encInfo)) {
+ ALOGE("Failed to get AAC encoder info");
+ // TODO: notify(OMX_EventError, OMX_ErrorUndefined, 0, NULL);
+ mSignalledError = true;
+ return;
+ }
+
+ std::unique_ptr<C2StreamCsdInfo::output> csd =
+ C2StreamCsdInfo::output::alloc_unique(encInfo.confSize, 0u);
+ // TODO: check NO_MEMORY
+ memcpy(csd->m.value, encInfo.confBuf, encInfo.confSize);
+ ALOGV("put csd");
+#if defined(LOG_NDEBUG) && !LOG_NDEBUG
+ hexdump(csd->m.value, csd->flexCount());
+#endif
+ work->worklets.front()->output.configUpdate.push_back(std::move(csd));
+
+ mOutBufferSize = encInfo.maxOutBufBytes;
+ mNumBytesPerInputFrame = encInfo.frameLength * mNumChannels * sizeof(int16_t);
+ mInputTimeUs = work->input.ordinal.timestamp;
+
+ mSentCodecSpecificData = true;
+ }
+
+ C2ReadView view = work->input.buffers[0]->data().linearBlocks().front().map().get();
+ uint64_t timestamp = mInputTimeUs.peeku();
+
+ size_t numFrames = (view.capacity() + mInputSize + (eos ? mNumBytesPerInputFrame - 1 : 0))
+ / mNumBytesPerInputFrame;
+ ALOGV("capacity = %u; mInputSize = %zu; numFrames = %zu", view.capacity(), mInputSize, numFrames);
+
+ std::shared_ptr<C2LinearBlock> block;
+ std::unique_ptr<C2WriteView> wView;
+ uint8_t *outPtr = nullptr;
+ size_t outAvailable = 0u;
+
+ if (numFrames) {
+ C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
+ // TODO: error handling, proper usage, etc.
+ c2_status_t err = pool->fetchLinearBlock(mOutBufferSize * numFrames, usage, &block);
+ if (err != C2_OK) {
+ ALOGE("err = %d", err);
+ }
+
+ wView.reset(new C2WriteView(block->map().get()));
+ outPtr = wView->data();
+ outAvailable = wView->size();
+ }
+
+ AACENC_InArgs inargs;
+ AACENC_OutArgs outargs;
+ memset(&inargs, 0, sizeof(inargs));
+ memset(&outargs, 0, sizeof(outargs));
+ inargs.numInSamples = view.capacity() / sizeof(int16_t);
+
+ void* inBuffer[] = { (unsigned char *)view.data() };
+ INT inBufferIds[] = { IN_AUDIO_DATA };
+ INT inBufferSize[] = { (INT)view.capacity() };
+ INT inBufferElSize[] = { sizeof(int16_t) };
+
+ AACENC_BufDesc inBufDesc;
+ inBufDesc.numBufs = sizeof(inBuffer) / sizeof(void*);
+ inBufDesc.bufs = (void**)&inBuffer;
+ inBufDesc.bufferIdentifiers = inBufferIds;
+ inBufDesc.bufSizes = inBufferSize;
+ inBufDesc.bufElSizes = inBufferElSize;
+
+ void* outBuffer[] = { outPtr };
+ INT outBufferIds[] = { OUT_BITSTREAM_DATA };
+ INT outBufferSize[] = { 0 };
+ INT outBufferElSize[] = { sizeof(UCHAR) };
+
+ AACENC_BufDesc outBufDesc;
+ outBufDesc.numBufs = sizeof(outBuffer) / sizeof(void*);
+ outBufDesc.bufs = (void**)&outBuffer;
+ outBufDesc.bufferIdentifiers = outBufferIds;
+ outBufDesc.bufSizes = outBufferSize;
+ outBufDesc.bufElSizes = outBufferElSize;
+
+ // Encode the mInputFrame, which is treated as a modulo buffer
+ AACENC_ERROR encoderErr = AACENC_OK;
+ size_t nOutputBytes = 0;
+
+ while (encoderErr == AACENC_OK && inargs.numInSamples > 0) {
+ memset(&outargs, 0, sizeof(outargs));
+
+ outBuffer[0] = outPtr;
+ outBufferSize[0] = outAvailable - nOutputBytes;
+
+ encoderErr = aacEncEncode(mAACEncoder,
+ &inBufDesc,
+ &outBufDesc,
+ &inargs,
+ &outargs);
+
+ if (encoderErr == AACENC_OK) {
+ if (outargs.numOutBytes > 0) {
+ mInputSize = 0;
+ int consumed = ((view.capacity() / sizeof(int16_t)) - inargs.numInSamples);
+ mInputTimeUs = work->input.ordinal.timestamp
+ + (consumed * 1000000ll / mNumChannels / mSampleRate);
+ } else {
+ mInputSize += outargs.numInSamples * sizeof(int16_t);
+ mInputTimeUs += outargs.numInSamples * 1000000ll / mNumChannels / mSampleRate;
+ }
+ outPtr += outargs.numOutBytes;
+ nOutputBytes += outargs.numOutBytes;
+
+ if (outargs.numInSamples > 0) {
+ inBuffer[0] = (int16_t *)inBuffer[0] + outargs.numInSamples;
+ inBufferSize[0] -= outargs.numInSamples * sizeof(int16_t);
+ inargs.numInSamples -= outargs.numInSamples;
+ }
+ }
+ ALOGV("nOutputBytes = %zu; inargs.numInSamples = %d", nOutputBytes, inargs.numInSamples);
+ }
+
+ if (eos && inBufferSize[0] > 0) {
+ memset(&outargs, 0, sizeof(outargs));
+
+ outBuffer[0] = outPtr;
+ outBufferSize[0] = outAvailable - nOutputBytes;
+
+ // Flush
+ inargs.numInSamples = -1;
+
+ (void)aacEncEncode(mAACEncoder,
+ &inBufDesc,
+ &outBufDesc,
+ &inargs,
+ &outargs);
+
+ nOutputBytes += outargs.numOutBytes;
+ }
+
+ work->worklets.front()->output.flags =
+ (C2FrameData::flags_t)(eos ? C2FrameData::FLAG_END_OF_STREAM : 0);
+ work->worklets.front()->output.buffers.clear();
+ work->worklets.front()->output.ordinal = work->input.ordinal;
+ work->worklets.front()->output.ordinal.timestamp = timestamp;
+ work->workletsProcessed = 1u;
+ if (nOutputBytes) {
+ work->worklets.front()->output.buffers.push_back(
+ createLinearBuffer(block, 0, nOutputBytes));
+ }
+
+#if 0
+ ALOGI("sending %d bytes of data (time = %lld us, flags = 0x%08lx)",
+ nOutputBytes, mInputTimeUs.peekll(), outHeader->nFlags);
+
+ hexdump(outHeader->pBuffer + outHeader->nOffset, outHeader->nFilledLen);
+#endif
+}
+
+c2_status_t C2SoftAacEnc::drain(
+ uint32_t drainMode,
+ const std::shared_ptr<C2BlockPool> &pool) {
+ switch (drainMode) {
+ case DRAIN_COMPONENT_NO_EOS: // fall-through
+ case NO_DRAIN:
+ // no-op
+ return C2_OK;
+ case DRAIN_CHAIN:
+ return C2_OMITTED;
+ case DRAIN_COMPONENT_WITH_EOS:
+ break;
+ default:
+ return C2_BAD_VALUE;
+ }
+
+ (void)pool;
+ mSentCodecSpecificData = false;
+ mInputSize = 0u;
+
+ // TODO: we don't have any pending work at this time to drain.
+ return C2_OK;
+}
+
+class C2SoftAacEncFactory : public C2ComponentFactory {
+public:
+ virtual c2_status_t createComponent(
+ c2_node_id_t id, std::shared_ptr<C2Component>* const component,
+ std::function<void(::android::C2Component*)> deleter) override {
+ *component = std::shared_ptr<C2Component>(new C2SoftAacEnc("aacenc", id), deleter);
+ return C2_OK;
+ }
+
+ virtual c2_status_t createInterface(
+ c2_node_id_t id, std::shared_ptr<C2ComponentInterface>* const interface,
+ std::function<void(::android::C2ComponentInterface*)> deleter) override {
+ *interface =
+ SimpleC2Interface::Builder("aacenc", id, deleter)
+ .inputFormat(C2FormatAudio)
+ .outputFormat(C2FormatCompressed)
+ .build();
+ return C2_OK;
+ }
+
+ virtual ~C2SoftAacEncFactory() override = default;
+};
+
+} // namespace android
+
+extern "C" ::android::C2ComponentFactory* CreateCodec2Factory() {
+ ALOGV("in %s", __func__);
+ return new ::android::C2SoftAacEncFactory();
+}
+
+extern "C" void DestroyCodec2Factory(::android::C2ComponentFactory* factory) {
+ ALOGV("in %s", __func__);
+ delete factory;
+}
diff --git a/media/libstagefright/codecs/aacenc/C2SoftAacEnc.h b/media/libstagefright/codecs/aacenc/C2SoftAacEnc.h
new file mode 100644
index 0000000..c9f440f
--- /dev/null
+++ b/media/libstagefright/codecs/aacenc/C2SoftAacEnc.h
@@ -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.
+ */
+
+#ifndef C2_SOFT_AAC_ENC_H_
+
+#define C2_SOFT_AAC_ENC_H_
+
+#include <SimpleC2Component.h>
+#include <media/stagefright/foundation/ABase.h>
+
+#include "aacenc_lib.h"
+
+namespace android {
+
+class C2SoftAacEnc : public SimpleC2Component {
+public:
+ C2SoftAacEnc(const char *name, c2_node_id_t id);
+ virtual ~C2SoftAacEnc();
+
+ // From SimpleC2Component
+ c2_status_t onInit() override;
+ c2_status_t onStop() override;
+ void onReset() override;
+ void onRelease() override;
+ c2_status_t onFlush_sm() override;
+ void process(
+ const std::unique_ptr<C2Work> &work,
+ const std::shared_ptr<C2BlockPool> &pool) override;
+ c2_status_t drain(
+ uint32_t drainMode,
+ const std::shared_ptr<C2BlockPool> &pool) override;
+
+private:
+ HANDLE_AACENCODER mAACEncoder;
+
+ uint32_t mNumChannels;
+ uint32_t mSampleRate;
+ uint32_t mBitRate;
+ int32_t mSBRMode;
+ int32_t mSBRRatio;
+ AUDIO_OBJECT_TYPE mAACProfile;
+ UINT mNumBytesPerInputFrame;
+ UINT mOutBufferSize;
+
+ bool mSentCodecSpecificData;
+ size_t mInputSize;
+ c2_cntr64_t mInputTimeUs;
+
+ bool mSignalledError;
+
+ status_t initEncoder();
+
+ status_t setAudioParams();
+
+ DISALLOW_EVIL_CONSTRUCTORS(C2SoftAacEnc);
+};
+
+} // namespace android
+
+#endif // C2_SOFT_AAC_ENC_H_
diff --git a/media/libstagefright/codecs/avcdec/C2SoftAvcDec.cpp b/media/libstagefright/codecs/avcdec/C2SoftAvcDec.cpp
index ffe6332..4f64f6e 100644
--- a/media/libstagefright/codecs/avcdec/C2SoftAvcDec.cpp
+++ b/media/libstagefright/codecs/avcdec/C2SoftAvcDec.cpp
@@ -206,14 +206,13 @@
void fillEmptyWork(const std::unique_ptr<C2Work> &work) {
uint32_t flags = 0;
- if ((work->input.flags & C2BufferPack::FLAG_END_OF_STREAM)) {
- flags |= C2BufferPack::FLAG_END_OF_STREAM;
+ if ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM)) {
+ flags |= C2FrameData::FLAG_END_OF_STREAM;
}
- work->worklets.front()->output.flags = (C2BufferPack::flags_t)flags;
+ work->worklets.front()->output.flags = (C2FrameData::flags_t)flags;
work->worklets.front()->output.buffers.clear();
- work->worklets.front()->output.buffers.emplace_back(nullptr);
work->worklets.front()->output.ordinal = work->input.ordinal;
- work->worklets_processed = 1u;
+ work->workletsProcessed = 1u;
}
} // namespace
@@ -448,7 +447,7 @@
}
c2_status_t C2SoftAvcDecIntf::query_vb(
- const std::vector<C2Param* const> & stackParams,
+ const std::vector<C2Param*> & stackParams,
const std::vector<C2Param::Index> & heapParamIndices,
c2_blocking_t mayBlock,
std::vector<std::unique_ptr<C2Param>>* const heapParams) const {
@@ -485,7 +484,7 @@
}
c2_status_t C2SoftAvcDecIntf::config_vb(
- const std::vector<C2Param* const> ¶ms,
+ const std::vector<C2Param*> ¶ms,
c2_blocking_t mayBlock,
std::vector<std::unique_ptr<C2SettingResult>>* const failures) {
(void)mayBlock;
@@ -1061,17 +1060,17 @@
std::shared_ptr<C2Buffer> buffer = createGraphicBuffer(std::move(mAllocatedBlock));
auto fillWork = [buffer](const std::unique_ptr<C2Work> &work) {
uint32_t flags = 0;
- if (work->input.flags & C2BufferPack::FLAG_END_OF_STREAM) {
- flags |= C2BufferPack::FLAG_END_OF_STREAM;
+ if (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) {
+ flags |= C2FrameData::FLAG_END_OF_STREAM;
ALOGV("EOS");
}
- work->worklets.front()->output.flags = (C2BufferPack::flags_t)flags;
+ work->worklets.front()->output.flags = (C2FrameData::flags_t)flags;
work->worklets.front()->output.buffers.clear();
work->worklets.front()->output.buffers.push_back(buffer);
work->worklets.front()->output.ordinal = work->input.ordinal;
- work->worklets_processed = 1u;
+ work->workletsProcessed = 1u;
};
- if (work && index == work->input.ordinal.frame_index) {
+ if (work && c2_cntr64_t(index) == work->input.ordinal.frameIndex) {
fillWork(work);
} else {
finish(index, fillWork);
@@ -1084,25 +1083,25 @@
bool eos = false;
work->result = C2_OK;
- work->worklets_processed = 0u;
+ work->workletsProcessed = 0u;
const C2ConstLinearBlock &buffer =
work->input.buffers[0]->data().linearBlocks().front();
if (buffer.capacity() == 0) {
- ALOGV("empty input: %llu", (long long)work->input.ordinal.frame_index);
+ ALOGV("empty input: %llu", work->input.ordinal.frameIndex.peekull());
// TODO: result?
fillEmptyWork(work);
- if ((work->input.flags & C2BufferPack::FLAG_END_OF_STREAM)) {
+ if ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM)) {
eos = true;
}
return;
- } else if (work->input.flags & C2BufferPack::FLAG_END_OF_STREAM) {
- ALOGV("input EOS: %llu", (long long)work->input.ordinal.frame_index);
+ } else if (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) {
+ ALOGV("input EOS: %llu", work->input.ordinal.frameIndex.peekull());
eos = true;
}
C2ReadView input = work->input.buffers[0]->data().linearBlocks().front().map().get();
- uint32_t workIndex = work->input.ordinal.frame_index & 0xFFFFFFFF;
+ uint32_t workIndex = work->input.ordinal.frameIndex.peeku() & 0xFFFFFFFF;
size_t inOffset = 0u;
while (inOffset < input.capacity()) {
@@ -1266,7 +1265,7 @@
}
if (drainMode == DRAIN_COMPONENT_WITH_EOS
- && work && work->worklets_processed == 0u) {
+ && work && work->workletsProcessed == 0u) {
fillEmptyWork(work);
}
diff --git a/media/libstagefright/codecs/avcdec/C2SoftAvcDec.h b/media/libstagefright/codecs/avcdec/C2SoftAvcDec.h
index 0e8cf77..6632bf3 100644
--- a/media/libstagefright/codecs/avcdec/C2SoftAvcDec.h
+++ b/media/libstagefright/codecs/avcdec/C2SoftAvcDec.h
@@ -84,12 +84,12 @@
virtual C2String getName() const override;
virtual c2_node_id_t getId() const override;
virtual c2_status_t query_vb(
- const std::vector<C2Param* const> &stackParams,
+ const std::vector<C2Param*> &stackParams,
const std::vector<C2Param::Index> &heapParamIndices,
c2_blocking_t mayBlock,
std::vector<std::unique_ptr<C2Param>>* const heapParams) const override;
virtual c2_status_t config_vb(
- const std::vector<C2Param* const> ¶ms,
+ const std::vector<C2Param*> ¶ms,
c2_blocking_t mayBlock,
std::vector<std::unique_ptr<C2SettingResult>>* const failures) override;
virtual c2_status_t createTunnel_sm(c2_node_id_t targetComponent) override;
diff --git a/media/libstagefright/codecs/avcenc/Android.bp b/media/libstagefright/codecs/avcenc/Android.bp
index cefe77c..67a2fdc 100644
--- a/media/libstagefright/codecs/avcenc/Android.bp
+++ b/media/libstagefright/codecs/avcenc/Android.bp
@@ -1,4 +1,48 @@
cc_library_shared {
+ name: "libstagefright_soft_c2avcenc",
+// vendor_available: true,
+// vndk: {
+// enabled: true,
+// },
+
+ static_libs: [ "libavcenc" ],
+ srcs: ["C2SoftAvcEnc.cpp"],
+
+ include_dirs: [
+ "external/libavc/encoder",
+ "external/libavc/common",
+ "frameworks/av/media/libstagefright/include",
+ "frameworks/native/include/media/hardware",
+ ],
+
+ shared_libs: [
+ "libstagefright_codec2",
+ "libstagefright_codec2_vndk",
+ "libstagefright_foundation",
+ "libstagefright_simple_c2component",
+ "libutils",
+ "liblog",
+ ],
+
+ sanitize: {
+ misc_undefined: [
+ "signed-integer-overflow",
+ ],
+ cfi: true,
+ diag: {
+ cfi: true,
+ },
+ },
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wno-unused-variable",
+ ],
+ ldflags: ["-Wl,-Bsymbolic"],
+}
+
+cc_library_shared {
name: "libstagefright_soft_avcenc",
vendor_available: true,
vndk: {
diff --git a/media/libstagefright/codecs/avcenc/C2SoftAvcEnc.cpp b/media/libstagefright/codecs/avcenc/C2SoftAvcEnc.cpp
new file mode 100644
index 0000000..7c281e3
--- /dev/null
+++ b/media/libstagefright/codecs/avcenc/C2SoftAvcEnc.cpp
@@ -0,0 +1,1239 @@
+/*
+ * 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 "C2SoftAvcEncEnc"
+#include <utils/Log.h>
+#include <utils/misc.h>
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/MetaData.h>
+#include <C2PlatformSupport.h>
+#include <SimpleC2Interface.h>
+
+#include "ih264_typedefs.h"
+#include "ih264e.h"
+#include "ih264e_error.h"
+#include "iv2.h"
+#include "ive2.h"
+#include "C2SoftAvcEnc.h"
+
+namespace android {
+
+#define ive_api_function ih264e_api_function
+
+namespace {
+
+// From external/libavc/encoder/ih264e_bitstream.h
+constexpr uint32_t MIN_STREAM_SIZE = 0x800;
+
+static size_t GetCPUCoreCount() {
+ long cpuCoreCount = 1;
+#if defined(_SC_NPROCESSORS_ONLN)
+ cpuCoreCount = sysconf(_SC_NPROCESSORS_ONLN);
+#else
+ // _SC_NPROC_ONLN must be defined...
+ cpuCoreCount = sysconf(_SC_NPROC_ONLN);
+#endif
+ CHECK(cpuCoreCount >= 1);
+ ALOGV("Number of CPU cores: %ld", cpuCoreCount);
+ return (size_t)cpuCoreCount;
+}
+
+void ConvertRGBToPlanarYUV(
+ uint8_t *dstY, size_t dstStride, size_t dstVStride,
+ const C2GraphicView &src) {
+ CHECK((src.width() & 1) == 0);
+ CHECK((src.height() & 1) == 0);
+
+ uint8_t *dstU = dstY + dstStride * dstVStride;
+ uint8_t *dstV = dstU + (dstStride >> 1) * (dstVStride >> 1);
+
+ const C2PlanarLayout &layout = src.layout();
+ const uint8_t *pRed = src.data()[C2PlanarLayout::PLANE_R];
+ const uint8_t *pGreen = src.data()[C2PlanarLayout::PLANE_G];
+ const uint8_t *pBlue = src.data()[C2PlanarLayout::PLANE_B];
+
+ for (size_t y = 0; y < src.height(); ++y) {
+ for (size_t x = 0; x < src.width(); ++x) {
+ unsigned red = *pRed;
+ unsigned green = *pGreen;
+ unsigned blue = *pBlue;
+
+ // using ITU-R BT.601 conversion matrix
+ unsigned luma =
+ ((red * 66 + green * 129 + blue * 25) >> 8) + 16;
+
+ dstY[x] = luma;
+
+ if ((x & 1) == 0 && (y & 1) == 0) {
+ unsigned U =
+ ((-red * 38 - green * 74 + blue * 112) >> 8) + 128;
+
+ unsigned V =
+ ((red * 112 - green * 94 - blue * 18) >> 8) + 128;
+
+ dstU[x >> 1] = U;
+ dstV[x >> 1] = V;
+ }
+ pRed += layout.planes[C2PlanarLayout::PLANE_R].colInc;
+ pGreen += layout.planes[C2PlanarLayout::PLANE_G].colInc;
+ pBlue += layout.planes[C2PlanarLayout::PLANE_B].colInc;
+ }
+
+ if ((y & 1) == 0) {
+ dstU += dstStride >> 1;
+ dstV += dstStride >> 1;
+ }
+
+ pRed -= layout.planes[C2PlanarLayout::PLANE_R].colInc * src.width();
+ pGreen -= layout.planes[C2PlanarLayout::PLANE_G].colInc * src.width();
+ pBlue -= layout.planes[C2PlanarLayout::PLANE_B].colInc * src.width();
+ pRed += layout.planes[C2PlanarLayout::PLANE_R].rowInc;
+ pGreen += layout.planes[C2PlanarLayout::PLANE_G].rowInc;
+ pBlue += layout.planes[C2PlanarLayout::PLANE_B].rowInc;
+
+ dstY += dstStride;
+ }
+}
+
+} // namespace
+
+C2SoftAvcEnc::C2SoftAvcEnc(const char *name, c2_node_id_t id)
+ : SimpleC2Component(
+ SimpleC2Interface::Builder(name, id)
+ .inputFormat(C2FormatVideo)
+ .outputFormat(C2FormatCompressed)
+ .build()),
+ mUpdateFlag(0),
+ mIvVideoColorFormat(IV_YUV_420P),
+ mAVCEncProfile(IV_PROFILE_BASE),
+ mAVCEncLevel(41),
+ mStarted(false),
+ mSawInputEOS(false),
+ mSawOutputEOS(false),
+ mSignalledError(false),
+ mCodecCtx(NULL),
+ mWidth(1080),
+ mHeight(1920),
+ mFramerate(60),
+ mBitrate(20000),
+ // TODO: output buffer size
+ mOutBufferSize(524288) {
+
+ // If dump is enabled, then open create an empty file
+ GENERATE_FILE_NAMES();
+ CREATE_DUMP_FILE(mInFile);
+ CREATE_DUMP_FILE(mOutFile);
+
+ initEncParams();
+}
+
+C2SoftAvcEnc::~C2SoftAvcEnc() {
+ releaseEncoder();
+}
+
+c2_status_t C2SoftAvcEnc::onInit() {
+ return C2_OK;
+}
+
+c2_status_t C2SoftAvcEnc::onStop() {
+ return C2_OK;
+}
+
+void C2SoftAvcEnc::onReset() {
+ // TODO: use IVE_CMD_CTL_RESET?
+ releaseEncoder();
+ initEncParams();
+}
+
+void C2SoftAvcEnc::onRelease() {
+ releaseEncoder();
+}
+
+c2_status_t C2SoftAvcEnc::onFlush_sm() {
+ // TODO: use IVE_CMD_CTL_FLUSH?
+ return C2_OK;
+}
+
+void C2SoftAvcEnc::initEncParams() {
+ mCodecCtx = NULL;
+ mMemRecords = NULL;
+ mNumMemRecords = DEFAULT_MEM_REC_CNT;
+ mHeaderGenerated = 0;
+ mNumCores = GetCPUCoreCount();
+ mArch = DEFAULT_ARCH;
+ mSliceMode = DEFAULT_SLICE_MODE;
+ mSliceParam = DEFAULT_SLICE_PARAM;
+ mHalfPelEnable = DEFAULT_HPEL;
+ mIInterval = DEFAULT_I_INTERVAL;
+ mIDRInterval = DEFAULT_IDR_INTERVAL;
+ mDisableDeblkLevel = DEFAULT_DISABLE_DEBLK_LEVEL;
+ mEnableFastSad = DEFAULT_ENABLE_FAST_SAD;
+ mEnableAltRef = DEFAULT_ENABLE_ALT_REF;
+ mEncSpeed = DEFAULT_ENC_SPEED;
+ mIntra4x4 = DEFAULT_INTRA4x4;
+ mConstrainedIntraFlag = DEFAULT_CONSTRAINED_INTRA;
+ mAIRMode = DEFAULT_AIR;
+ mAIRRefreshPeriod = DEFAULT_AIR_REFRESH_PERIOD;
+ mPSNREnable = DEFAULT_PSNR_ENABLE;
+ mReconEnable = DEFAULT_RECON_ENABLE;
+ mEntropyMode = DEFAULT_ENTROPY_MODE;
+ mBframes = DEFAULT_B_FRAMES;
+
+ gettimeofday(&mTimeStart, NULL);
+ gettimeofday(&mTimeEnd, NULL);
+}
+
+c2_status_t C2SoftAvcEnc::setDimensions() {
+ ive_ctl_set_dimensions_ip_t s_dimensions_ip;
+ ive_ctl_set_dimensions_op_t s_dimensions_op;
+ IV_STATUS_T status;
+
+ s_dimensions_ip.e_cmd = IVE_CMD_VIDEO_CTL;
+ s_dimensions_ip.e_sub_cmd = IVE_CMD_CTL_SET_DIMENSIONS;
+ s_dimensions_ip.u4_ht = mHeight;
+ s_dimensions_ip.u4_wd = mWidth;
+
+ s_dimensions_ip.u4_timestamp_high = -1;
+ s_dimensions_ip.u4_timestamp_low = -1;
+
+ s_dimensions_ip.u4_size = sizeof(ive_ctl_set_dimensions_ip_t);
+ s_dimensions_op.u4_size = sizeof(ive_ctl_set_dimensions_op_t);
+
+ status = ive_api_function(mCodecCtx, &s_dimensions_ip, &s_dimensions_op);
+ if (status != IV_SUCCESS) {
+ ALOGE("Unable to set frame dimensions = 0x%x\n",
+ s_dimensions_op.u4_error_code);
+ return C2_CORRUPTED;
+ }
+ return C2_OK;
+}
+
+c2_status_t C2SoftAvcEnc::setNumCores() {
+ IV_STATUS_T status;
+ ive_ctl_set_num_cores_ip_t s_num_cores_ip;
+ ive_ctl_set_num_cores_op_t s_num_cores_op;
+ s_num_cores_ip.e_cmd = IVE_CMD_VIDEO_CTL;
+ s_num_cores_ip.e_sub_cmd = IVE_CMD_CTL_SET_NUM_CORES;
+ s_num_cores_ip.u4_num_cores = MIN(mNumCores, CODEC_MAX_CORES);
+ s_num_cores_ip.u4_timestamp_high = -1;
+ s_num_cores_ip.u4_timestamp_low = -1;
+ s_num_cores_ip.u4_size = sizeof(ive_ctl_set_num_cores_ip_t);
+
+ s_num_cores_op.u4_size = sizeof(ive_ctl_set_num_cores_op_t);
+
+ status = ive_api_function(
+ mCodecCtx, (void *) &s_num_cores_ip, (void *) &s_num_cores_op);
+ if (status != IV_SUCCESS) {
+ ALOGE("Unable to set processor params = 0x%x\n",
+ s_num_cores_op.u4_error_code);
+ return C2_CORRUPTED;
+ }
+ return C2_OK;
+}
+
+c2_status_t C2SoftAvcEnc::setFrameRate() {
+ ive_ctl_set_frame_rate_ip_t s_frame_rate_ip;
+ ive_ctl_set_frame_rate_op_t s_frame_rate_op;
+ IV_STATUS_T status;
+
+ s_frame_rate_ip.e_cmd = IVE_CMD_VIDEO_CTL;
+ s_frame_rate_ip.e_sub_cmd = IVE_CMD_CTL_SET_FRAMERATE;
+
+ s_frame_rate_ip.u4_src_frame_rate = mFramerate;
+ s_frame_rate_ip.u4_tgt_frame_rate = mFramerate;
+
+ s_frame_rate_ip.u4_timestamp_high = -1;
+ s_frame_rate_ip.u4_timestamp_low = -1;
+
+ s_frame_rate_ip.u4_size = sizeof(ive_ctl_set_frame_rate_ip_t);
+ s_frame_rate_op.u4_size = sizeof(ive_ctl_set_frame_rate_op_t);
+
+ status = ive_api_function(mCodecCtx, &s_frame_rate_ip, &s_frame_rate_op);
+ if (status != IV_SUCCESS) {
+ ALOGE("Unable to set frame rate = 0x%x\n",
+ s_frame_rate_op.u4_error_code);
+ return C2_CORRUPTED;
+ }
+ return C2_OK;
+}
+
+c2_status_t C2SoftAvcEnc::setIpeParams() {
+ ive_ctl_set_ipe_params_ip_t s_ipe_params_ip;
+ ive_ctl_set_ipe_params_op_t s_ipe_params_op;
+ IV_STATUS_T status;
+
+ s_ipe_params_ip.e_cmd = IVE_CMD_VIDEO_CTL;
+ s_ipe_params_ip.e_sub_cmd = IVE_CMD_CTL_SET_IPE_PARAMS;
+
+ s_ipe_params_ip.u4_enable_intra_4x4 = mIntra4x4;
+ s_ipe_params_ip.u4_enc_speed_preset = mEncSpeed;
+ s_ipe_params_ip.u4_constrained_intra_pred = mConstrainedIntraFlag;
+
+ s_ipe_params_ip.u4_timestamp_high = -1;
+ s_ipe_params_ip.u4_timestamp_low = -1;
+
+ s_ipe_params_ip.u4_size = sizeof(ive_ctl_set_ipe_params_ip_t);
+ s_ipe_params_op.u4_size = sizeof(ive_ctl_set_ipe_params_op_t);
+
+ status = ive_api_function(mCodecCtx, &s_ipe_params_ip, &s_ipe_params_op);
+ if (status != IV_SUCCESS) {
+ ALOGE("Unable to set ipe params = 0x%x\n",
+ s_ipe_params_op.u4_error_code);
+ return C2_CORRUPTED;
+ }
+ return C2_OK;
+}
+
+c2_status_t C2SoftAvcEnc::setBitRate() {
+ ive_ctl_set_bitrate_ip_t s_bitrate_ip;
+ ive_ctl_set_bitrate_op_t s_bitrate_op;
+ IV_STATUS_T status;
+
+ s_bitrate_ip.e_cmd = IVE_CMD_VIDEO_CTL;
+ s_bitrate_ip.e_sub_cmd = IVE_CMD_CTL_SET_BITRATE;
+
+ s_bitrate_ip.u4_target_bitrate = mBitrate;
+
+ s_bitrate_ip.u4_timestamp_high = -1;
+ s_bitrate_ip.u4_timestamp_low = -1;
+
+ s_bitrate_ip.u4_size = sizeof(ive_ctl_set_bitrate_ip_t);
+ s_bitrate_op.u4_size = sizeof(ive_ctl_set_bitrate_op_t);
+
+ status = ive_api_function(mCodecCtx, &s_bitrate_ip, &s_bitrate_op);
+ if (status != IV_SUCCESS) {
+ ALOGE("Unable to set bit rate = 0x%x\n", s_bitrate_op.u4_error_code);
+ return C2_CORRUPTED;
+ }
+ return C2_OK;
+}
+
+c2_status_t C2SoftAvcEnc::setFrameType(IV_PICTURE_CODING_TYPE_T e_frame_type) {
+ ive_ctl_set_frame_type_ip_t s_frame_type_ip;
+ ive_ctl_set_frame_type_op_t s_frame_type_op;
+ IV_STATUS_T status;
+ s_frame_type_ip.e_cmd = IVE_CMD_VIDEO_CTL;
+ s_frame_type_ip.e_sub_cmd = IVE_CMD_CTL_SET_FRAMETYPE;
+
+ s_frame_type_ip.e_frame_type = e_frame_type;
+
+ s_frame_type_ip.u4_timestamp_high = -1;
+ s_frame_type_ip.u4_timestamp_low = -1;
+
+ s_frame_type_ip.u4_size = sizeof(ive_ctl_set_frame_type_ip_t);
+ s_frame_type_op.u4_size = sizeof(ive_ctl_set_frame_type_op_t);
+
+ status = ive_api_function(mCodecCtx, &s_frame_type_ip, &s_frame_type_op);
+ if (status != IV_SUCCESS) {
+ ALOGE("Unable to set frame type = 0x%x\n",
+ s_frame_type_op.u4_error_code);
+ return C2_CORRUPTED;
+ }
+ return C2_OK;
+}
+
+c2_status_t C2SoftAvcEnc::setQp() {
+ ive_ctl_set_qp_ip_t s_qp_ip;
+ ive_ctl_set_qp_op_t s_qp_op;
+ IV_STATUS_T status;
+
+ s_qp_ip.e_cmd = IVE_CMD_VIDEO_CTL;
+ s_qp_ip.e_sub_cmd = IVE_CMD_CTL_SET_QP;
+
+ s_qp_ip.u4_i_qp = DEFAULT_I_QP;
+ s_qp_ip.u4_i_qp_max = DEFAULT_QP_MAX;
+ s_qp_ip.u4_i_qp_min = DEFAULT_QP_MIN;
+
+ s_qp_ip.u4_p_qp = DEFAULT_P_QP;
+ s_qp_ip.u4_p_qp_max = DEFAULT_QP_MAX;
+ s_qp_ip.u4_p_qp_min = DEFAULT_QP_MIN;
+
+ s_qp_ip.u4_b_qp = DEFAULT_P_QP;
+ s_qp_ip.u4_b_qp_max = DEFAULT_QP_MAX;
+ s_qp_ip.u4_b_qp_min = DEFAULT_QP_MIN;
+
+ s_qp_ip.u4_timestamp_high = -1;
+ s_qp_ip.u4_timestamp_low = -1;
+
+ s_qp_ip.u4_size = sizeof(ive_ctl_set_qp_ip_t);
+ s_qp_op.u4_size = sizeof(ive_ctl_set_qp_op_t);
+
+ status = ive_api_function(mCodecCtx, &s_qp_ip, &s_qp_op);
+ if (status != IV_SUCCESS) {
+ ALOGE("Unable to set qp 0x%x\n", s_qp_op.u4_error_code);
+ return C2_CORRUPTED;
+ }
+ return C2_OK;
+}
+
+c2_status_t C2SoftAvcEnc::setEncMode(IVE_ENC_MODE_T e_enc_mode) {
+ IV_STATUS_T status;
+ ive_ctl_set_enc_mode_ip_t s_enc_mode_ip;
+ ive_ctl_set_enc_mode_op_t s_enc_mode_op;
+
+ s_enc_mode_ip.e_cmd = IVE_CMD_VIDEO_CTL;
+ s_enc_mode_ip.e_sub_cmd = IVE_CMD_CTL_SET_ENC_MODE;
+
+ s_enc_mode_ip.e_enc_mode = e_enc_mode;
+
+ s_enc_mode_ip.u4_timestamp_high = -1;
+ s_enc_mode_ip.u4_timestamp_low = -1;
+
+ s_enc_mode_ip.u4_size = sizeof(ive_ctl_set_enc_mode_ip_t);
+ s_enc_mode_op.u4_size = sizeof(ive_ctl_set_enc_mode_op_t);
+
+ status = ive_api_function(mCodecCtx, &s_enc_mode_ip, &s_enc_mode_op);
+ if (status != IV_SUCCESS) {
+ ALOGE("Unable to set in header encode mode = 0x%x\n",
+ s_enc_mode_op.u4_error_code);
+ return C2_CORRUPTED;
+ }
+ return C2_OK;
+}
+
+c2_status_t C2SoftAvcEnc::setVbvParams() {
+ ive_ctl_set_vbv_params_ip_t s_vbv_ip;
+ ive_ctl_set_vbv_params_op_t s_vbv_op;
+ IV_STATUS_T status;
+
+ s_vbv_ip.e_cmd = IVE_CMD_VIDEO_CTL;
+ s_vbv_ip.e_sub_cmd = IVE_CMD_CTL_SET_VBV_PARAMS;
+
+ s_vbv_ip.u4_vbv_buf_size = 0;
+ s_vbv_ip.u4_vbv_buffer_delay = 1000;
+
+ s_vbv_ip.u4_timestamp_high = -1;
+ s_vbv_ip.u4_timestamp_low = -1;
+
+ s_vbv_ip.u4_size = sizeof(ive_ctl_set_vbv_params_ip_t);
+ s_vbv_op.u4_size = sizeof(ive_ctl_set_vbv_params_op_t);
+
+ status = ive_api_function(mCodecCtx, &s_vbv_ip, &s_vbv_op);
+ if (status != IV_SUCCESS) {
+ ALOGE("Unable to set VBV params = 0x%x\n", s_vbv_op.u4_error_code);
+ return C2_CORRUPTED;
+ }
+ return C2_OK;
+}
+
+c2_status_t C2SoftAvcEnc::setAirParams() {
+ ive_ctl_set_air_params_ip_t s_air_ip;
+ ive_ctl_set_air_params_op_t s_air_op;
+ IV_STATUS_T status;
+
+ s_air_ip.e_cmd = IVE_CMD_VIDEO_CTL;
+ s_air_ip.e_sub_cmd = IVE_CMD_CTL_SET_AIR_PARAMS;
+
+ s_air_ip.e_air_mode = mAIRMode;
+ s_air_ip.u4_air_refresh_period = mAIRRefreshPeriod;
+
+ s_air_ip.u4_timestamp_high = -1;
+ s_air_ip.u4_timestamp_low = -1;
+
+ s_air_ip.u4_size = sizeof(ive_ctl_set_air_params_ip_t);
+ s_air_op.u4_size = sizeof(ive_ctl_set_air_params_op_t);
+
+ status = ive_api_function(mCodecCtx, &s_air_ip, &s_air_op);
+ if (status != IV_SUCCESS) {
+ ALOGE("Unable to set air params = 0x%x\n", s_air_op.u4_error_code);
+ return C2_CORRUPTED;
+ }
+ return C2_OK;
+}
+
+c2_status_t C2SoftAvcEnc::setMeParams() {
+ IV_STATUS_T status;
+ ive_ctl_set_me_params_ip_t s_me_params_ip;
+ ive_ctl_set_me_params_op_t s_me_params_op;
+
+ s_me_params_ip.e_cmd = IVE_CMD_VIDEO_CTL;
+ s_me_params_ip.e_sub_cmd = IVE_CMD_CTL_SET_ME_PARAMS;
+
+ s_me_params_ip.u4_enable_fast_sad = mEnableFastSad;
+ s_me_params_ip.u4_enable_alt_ref = mEnableAltRef;
+
+ s_me_params_ip.u4_enable_hpel = mHalfPelEnable;
+ s_me_params_ip.u4_enable_qpel = DEFAULT_QPEL;
+ s_me_params_ip.u4_me_speed_preset = DEFAULT_ME_SPEED;
+ s_me_params_ip.u4_srch_rng_x = DEFAULT_SRCH_RNG_X;
+ s_me_params_ip.u4_srch_rng_y = DEFAULT_SRCH_RNG_Y;
+
+ s_me_params_ip.u4_timestamp_high = -1;
+ s_me_params_ip.u4_timestamp_low = -1;
+
+ s_me_params_ip.u4_size = sizeof(ive_ctl_set_me_params_ip_t);
+ s_me_params_op.u4_size = sizeof(ive_ctl_set_me_params_op_t);
+
+ status = ive_api_function(mCodecCtx, &s_me_params_ip, &s_me_params_op);
+ if (status != IV_SUCCESS) {
+ ALOGE("Unable to set me params = 0x%x\n", s_me_params_op.u4_error_code);
+ return C2_CORRUPTED;
+ }
+ return C2_OK;
+}
+
+c2_status_t C2SoftAvcEnc::setGopParams() {
+ IV_STATUS_T status;
+ ive_ctl_set_gop_params_ip_t s_gop_params_ip;
+ ive_ctl_set_gop_params_op_t s_gop_params_op;
+
+ s_gop_params_ip.e_cmd = IVE_CMD_VIDEO_CTL;
+ s_gop_params_ip.e_sub_cmd = IVE_CMD_CTL_SET_GOP_PARAMS;
+
+ s_gop_params_ip.u4_i_frm_interval = mIInterval;
+ s_gop_params_ip.u4_idr_frm_interval = mIDRInterval;
+
+ s_gop_params_ip.u4_timestamp_high = -1;
+ s_gop_params_ip.u4_timestamp_low = -1;
+
+ s_gop_params_ip.u4_size = sizeof(ive_ctl_set_gop_params_ip_t);
+ s_gop_params_op.u4_size = sizeof(ive_ctl_set_gop_params_op_t);
+
+ status = ive_api_function(mCodecCtx, &s_gop_params_ip, &s_gop_params_op);
+ if (status != IV_SUCCESS) {
+ ALOGE("Unable to set GOP params = 0x%x\n",
+ s_gop_params_op.u4_error_code);
+ return C2_CORRUPTED;
+ }
+ return C2_OK;
+}
+
+c2_status_t C2SoftAvcEnc::setProfileParams() {
+ IV_STATUS_T status;
+ ive_ctl_set_profile_params_ip_t s_profile_params_ip;
+ ive_ctl_set_profile_params_op_t s_profile_params_op;
+
+ s_profile_params_ip.e_cmd = IVE_CMD_VIDEO_CTL;
+ s_profile_params_ip.e_sub_cmd = IVE_CMD_CTL_SET_PROFILE_PARAMS;
+
+ s_profile_params_ip.e_profile = DEFAULT_EPROFILE;
+ s_profile_params_ip.u4_entropy_coding_mode = mEntropyMode;
+ s_profile_params_ip.u4_timestamp_high = -1;
+ s_profile_params_ip.u4_timestamp_low = -1;
+
+ s_profile_params_ip.u4_size = sizeof(ive_ctl_set_profile_params_ip_t);
+ s_profile_params_op.u4_size = sizeof(ive_ctl_set_profile_params_op_t);
+
+ status = ive_api_function(mCodecCtx, &s_profile_params_ip, &s_profile_params_op);
+ if (status != IV_SUCCESS) {
+ ALOGE("Unable to set profile params = 0x%x\n",
+ s_profile_params_op.u4_error_code);
+ return C2_CORRUPTED;
+ }
+ return C2_OK;
+}
+
+c2_status_t C2SoftAvcEnc::setDeblockParams() {
+ IV_STATUS_T status;
+ ive_ctl_set_deblock_params_ip_t s_deblock_params_ip;
+ ive_ctl_set_deblock_params_op_t s_deblock_params_op;
+
+ s_deblock_params_ip.e_cmd = IVE_CMD_VIDEO_CTL;
+ s_deblock_params_ip.e_sub_cmd = IVE_CMD_CTL_SET_DEBLOCK_PARAMS;
+
+ s_deblock_params_ip.u4_disable_deblock_level = mDisableDeblkLevel;
+
+ s_deblock_params_ip.u4_timestamp_high = -1;
+ s_deblock_params_ip.u4_timestamp_low = -1;
+
+ s_deblock_params_ip.u4_size = sizeof(ive_ctl_set_deblock_params_ip_t);
+ s_deblock_params_op.u4_size = sizeof(ive_ctl_set_deblock_params_op_t);
+
+ status = ive_api_function(mCodecCtx, &s_deblock_params_ip, &s_deblock_params_op);
+ if (status != IV_SUCCESS) {
+ ALOGE("Unable to enable/disable deblock params = 0x%x\n",
+ s_deblock_params_op.u4_error_code);
+ return C2_CORRUPTED;
+ }
+ return C2_OK;
+}
+
+void C2SoftAvcEnc::logVersion() {
+ ive_ctl_getversioninfo_ip_t s_ctl_ip;
+ ive_ctl_getversioninfo_op_t s_ctl_op;
+ UWORD8 au1_buf[512];
+ IV_STATUS_T status;
+
+ s_ctl_ip.e_cmd = IVE_CMD_VIDEO_CTL;
+ s_ctl_ip.e_sub_cmd = IVE_CMD_CTL_GETVERSION;
+ s_ctl_ip.u4_size = sizeof(ive_ctl_getversioninfo_ip_t);
+ s_ctl_op.u4_size = sizeof(ive_ctl_getversioninfo_op_t);
+ s_ctl_ip.pu1_version = au1_buf;
+ s_ctl_ip.u4_version_bufsize = sizeof(au1_buf);
+
+ status = ive_api_function(mCodecCtx, (void *) &s_ctl_ip, (void *) &s_ctl_op);
+
+ if (status != IV_SUCCESS) {
+ ALOGE("Error in getting version: 0x%x", s_ctl_op.u4_error_code);
+ } else {
+ ALOGV("Ittiam encoder version: %s", (char *)s_ctl_ip.pu1_version);
+ }
+ return;
+}
+
+c2_status_t C2SoftAvcEnc::initEncoder() {
+ IV_STATUS_T status;
+ WORD32 level;
+ uint32_t displaySizeY;
+
+ CHECK(!mStarted);
+
+ c2_status_t errType = C2_OK;
+
+ displaySizeY = mWidth * mHeight;
+ if (displaySizeY > (1920 * 1088)) {
+ level = 50;
+ } else if (displaySizeY > (1280 * 720)) {
+ level = 40;
+ } else if (displaySizeY > (720 * 576)) {
+ level = 31;
+ } else if (displaySizeY > (624 * 320)) {
+ level = 30;
+ } else if (displaySizeY > (352 * 288)) {
+ level = 21;
+ } else if (displaySizeY > (176 * 144)) {
+ level = 20;
+ } else {
+ level = 10;
+ }
+ mAVCEncLevel = MAX(level, mAVCEncLevel);
+
+ mStride = mWidth;
+
+ // TODO
+ mIvVideoColorFormat = IV_YUV_420P;
+
+ ALOGD("Params width %d height %d level %d colorFormat %d", mWidth,
+ mHeight, mAVCEncLevel, mIvVideoColorFormat);
+
+ /* Getting Number of MemRecords */
+ {
+ iv_num_mem_rec_ip_t s_num_mem_rec_ip;
+ iv_num_mem_rec_op_t s_num_mem_rec_op;
+
+ s_num_mem_rec_ip.u4_size = sizeof(iv_num_mem_rec_ip_t);
+ s_num_mem_rec_op.u4_size = sizeof(iv_num_mem_rec_op_t);
+
+ s_num_mem_rec_ip.e_cmd = IV_CMD_GET_NUM_MEM_REC;
+
+ status = ive_api_function(0, &s_num_mem_rec_ip, &s_num_mem_rec_op);
+
+ if (status != IV_SUCCESS) {
+ ALOGE("Get number of memory records failed = 0x%x\n",
+ s_num_mem_rec_op.u4_error_code);
+ return C2_CORRUPTED;
+ }
+
+ mNumMemRecords = s_num_mem_rec_op.u4_num_mem_rec;
+ }
+
+ /* Allocate array to hold memory records */
+ if (mNumMemRecords > SIZE_MAX / sizeof(iv_mem_rec_t)) {
+ ALOGE("requested memory size is too big.");
+ return C2_CORRUPTED;
+ }
+ mMemRecords = (iv_mem_rec_t *)malloc(mNumMemRecords * sizeof(iv_mem_rec_t));
+ if (NULL == mMemRecords) {
+ ALOGE("Unable to allocate memory for hold memory records: Size %zu",
+ mNumMemRecords * sizeof(iv_mem_rec_t));
+ mSignalledError = true;
+ // TODO: notify(error, C2_CORRUPTED, 0, 0);
+ return C2_CORRUPTED;
+ }
+
+ {
+ iv_mem_rec_t *ps_mem_rec;
+ ps_mem_rec = mMemRecords;
+ for (size_t i = 0; i < mNumMemRecords; i++) {
+ ps_mem_rec->u4_size = sizeof(iv_mem_rec_t);
+ ps_mem_rec->pv_base = NULL;
+ ps_mem_rec->u4_mem_size = 0;
+ ps_mem_rec->u4_mem_alignment = 0;
+ ps_mem_rec->e_mem_type = IV_NA_MEM_TYPE;
+
+ ps_mem_rec++;
+ }
+ }
+
+ /* Getting MemRecords Attributes */
+ {
+ iv_fill_mem_rec_ip_t s_fill_mem_rec_ip;
+ iv_fill_mem_rec_op_t s_fill_mem_rec_op;
+
+ s_fill_mem_rec_ip.u4_size = sizeof(iv_fill_mem_rec_ip_t);
+ s_fill_mem_rec_op.u4_size = sizeof(iv_fill_mem_rec_op_t);
+
+ s_fill_mem_rec_ip.e_cmd = IV_CMD_FILL_NUM_MEM_REC;
+ s_fill_mem_rec_ip.ps_mem_rec = mMemRecords;
+ s_fill_mem_rec_ip.u4_num_mem_rec = mNumMemRecords;
+ s_fill_mem_rec_ip.u4_max_wd = mWidth;
+ s_fill_mem_rec_ip.u4_max_ht = mHeight;
+ s_fill_mem_rec_ip.u4_max_level = mAVCEncLevel;
+ s_fill_mem_rec_ip.e_color_format = DEFAULT_INP_COLOR_FORMAT;
+ s_fill_mem_rec_ip.u4_max_ref_cnt = DEFAULT_MAX_REF_FRM;
+ s_fill_mem_rec_ip.u4_max_reorder_cnt = DEFAULT_MAX_REORDER_FRM;
+ s_fill_mem_rec_ip.u4_max_srch_rng_x = DEFAULT_MAX_SRCH_RANGE_X;
+ s_fill_mem_rec_ip.u4_max_srch_rng_y = DEFAULT_MAX_SRCH_RANGE_Y;
+
+ status = ive_api_function(0, &s_fill_mem_rec_ip, &s_fill_mem_rec_op);
+
+ if (status != IV_SUCCESS) {
+ ALOGE("Fill memory records failed = 0x%x\n",
+ s_fill_mem_rec_op.u4_error_code);
+ mSignalledError = true;
+ // TODO: notify(error, C2_CORRUPTED, 0, 0);
+ return C2_CORRUPTED;
+ }
+ }
+
+ /* Allocating Memory for Mem Records */
+ {
+ WORD32 total_size;
+ iv_mem_rec_t *ps_mem_rec;
+ total_size = 0;
+ ps_mem_rec = mMemRecords;
+
+ for (size_t i = 0; i < mNumMemRecords; i++) {
+ ps_mem_rec->pv_base = ive_aligned_malloc(
+ ps_mem_rec->u4_mem_alignment, ps_mem_rec->u4_mem_size);
+ if (ps_mem_rec->pv_base == NULL) {
+ ALOGE("Allocation failure for mem record id %zu size %u\n", i,
+ ps_mem_rec->u4_mem_size);
+ mSignalledError = true;
+ // TODO: notify(error, C2_CORRUPTED, 0, 0);
+ return C2_CORRUPTED;
+
+ }
+ total_size += ps_mem_rec->u4_mem_size;
+
+ ps_mem_rec++;
+ }
+ }
+
+ /* Codec Instance Creation */
+ {
+ ive_init_ip_t s_init_ip;
+ ive_init_op_t s_init_op;
+
+ mCodecCtx = (iv_obj_t *)mMemRecords[0].pv_base;
+ mCodecCtx->u4_size = sizeof(iv_obj_t);
+ mCodecCtx->pv_fxns = (void *)ive_api_function;
+
+ s_init_ip.u4_size = sizeof(ive_init_ip_t);
+ s_init_op.u4_size = sizeof(ive_init_op_t);
+
+ s_init_ip.e_cmd = IV_CMD_INIT;
+ s_init_ip.u4_num_mem_rec = mNumMemRecords;
+ s_init_ip.ps_mem_rec = mMemRecords;
+ s_init_ip.u4_max_wd = mWidth;
+ s_init_ip.u4_max_ht = mHeight;
+ s_init_ip.u4_max_ref_cnt = DEFAULT_MAX_REF_FRM;
+ s_init_ip.u4_max_reorder_cnt = DEFAULT_MAX_REORDER_FRM;
+ s_init_ip.u4_max_level = mAVCEncLevel;
+ s_init_ip.e_inp_color_fmt = mIvVideoColorFormat;
+
+ if (mReconEnable || mPSNREnable) {
+ s_init_ip.u4_enable_recon = 1;
+ } else {
+ s_init_ip.u4_enable_recon = 0;
+ }
+ s_init_ip.e_recon_color_fmt = DEFAULT_RECON_COLOR_FORMAT;
+ s_init_ip.e_rc_mode = DEFAULT_RC_MODE;
+ s_init_ip.u4_max_framerate = DEFAULT_MAX_FRAMERATE;
+ s_init_ip.u4_max_bitrate = DEFAULT_MAX_BITRATE;
+ s_init_ip.u4_num_bframes = mBframes;
+ s_init_ip.e_content_type = IV_PROGRESSIVE;
+ s_init_ip.u4_max_srch_rng_x = DEFAULT_MAX_SRCH_RANGE_X;
+ s_init_ip.u4_max_srch_rng_y = DEFAULT_MAX_SRCH_RANGE_Y;
+ s_init_ip.e_slice_mode = mSliceMode;
+ s_init_ip.u4_slice_param = mSliceParam;
+ s_init_ip.e_arch = mArch;
+ s_init_ip.e_soc = DEFAULT_SOC;
+
+ status = ive_api_function(mCodecCtx, &s_init_ip, &s_init_op);
+
+ if (status != IV_SUCCESS) {
+ ALOGE("Init encoder failed = 0x%x\n", s_init_op.u4_error_code);
+ mSignalledError = true;
+ // TODO: notify(error, C2_CORRUPTED, 0 /* arg2 */, NULL /* data */);
+ return C2_CORRUPTED;
+ }
+ }
+
+ /* Get Codec Version */
+ logVersion();
+
+ /* set processor details */
+ setNumCores();
+
+ /* Video control Set Frame dimensions */
+ setDimensions();
+
+ /* Video control Set Frame rates */
+ setFrameRate();
+
+ /* Video control Set IPE Params */
+ setIpeParams();
+
+ /* Video control Set Bitrate */
+ setBitRate();
+
+ /* Video control Set QP */
+ setQp();
+
+ /* Video control Set AIR params */
+ setAirParams();
+
+ /* Video control Set VBV params */
+ setVbvParams();
+
+ /* Video control Set Motion estimation params */
+ setMeParams();
+
+ /* Video control Set GOP params */
+ setGopParams();
+
+ /* Video control Set Deblock params */
+ setDeblockParams();
+
+ /* Video control Set Profile params */
+ setProfileParams();
+
+ /* Video control Set in Encode header mode */
+ setEncMode(IVE_ENC_MODE_HEADER);
+
+ ALOGV("init_codec successfull");
+
+ mSpsPpsHeaderReceived = false;
+ mStarted = true;
+
+ return C2_OK;
+}
+
+c2_status_t C2SoftAvcEnc::releaseEncoder() {
+ IV_STATUS_T status = IV_SUCCESS;
+ iv_retrieve_mem_rec_ip_t s_retrieve_mem_ip;
+ iv_retrieve_mem_rec_op_t s_retrieve_mem_op;
+ iv_mem_rec_t *ps_mem_rec;
+
+ if (!mStarted) {
+ return C2_OK;
+ }
+
+ s_retrieve_mem_ip.u4_size = sizeof(iv_retrieve_mem_rec_ip_t);
+ s_retrieve_mem_op.u4_size = sizeof(iv_retrieve_mem_rec_op_t);
+ s_retrieve_mem_ip.e_cmd = IV_CMD_RETRIEVE_MEMREC;
+ s_retrieve_mem_ip.ps_mem_rec = mMemRecords;
+
+ status = ive_api_function(mCodecCtx, &s_retrieve_mem_ip, &s_retrieve_mem_op);
+
+ if (status != IV_SUCCESS) {
+ ALOGE("Unable to retrieve memory records = 0x%x\n",
+ s_retrieve_mem_op.u4_error_code);
+ return C2_CORRUPTED;
+ }
+
+ /* Free memory records */
+ ps_mem_rec = mMemRecords;
+ for (size_t i = 0; i < s_retrieve_mem_op.u4_num_mem_rec_filled; i++) {
+ ive_aligned_free(ps_mem_rec->pv_base);
+ ps_mem_rec++;
+ }
+
+ free(mMemRecords);
+
+ // clear other pointers into the space being free()d
+ mCodecCtx = NULL;
+
+ mStarted = false;
+
+ return C2_OK;
+}
+
+c2_status_t C2SoftAvcEnc::setEncodeArgs(
+ ive_video_encode_ip_t *ps_encode_ip,
+ ive_video_encode_op_t *ps_encode_op,
+ const C2GraphicView *const input,
+ uint8_t *base,
+ uint32_t capacity,
+ uint64_t timestamp) {
+ iv_raw_buf_t *ps_inp_raw_buf;
+
+ ps_inp_raw_buf = &ps_encode_ip->s_inp_buf;
+ ps_encode_ip->s_out_buf.pv_buf = base;
+ ps_encode_ip->s_out_buf.u4_bytes = 0;
+ ps_encode_ip->s_out_buf.u4_bufsize = capacity;
+ ps_encode_ip->u4_size = sizeof(ive_video_encode_ip_t);
+ ps_encode_op->u4_size = sizeof(ive_video_encode_op_t);
+
+ ps_encode_ip->e_cmd = IVE_CMD_VIDEO_ENCODE;
+ ps_encode_ip->pv_bufs = NULL;
+ ps_encode_ip->pv_mb_info = NULL;
+ ps_encode_ip->pv_pic_info = NULL;
+ ps_encode_ip->u4_mb_info_type = 0;
+ ps_encode_ip->u4_pic_info_type = 0;
+ ps_encode_ip->u4_is_last = 0;
+ ps_encode_ip->u4_timestamp_high = timestamp >> 32;
+ ps_encode_ip->u4_timestamp_low = timestamp & 0xFFFFFFFF;
+ ps_encode_op->s_out_buf.pv_buf = NULL;
+
+ /* Initialize color formats */
+ memset(ps_inp_raw_buf, 0, sizeof(iv_raw_buf_t));
+ ps_inp_raw_buf->u4_size = sizeof(iv_raw_buf_t);
+ ps_inp_raw_buf->e_color_fmt = mIvVideoColorFormat;
+ if (input == nullptr) {
+ if (mSawInputEOS){
+ ps_encode_ip->u4_is_last = 1;
+ }
+ return C2_OK;
+ }
+
+ ALOGV("width = %d, height = %d", input->width(), input->height());
+ const C2PlanarLayout &layout = input->layout();
+ uint8_t *yPlane = const_cast<uint8_t *>(input->data()[C2PlanarLayout::PLANE_Y]);
+ uint8_t *uPlane = const_cast<uint8_t *>(input->data()[C2PlanarLayout::PLANE_U]);
+ uint8_t *vPlane = const_cast<uint8_t *>(input->data()[C2PlanarLayout::PLANE_V]);
+ int32_t yStride = layout.planes[C2PlanarLayout::PLANE_Y].rowInc;
+ int32_t uStride = layout.planes[C2PlanarLayout::PLANE_U].rowInc;
+ int32_t vStride = layout.planes[C2PlanarLayout::PLANE_V].rowInc;
+
+ switch (layout.type) {
+ case C2PlanarLayout::TYPE_RGB:
+ // fall-through
+ case C2PlanarLayout::TYPE_RGBA: {
+ size_t yPlaneSize = input->width() * input->height();
+ std::unique_ptr<uint8_t[]> freeBuffer;
+ if (mFreeConversionBuffers.empty()) {
+ freeBuffer.reset(new uint8_t[yPlaneSize * 3 / 2]);
+ } else {
+ freeBuffer.swap(mFreeConversionBuffers.front());
+ mFreeConversionBuffers.pop_front();
+ }
+ yPlane = freeBuffer.get();
+ mConversionBuffersInUse.push_back(std::move(freeBuffer));
+ uPlane = yPlane + yPlaneSize;
+ vPlane = uPlane + yPlaneSize / 4;
+ yStride = input->width();
+ uStride = vStride = input->width() / 2;
+ ConvertRGBToPlanarYUV(yPlane, yStride, input->height(), *input);
+ break;
+ }
+ case C2PlanarLayout::TYPE_YUV:
+ // fall-through
+ case C2PlanarLayout::TYPE_YUVA:
+ // Do nothing
+ break;
+ default:
+ ALOGE("Unrecognized plane type: %d", layout.type);
+ return C2_BAD_VALUE;
+ }
+
+ switch (mIvVideoColorFormat) {
+ case IV_YUV_420P:
+ {
+ // input buffer is supposed to be const but Ittiam API wants bare pointer.
+ ps_inp_raw_buf->apv_bufs[0] = yPlane;
+ ps_inp_raw_buf->apv_bufs[1] = uPlane;
+ ps_inp_raw_buf->apv_bufs[2] = vPlane;
+
+ ps_inp_raw_buf->au4_wd[0] = input->width();
+ ps_inp_raw_buf->au4_wd[1] = input->width() / 2;
+ ps_inp_raw_buf->au4_wd[2] = input->width() / 2;
+
+ ps_inp_raw_buf->au4_ht[0] = input->height();
+ ps_inp_raw_buf->au4_ht[1] = input->height() / 2;
+ ps_inp_raw_buf->au4_ht[2] = input->height() / 2;
+
+ ps_inp_raw_buf->au4_strd[0] = yStride;
+ ps_inp_raw_buf->au4_strd[1] = uStride;
+ ps_inp_raw_buf->au4_strd[2] = vStride;
+ break;
+ }
+
+ case IV_YUV_422ILE:
+ {
+ // TODO
+ // ps_inp_raw_buf->apv_bufs[0] = pu1_buf;
+ // ps_inp_raw_buf->au4_wd[0] = mWidth * 2;
+ // ps_inp_raw_buf->au4_ht[0] = mHeight;
+ // ps_inp_raw_buf->au4_strd[0] = mStride * 2;
+ break;
+ }
+
+ case IV_YUV_420SP_UV:
+ case IV_YUV_420SP_VU:
+ default:
+ {
+ ps_inp_raw_buf->apv_bufs[0] = yPlane;
+ ps_inp_raw_buf->apv_bufs[1] = uPlane;
+
+ ps_inp_raw_buf->au4_wd[0] = input->width();
+ ps_inp_raw_buf->au4_wd[1] = input->width();
+
+ ps_inp_raw_buf->au4_ht[0] = input->height();
+ ps_inp_raw_buf->au4_ht[1] = input->height() / 2;
+
+ ps_inp_raw_buf->au4_strd[0] = yStride;
+ ps_inp_raw_buf->au4_strd[1] = uStride;
+ break;
+ }
+ }
+ return C2_OK;
+}
+
+void C2SoftAvcEnc::process(
+ const std::unique_ptr<C2Work> &work,
+ const std::shared_ptr<C2BlockPool> &pool) {
+ work->workletsProcessed = 0u;
+
+ IV_STATUS_T status;
+ WORD32 timeDelay, timeTaken;
+ uint64_t timestamp = work->input.ordinal.timestamp.peekull();
+
+ // Initialize encoder if not already initialized
+ if (mCodecCtx == NULL) {
+ if (C2_OK != initEncoder()) {
+ ALOGE("Failed to initialize encoder");
+ // TODO: notify(error, C2_CORRUPTED, 0 /* arg2 */, NULL /* data */);
+ return;
+ }
+ }
+ if (mSignalledError) {
+ return;
+ }
+
+ // while (!mSawOutputEOS && !outQueue.empty()) {
+ c2_status_t error;
+ ive_video_encode_ip_t s_encode_ip;
+ ive_video_encode_op_t s_encode_op;
+
+ if (!mSpsPpsHeaderReceived) {
+ constexpr uint32_t kHeaderLength = MIN_STREAM_SIZE;
+ uint8_t header[kHeaderLength];
+ error = setEncodeArgs(
+ &s_encode_ip, &s_encode_op, NULL, header, kHeaderLength, timestamp);
+ if (error != C2_OK) {
+ mSignalledError = true;
+ // TODO: notify(error, C2_CORRUPTED, 0, 0);
+ return;
+ }
+ status = ive_api_function(mCodecCtx, &s_encode_ip, &s_encode_op);
+
+ if (IV_SUCCESS != status) {
+ ALOGE("Encode header failed = 0x%x\n",
+ s_encode_op.u4_error_code);
+ return;
+ } else {
+ ALOGV("Bytes Generated in header %d\n",
+ s_encode_op.s_out_buf.u4_bytes);
+ }
+
+ mSpsPpsHeaderReceived = true;
+
+ std::unique_ptr<C2StreamCsdInfo::output> csd =
+ C2StreamCsdInfo::output::alloc_unique(s_encode_op.s_out_buf.u4_bytes, 0u);
+ memcpy(csd->m.value, header, s_encode_op.s_out_buf.u4_bytes);
+ work->worklets.front()->output.configUpdate.push_back(std::move(csd));
+
+ DUMP_TO_FILE(
+ mOutFile, csd->m.value, csd->flexCount());
+ }
+
+ if (mUpdateFlag) {
+ if (mUpdateFlag & kUpdateBitrate) {
+ setBitRate();
+ }
+ if (mUpdateFlag & kRequestKeyFrame) {
+ setFrameType(IV_IDR_FRAME);
+ }
+ if (mUpdateFlag & kUpdateAIRMode) {
+ setAirParams();
+ // notify(OMX_EventPortSettingsChanged, kOutputPortIndex,
+ // OMX_IndexConfigAndroidIntraRefresh, NULL);
+ }
+ mUpdateFlag = 0;
+ }
+
+ if (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) {
+ mSawInputEOS = true;
+ }
+
+ /* In normal mode, store inputBufferInfo and this will be returned
+ when encoder consumes this input */
+ // if (!mInputDataIsMeta && (inputBufferInfo != NULL)) {
+ // for (size_t i = 0; i < MAX_INPUT_BUFFER_HEADERS; i++) {
+ // if (NULL == mInputBufferInfo[i]) {
+ // mInputBufferInfo[i] = inputBufferInfo;
+ // break;
+ // }
+ // }
+ // }
+ const C2GraphicView view =
+ work->input.buffers[0]->data().graphicBlocks().front().map().get();
+ if (view.error() != C2_OK) {
+ ALOGE("graphic view map err = %d", view.error());
+ return;
+ }
+
+ std::shared_ptr<C2LinearBlock> block;
+
+ do {
+ C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
+ // TODO: error handling, proper usage, etc.
+ c2_status_t err = pool->fetchLinearBlock(mOutBufferSize, usage, &block);
+ if (err != C2_OK) {
+ ALOGE("fetch linear block err = %d", err);
+ return;
+ }
+ C2WriteView wView = block->map().get();
+ if (wView.error() != C2_OK) {
+ ALOGE("write view map err = %d", wView.error());
+ return;
+ }
+
+ error = setEncodeArgs(
+ &s_encode_ip, &s_encode_op, &view, wView.base(), wView.capacity(), timestamp);
+ if (error != C2_OK) {
+ mSignalledError = true;
+ ALOGE("setEncodeArgs failed : %d", error);
+ // TODO: notify(error, C2_CORRUPTED, 0, 0);
+ return;
+ }
+
+ // DUMP_TO_FILE(
+ // mInFile, s_encode_ip.s_inp_buf.apv_bufs[0],
+ // (mHeight * mStride * 3 / 2));
+
+ GETTIME(&mTimeStart, NULL);
+ /* Compute time elapsed between end of previous decode()
+ * to start of current decode() */
+ TIME_DIFF(mTimeEnd, mTimeStart, timeDelay);
+ status = ive_api_function(mCodecCtx, &s_encode_ip, &s_encode_op);
+
+ if (IV_SUCCESS != status) {
+ if ((s_encode_op.u4_error_code & 0xFF) == IH264E_BITSTREAM_BUFFER_OVERFLOW) {
+ // TODO: use IVE_CMD_CTL_GETBUFINFO for proper max input size?
+ mOutBufferSize *= 2;
+ continue;
+ }
+ ALOGE("Encode Frame failed = 0x%x\n",
+ s_encode_op.u4_error_code);
+ mSignalledError = true;
+ // TODO: notify(error, C2_CORRUPTED, 0, 0);
+ return;
+ }
+ } while (IV_SUCCESS != status);
+
+ // Hold input buffer reference
+ mBuffers[s_encode_ip.s_inp_buf.apv_bufs[0]] = work->input.buffers[0];
+
+ GETTIME(&mTimeEnd, NULL);
+ /* Compute time taken for decode() */
+ TIME_DIFF(mTimeStart, mTimeEnd, timeTaken);
+
+ ALOGV("timeTaken=%6d delay=%6d numBytes=%6d", timeTaken, timeDelay,
+ s_encode_op.s_out_buf.u4_bytes);
+
+ void *freed = s_encode_op.s_inp_buf.apv_bufs[0];
+ /* If encoder frees up an input buffer, mark it as free */
+ if (freed != NULL) {
+ if (mBuffers.count(freed) == 0u) {
+ // TODO: error
+ return;
+ }
+ // Release input buffer reference
+ mBuffers.erase(freed);
+
+ auto it = std::find_if(
+ mConversionBuffersInUse.begin(), mConversionBuffersInUse.end(),
+ [freed](const auto &elem) { return elem.get() == freed; });
+ if (it != mConversionBuffersInUse.end()) {
+ mFreeConversionBuffers.push_back(std::move(*it));
+ mConversionBuffersInUse.erase(it);
+ }
+ }
+
+ work->worklets.front()->output.flags = work->input.flags;
+ work->worklets.front()->output.ordinal = work->input.ordinal;
+ work->worklets.front()->output.ordinal.timestamp =
+ ((uint64_t)s_encode_op.u4_timestamp_high << 32) | s_encode_op.u4_timestamp_low;
+ work->worklets.front()->output.buffers.clear();
+ std::shared_ptr<C2Buffer> buffer =
+ createLinearBuffer(block, 0, s_encode_op.s_out_buf.u4_bytes);
+ work->worklets.front()->output.buffers.push_back(buffer);
+ work->workletsProcessed = 1u;
+
+ if (IV_IDR_FRAME == s_encode_op.u4_encoded_frame_type) {
+ buffer->setInfo(std::make_shared<C2StreamPictureTypeMaskInfo::output>(
+ 0u /* stream id */, C2PictureTypeKeyFrame));
+ }
+
+ if (s_encode_op.u4_is_last) {
+ // outputBufferHeader->nFlags |= OMX_BUFFERFLAG_EOS;
+ mSawOutputEOS = true;
+ } else {
+ // outputBufferHeader->nFlags &= ~OMX_BUFFERFLAG_EOS;
+ }
+}
+
+c2_status_t C2SoftAvcEnc::drain(
+ uint32_t drainMode,
+ const std::shared_ptr<C2BlockPool> &pool) {
+ // TODO: use IVE_CMD_CTL_FLUSH?
+ (void)drainMode;
+ (void)pool;
+ return C2_OK;
+}
+
+
+class C2SoftAvcEncFactory : public C2ComponentFactory {
+public:
+ virtual c2_status_t createComponent(
+ c2_node_id_t id, std::shared_ptr<C2Component>* const component,
+ std::function<void(::android::C2Component*)> deleter) override {
+ *component = std::shared_ptr<C2Component>(new C2SoftAvcEnc("avcenc", id), deleter);
+ return C2_OK;
+ }
+
+ virtual c2_status_t createInterface(
+ c2_node_id_t id, std::shared_ptr<C2ComponentInterface>* const interface,
+ std::function<void(::android::C2ComponentInterface*)> deleter) override {
+ *interface =
+ SimpleC2Interface::Builder("avcenc", id, deleter)
+ .inputFormat(C2FormatVideo)
+ .outputFormat(C2FormatCompressed)
+ .build();
+ return C2_OK;
+ }
+
+ virtual ~C2SoftAvcEncFactory() override = default;
+};
+
+} // namespace android
+
+extern "C" ::android::C2ComponentFactory* CreateCodec2Factory() {
+ ALOGV("in %s", __func__);
+ return new ::android::C2SoftAvcEncFactory();
+}
+
+extern "C" void DestroyCodec2Factory(::android::C2ComponentFactory* factory) {
+ ALOGV("in %s", __func__);
+ delete factory;
+}
diff --git a/media/libstagefright/codecs/avcenc/C2SoftAvcEnc.h b/media/libstagefright/codecs/avcenc/C2SoftAvcEnc.h
new file mode 100644
index 0000000..5b2a514
--- /dev/null
+++ b/media/libstagefright/codecs/avcenc/C2SoftAvcEnc.h
@@ -0,0 +1,293 @@
+/*
+ * 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.
+ */
+
+#ifndef C2_SOFT_AVC_ENC_H__
+#define C2_SOFT_AVC_ENC_H__
+
+#include <media/stagefright/foundation/ABase.h>
+#include <utils/Vector.h>
+
+#include <SimpleC2Component.h>
+
+namespace android {
+
+#define CODEC_MAX_CORES 4
+#define LEN_STATUS_BUFFER (10 * 1024)
+#define MAX_VBV_BUFF_SIZE (120 * 16384)
+#define MAX_NUM_IO_BUFS 3
+
+#define DEFAULT_MAX_REF_FRM 2
+#define DEFAULT_MAX_REORDER_FRM 0
+#define DEFAULT_QP_MIN 10
+#define DEFAULT_QP_MAX 40
+#define DEFAULT_MAX_BITRATE 20000000
+#define DEFAULT_MAX_SRCH_RANGE_X 256
+#define DEFAULT_MAX_SRCH_RANGE_Y 256
+#define DEFAULT_MAX_FRAMERATE 120000
+#define DEFAULT_NUM_CORES 1
+#define DEFAULT_NUM_CORES_PRE_ENC 0
+#define DEFAULT_FPS 30
+#define DEFAULT_ENC_SPEED IVE_NORMAL
+
+#define DEFAULT_MEM_REC_CNT 0
+#define DEFAULT_RECON_ENABLE 0
+#define DEFAULT_CHKSUM_ENABLE 0
+#define DEFAULT_START_FRM 0
+#define DEFAULT_NUM_FRMS 0xFFFFFFFF
+#define DEFAULT_INP_COLOR_FORMAT IV_YUV_420SP_VU
+#define DEFAULT_RECON_COLOR_FORMAT IV_YUV_420P
+#define DEFAULT_LOOPBACK 0
+#define DEFAULT_SRC_FRAME_RATE 30
+#define DEFAULT_TGT_FRAME_RATE 30
+#define DEFAULT_MAX_WD 1920
+#define DEFAULT_MAX_HT 1920
+#define DEFAULT_MAX_LEVEL 41
+#define DEFAULT_STRIDE 0
+#define DEFAULT_WD 1280
+#define DEFAULT_HT 720
+#define DEFAULT_PSNR_ENABLE 0
+#define DEFAULT_ME_SPEED 100
+#define DEFAULT_ENABLE_FAST_SAD 0
+#define DEFAULT_ENABLE_ALT_REF 0
+#define DEFAULT_RC_MODE IVE_RC_STORAGE
+#define DEFAULT_BITRATE 6000000
+#define DEFAULT_I_QP 22
+#define DEFAULT_I_QP_MAX DEFAULT_QP_MAX
+#define DEFAULT_I_QP_MIN DEFAULT_QP_MIN
+#define DEFAULT_P_QP 28
+#define DEFAULT_P_QP_MAX DEFAULT_QP_MAX
+#define DEFAULT_P_QP_MIN DEFAULT_QP_MIN
+#define DEFAULT_B_QP 22
+#define DEFAULT_B_QP_MAX DEFAULT_QP_MAX
+#define DEFAULT_B_QP_MIN DEFAULT_QP_MIN
+#define DEFAULT_AIR IVE_AIR_MODE_NONE
+#define DEFAULT_AIR_REFRESH_PERIOD 30
+#define DEFAULT_SRCH_RNG_X 64
+#define DEFAULT_SRCH_RNG_Y 48
+#define DEFAULT_I_INTERVAL 30
+#define DEFAULT_IDR_INTERVAL 1000
+#define DEFAULT_B_FRAMES 0
+#define DEFAULT_DISABLE_DEBLK_LEVEL 0
+#define DEFAULT_HPEL 1
+#define DEFAULT_QPEL 1
+#define DEFAULT_I4 1
+#define DEFAULT_EPROFILE IV_PROFILE_BASE
+#define DEFAULT_ENTROPY_MODE 0
+#define DEFAULT_SLICE_MODE IVE_SLICE_MODE_NONE
+#define DEFAULT_SLICE_PARAM 256
+#define DEFAULT_ARCH ARCH_ARM_A9Q
+#define DEFAULT_SOC SOC_GENERIC
+#define DEFAULT_INTRA4x4 0
+#define STRLENGTH 500
+#define DEFAULT_CONSTRAINED_INTRA 0
+
+#define MIN(a, b) ((a) < (b))? (a) : (b)
+#define MAX(a, b) ((a) > (b))? (a) : (b)
+#define ALIGN16(x) ((((x) + 15) >> 4) << 4)
+#define ALIGN128(x) ((((x) + 127) >> 7) << 7)
+#define ALIGN4096(x) ((((x) + 4095) >> 12) << 12)
+
+/** Used to remove warnings about unused parameters */
+#define UNUSED(x) ((void)(x))
+
+/** Get time */
+#define GETTIME(a, b) gettimeofday(a, b);
+
+/** Compute difference between start and end */
+#define TIME_DIFF(start, end, diff) \
+ diff = (((end).tv_sec - (start).tv_sec) * 1000000) + \
+ ((end).tv_usec - (start).tv_usec);
+
+#define ive_aligned_malloc(alignment, size) memalign(alignment, size)
+#define ive_aligned_free(buf) free(buf)
+
+struct C2SoftAvcEnc : public SimpleC2Component {
+ C2SoftAvcEnc(const char *name, c2_node_id_t id);
+
+ // From SimpleC2Component
+ c2_status_t onInit() override;
+ c2_status_t onStop() override;
+ void onReset() override;
+ void onRelease() override;
+ c2_status_t onFlush_sm() override;
+ void process(
+ const std::unique_ptr<C2Work> &work,
+ const std::shared_ptr<C2BlockPool> &pool) override;
+ c2_status_t drain(
+ uint32_t drainMode,
+ const std::shared_ptr<C2BlockPool> &pool) override;
+
+protected:
+ virtual ~C2SoftAvcEnc();
+
+private:
+ enum {
+ kUpdateBitrate = 1 << 0,
+ kRequestKeyFrame = 1 << 1,
+ kUpdateAIRMode = 1 << 2,
+ };
+
+ // OMX input buffer's timestamp and flags
+ typedef struct {
+ int64_t mTimeUs;
+ int32_t mFlags;
+ } InputBufferInfo;
+
+ int32_t mStride;
+
+ struct timeval mTimeStart; // Time at the start of decode()
+ struct timeval mTimeEnd; // Time at the end of decode()
+
+ int mUpdateFlag;
+
+#ifdef FILE_DUMP_ENABLE
+ char mInFile[200];
+ char mOutFile[200];
+#endif /* FILE_DUMP_ENABLE */
+
+ IV_COLOR_FORMAT_T mIvVideoColorFormat;
+
+ IV_PROFILE_T mAVCEncProfile;
+ WORD32 mAVCEncLevel;
+ bool mStarted;
+ bool mSpsPpsHeaderReceived;
+
+ bool mSawInputEOS;
+ bool mSawOutputEOS;
+ bool mSignalledError;
+ bool mIntra4x4;
+ bool mEnableFastSad;
+ bool mEnableAltRef;
+ bool mReconEnable;
+ bool mPSNREnable;
+ bool mEntropyMode;
+ bool mConstrainedIntraFlag;
+ IVE_SPEED_CONFIG mEncSpeed;
+
+ iv_obj_t *mCodecCtx; // Codec context
+ iv_mem_rec_t *mMemRecords; // Memory records requested by the codec
+ size_t mNumMemRecords; // Number of memory records requested by codec
+ size_t mNumCores; // Number of cores used by the codec
+
+ uint32_t mWidth;
+ uint32_t mHeight;
+ uint32_t mFramerate;
+ uint32_t mBitrate;
+ uint32_t mOutBufferSize;
+ UWORD32 mHeaderGenerated;
+ UWORD32 mBframes;
+ IV_ARCH_T mArch;
+ IVE_SLICE_MODE_T mSliceMode;
+ UWORD32 mSliceParam;
+ bool mHalfPelEnable;
+ UWORD32 mIInterval;
+ UWORD32 mIDRInterval;
+ UWORD32 mDisableDeblkLevel;
+ IVE_AIR_MODE_T mAIRMode;
+ UWORD32 mAIRRefreshPeriod;
+ std::map<const void *, std::shared_ptr<C2Buffer>> mBuffers;
+ std::list<std::unique_ptr<uint8_t[]>> mFreeConversionBuffers;
+ std::list<std::unique_ptr<uint8_t[]>> mConversionBuffersInUse;
+
+ void initEncParams();
+ c2_status_t initEncoder();
+ c2_status_t releaseEncoder();
+
+ c2_status_t setFrameType(IV_PICTURE_CODING_TYPE_T e_frame_type);
+ c2_status_t setQp();
+ c2_status_t setEncMode(IVE_ENC_MODE_T e_enc_mode);
+ c2_status_t setDimensions();
+ c2_status_t setNumCores();
+ c2_status_t setFrameRate();
+ c2_status_t setIpeParams();
+ c2_status_t setBitRate();
+ c2_status_t setAirParams();
+ c2_status_t setMeParams();
+ c2_status_t setGopParams();
+ c2_status_t setProfileParams();
+ c2_status_t setDeblockParams();
+ c2_status_t setVbvParams();
+ void logVersion();
+ c2_status_t setEncodeArgs(
+ ive_video_encode_ip_t *ps_encode_ip,
+ ive_video_encode_op_t *ps_encode_op,
+ const C2GraphicView *const input,
+ uint8_t *base,
+ uint32_t capacity,
+ uint64_t timestamp);
+
+ DISALLOW_EVIL_CONSTRUCTORS(C2SoftAvcEnc);
+};
+
+#ifdef FILE_DUMP_ENABLE
+
+#define INPUT_DUMP_PATH "/sdcard/media/avce_input"
+#define INPUT_DUMP_EXT "yuv"
+#define OUTPUT_DUMP_PATH "/sdcard/media/avce_output"
+#define OUTPUT_DUMP_EXT "h264"
+
+#define GENERATE_FILE_NAMES() { \
+ GETTIME(&mTimeStart, NULL); \
+ strcpy(mInFile, ""); \
+ sprintf(mInFile, "%s_%ld.%ld.%s", INPUT_DUMP_PATH, \
+ mTimeStart.tv_sec, mTimeStart.tv_usec, \
+ INPUT_DUMP_EXT); \
+ strcpy(mOutFile, ""); \
+ sprintf(mOutFile, "%s_%ld.%ld.%s", OUTPUT_DUMP_PATH,\
+ mTimeStart.tv_sec, mTimeStart.tv_usec, \
+ OUTPUT_DUMP_EXT); \
+}
+
+#define CREATE_DUMP_FILE(m_filename) { \
+ FILE *fp = fopen(m_filename, "wb"); \
+ if (fp != NULL) { \
+ ALOGD("Opened file %s", m_filename); \
+ fclose(fp); \
+ } else { \
+ ALOGD("Could not open file %s", m_filename); \
+ } \
+}
+#define DUMP_TO_FILE(m_filename, m_buf, m_size) \
+{ \
+ FILE *fp = fopen(m_filename, "ab"); \
+ if (fp != NULL && m_buf != NULL) { \
+ int i; \
+ i = fwrite(m_buf, 1, m_size, fp); \
+ ALOGD("fwrite ret %d to write %d", i, m_size); \
+ if (i != (int)m_size) { \
+ ALOGD("Error in fwrite, returned %d", i); \
+ perror("Error in write to file"); \
+ } \
+ fclose(fp); \
+ } else { \
+ ALOGD("Could not write to file %s", m_filename);\
+ if (fp != NULL) \
+ fclose(fp); \
+ } \
+}
+#else /* FILE_DUMP_ENABLE */
+#define INPUT_DUMP_PATH
+#define INPUT_DUMP_EXT
+#define OUTPUT_DUMP_PATH
+#define OUTPUT_DUMP_EXT
+#define GENERATE_FILE_NAMES()
+#define CREATE_DUMP_FILE(m_filename)
+#define DUMP_TO_FILE(m_filename, m_buf, m_size)
+#endif /* FILE_DUMP_ENABLE */
+
+} // namespace android
+
+#endif // C2_SOFT_AVC_ENC_H__
diff --git a/media/libstagefright/codecs/avcenc/SoftAVCEnc.cpp b/media/libstagefright/codecs/avcenc/SoftAVCEnc.cpp
index 32fdbd3..379d41e 100644
--- a/media/libstagefright/codecs/avcenc/SoftAVCEnc.cpp
+++ b/media/libstagefright/codecs/avcenc/SoftAVCEnc.cpp
@@ -26,7 +26,6 @@
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/MediaDefs.h>
#include <media/stagefright/MediaErrors.h>
-#include <media/stagefright/MetaData.h>
#include <OMX_IndexExt.h>
#include <OMX_VideoExt.h>
diff --git a/media/libstagefright/codecs/cmds/Android.bp b/media/libstagefright/codecs/cmds/Android.bp
index 40f1a3d..6dba0a3 100644
--- a/media/libstagefright/codecs/cmds/Android.bp
+++ b/media/libstagefright/codecs/cmds/Android.bp
@@ -13,6 +13,7 @@
"libcutils",
"libgui",
"liblog",
+ "libmediaextractor",
"libstagefright",
"libstagefright_codec2",
"libstagefright_codec2_vndk",
diff --git a/media/libstagefright/codecs/cmds/codec2.cpp b/media/libstagefright/codecs/cmds/codec2.cpp
index 78fb527..ec05d7a 100644
--- a/media/libstagefright/codecs/cmds/codec2.cpp
+++ b/media/libstagefright/codecs/cmds/codec2.cpp
@@ -247,7 +247,7 @@
}
int slot;
sp<Fence> fence;
- ALOGV("Render: Frame #%" PRId64, work->worklets.front()->output.ordinal.frame_index);
+ ALOGV("Render: Frame #%lld", work->worklets.front()->output.ordinal.frameIndex.peekll());
const std::shared_ptr<C2Buffer> &output = work->worklets.front()->output.buffers[0];
if (output) {
const C2ConstGraphicBlock &block = output->data().graphicBlocks().front();
@@ -266,7 +266,7 @@
status_t err = igbp->attachBuffer(&slot, buffer);
IGraphicBufferProducer::QueueBufferInput qbi(
- work->worklets.front()->output.ordinal.timestamp * 1000ll,
+ (work->worklets.front()->output.ordinal.timestamp * 1000ll).peekll(),
false,
HAL_DATASPACE_UNKNOWN,
Rect(block.width(), block.height()),
@@ -338,9 +338,9 @@
mQueueCondition.wait_for(l, 100ms);
}
}
- work->input.flags = (C2BufferPack::flags_t)0;
+ work->input.flags = (C2FrameData::flags_t)0;
work->input.ordinal.timestamp = timestamp;
- work->input.ordinal.frame_index = numFrames;
+ work->input.ordinal.frameIndex = numFrames;
std::shared_ptr<C2LinearBlock> block;
mLinearPool->fetchLinearBlock(
diff --git a/media/libstagefright/colorconversion/ColorConverter.cpp b/media/libstagefright/colorconversion/ColorConverter.cpp
index c7f9001..eae73fc 100644
--- a/media/libstagefright/colorconversion/ColorConverter.cpp
+++ b/media/libstagefright/colorconversion/ColorConverter.cpp
@@ -64,7 +64,7 @@
|| mDstFormat == OMX_COLOR_Format32bitBGRA8888;
case OMX_COLOR_FormatYUV420Planar16:
- return mDstFormat == OMX_COLOR_Format32BitRGBA1010102;
+ return mDstFormat == OMX_COLOR_FormatYUV444Y410;
case OMX_COLOR_FormatCbYCrY:
case OMX_QCOM_COLOR_FormatYVU420SemiPlanar:
@@ -77,6 +77,12 @@
}
}
+bool ColorConverter::isDstRGB() const {
+ return mDstFormat == OMX_COLOR_Format16bitRGB565
+ || mDstFormat == OMX_COLOR_Format32BitRGBA8888
+ || mDstFormat == OMX_COLOR_Format32bitBGRA8888;
+}
+
ColorConverter::BitmapParams::BitmapParams(
void *bits,
size_t width, size_t height,
@@ -99,7 +105,7 @@
case OMX_COLOR_Format32bitBGRA8888:
case OMX_COLOR_Format32BitRGBA8888:
- case OMX_COLOR_Format32BitRGBA1010102:
+ case OMX_COLOR_FormatYUV444Y410:
mBpp = 4;
mStride = 4 * mWidth;
break;
diff --git a/media/libstagefright/colorconversion/SoftwareRenderer.cpp b/media/libstagefright/colorconversion/SoftwareRenderer.cpp
index fca9c09..c9ebbdc 100644
--- a/media/libstagefright/colorconversion/SoftwareRenderer.cpp
+++ b/media/libstagefright/colorconversion/SoftwareRenderer.cpp
@@ -134,6 +134,10 @@
}
case OMX_COLOR_FormatYUV420Planar16:
{
+ // Here we would convert OMX_COLOR_FormatYUV420Planar16 into
+ // OMX_COLOR_FormatYUV444Y410, and put it inside a buffer with
+ // format HAL_PIXEL_FORMAT_RGBA_1010102. Surfaceflinger will
+ // use render engine to convert it to RGB if needed.
halFormat = HAL_PIXEL_FORMAT_RGBA_1010102;
bufWidth = (mCropWidth + 1) & ~1;
bufHeight = (mCropHeight + 1) & ~1;
@@ -152,7 +156,7 @@
CHECK(mConverter->isValid());
} else if (mColorFormat == OMX_COLOR_FormatYUV420Planar16) {
mConverter = new ColorConverter(
- mColorFormat, OMX_COLOR_Format32BitRGBA1010102);
+ mColorFormat, OMX_COLOR_FormatYUV444Y410);
CHECK(mConverter->isValid());
}
@@ -380,7 +384,7 @@
if (format->findInt32("android._dataspace", (int32_t *)&dataSpace) && dataSpace != mDataSpace) {
mDataSpace = dataSpace;
- if (mConverter != NULL) {
+ if (mConverter != NULL && mConverter->isDstRGB()) {
// graphics only supports full range RGB. ColorConverter should have
// converted any YUV to full range.
dataSpace = (android_dataspace)
diff --git a/media/libstagefright/foundation/ABuffer.cpp b/media/libstagefright/foundation/ABuffer.cpp
index 804046a..c8965d9 100644
--- a/media/libstagefright/foundation/ABuffer.cpp
+++ b/media/libstagefright/foundation/ABuffer.cpp
@@ -19,13 +19,11 @@
#include "ADebug.h"
#include "ALooper.h"
#include "AMessage.h"
-#include "MediaBufferBase.h"
namespace android {
ABuffer::ABuffer(size_t capacity)
- : mMediaBufferBase(NULL),
- mRangeOffset(0),
+ : mRangeOffset(0),
mInt32Data(0),
mOwnsData(true) {
mData = malloc(capacity);
@@ -39,8 +37,7 @@
}
ABuffer::ABuffer(void *data, size_t capacity)
- : mMediaBufferBase(NULL),
- mData(data),
+ : mData(data),
mCapacity(capacity),
mRangeOffset(0),
mRangeLength(capacity),
@@ -66,8 +63,6 @@
mData = NULL;
}
}
-
- setMediaBufferBase(NULL);
}
void ABuffer::setRange(size_t offset, size_t size) {
@@ -85,19 +80,5 @@
return mMeta;
}
-MediaBufferBase *ABuffer::getMediaBufferBase() {
- if (mMediaBufferBase != NULL) {
- mMediaBufferBase->add_ref();
- }
- return mMediaBufferBase;
-}
-
-void ABuffer::setMediaBufferBase(MediaBufferBase *mediaBuffer) {
- if (mMediaBufferBase != NULL) {
- mMediaBufferBase->release();
- }
- mMediaBufferBase = mediaBuffer;
-}
-
} // namespace android
diff --git a/media/libstagefright/foundation/Android.bp b/media/libstagefright/foundation/Android.bp
index df3e280..2258e2c 100644
--- a/media/libstagefright/foundation/Android.bp
+++ b/media/libstagefright/foundation/Android.bp
@@ -4,7 +4,7 @@
vendor_available: true,
}
-cc_library_shared {
+cc_library {
name: "libstagefright_foundation",
vendor_available: true,
vndk: {
@@ -62,8 +62,6 @@
"AStringUtils.cpp",
"ByteUtils.cpp",
"ColorUtils.cpp",
- "MediaBuffer.cpp",
- "MediaBufferGroup.cpp",
"MediaDefs.cpp",
"MediaKeys.cpp",
"MetaData.cpp",
diff --git a/media/libstagefright/foundation/MediaBuffer.cpp b/media/libstagefright/foundation/MediaBuffer.cpp
deleted file mode 100644
index 95951dd..0000000
--- a/media/libstagefright/foundation/MediaBuffer.cpp
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "MediaBuffer"
-#include <utils/Log.h>
-
-#include <errno.h>
-#include <pthread.h>
-#include <stdlib.h>
-
-#include <media/stagefright/foundation/ABuffer.h>
-#include <media/stagefright/foundation/ADebug.h>
-#include <media/stagefright/MediaBuffer.h>
-#include <media/stagefright/MetaData.h>
-
-#include <ui/GraphicBuffer.h>
-
-namespace android {
-
-/* static */
-std::atomic_int_least32_t MediaBuffer::mUseSharedMemory(0);
-
-MediaBuffer::MediaBuffer(void *data, size_t size)
- : mObserver(NULL),
- mRefCount(0),
- mData(data),
- mSize(size),
- mRangeOffset(0),
- mRangeLength(size),
- mOwnsData(false),
- mMetaData(new MetaData),
- mOriginal(NULL) {
-}
-
-MediaBuffer::MediaBuffer(size_t size)
- : mObserver(NULL),
- mRefCount(0),
- mData(NULL),
- mSize(size),
- mRangeOffset(0),
- mRangeLength(size),
- mOwnsData(true),
- mMetaData(new MetaData),
- mOriginal(NULL) {
- if (size < kSharedMemThreshold
- || std::atomic_load_explicit(&mUseSharedMemory, std::memory_order_seq_cst) == 0) {
- mData = malloc(size);
- } else {
- ALOGV("creating memoryDealer");
- sp<MemoryDealer> memoryDealer =
- new MemoryDealer(size + sizeof(SharedControl), "MediaBuffer");
- mMemory = memoryDealer->allocate(size + sizeof(SharedControl));
- if (mMemory == NULL) {
- ALOGW("Failed to allocate shared memory, trying regular allocation!");
- mData = malloc(size);
- if (mData == NULL) {
- ALOGE("Out of memory");
- }
- } else {
- getSharedControl()->clear();
- mData = (uint8_t *)mMemory->pointer() + sizeof(SharedControl);
- ALOGV("Allocated shared mem buffer of size %zu @ %p", size, mData);
- }
- }
-}
-
-MediaBuffer::MediaBuffer(const sp<ABuffer> &buffer)
- : mObserver(NULL),
- mRefCount(0),
- mData(buffer->data()),
- mSize(buffer->size()),
- mRangeOffset(0),
- mRangeLength(mSize),
- mBuffer(buffer),
- mOwnsData(false),
- mMetaData(new MetaData),
- mOriginal(NULL) {
-}
-
-void MediaBuffer::release() {
- if (mObserver == NULL) {
- // Legacy contract for MediaBuffer without a MediaBufferGroup.
- CHECK_EQ(mRefCount, 0);
- delete this;
- return;
- }
-
- int prevCount = __sync_fetch_and_sub(&mRefCount, 1);
- if (prevCount == 1) {
- if (mObserver == NULL) {
- delete this;
- return;
- }
-
- mObserver->signalBufferReturned(this);
- }
- CHECK(prevCount > 0);
-}
-
-void MediaBuffer::claim() {
- CHECK(mObserver != NULL);
- CHECK_EQ(mRefCount, 1);
-
- mRefCount = 0;
-}
-
-void MediaBuffer::add_ref() {
- (void) __sync_fetch_and_add(&mRefCount, 1);
-}
-
-void *MediaBuffer::data() const {
- return mData;
-}
-
-size_t MediaBuffer::size() const {
- return mSize;
-}
-
-size_t MediaBuffer::range_offset() const {
- return mRangeOffset;
-}
-
-size_t MediaBuffer::range_length() const {
- return mRangeLength;
-}
-
-void MediaBuffer::set_range(size_t offset, size_t length) {
- if (offset + length > mSize) {
- ALOGE("offset = %zu, length = %zu, mSize = %zu", offset, length, mSize);
- }
- CHECK(offset + length <= mSize);
-
- mRangeOffset = offset;
- mRangeLength = length;
-}
-
-sp<MetaData> MediaBuffer::meta_data() {
- return mMetaData;
-}
-
-void MediaBuffer::reset() {
- mMetaData->clear();
- set_range(0, mSize);
-}
-
-MediaBuffer::~MediaBuffer() {
- CHECK(mObserver == NULL);
-
- if (mOwnsData && mData != NULL && mMemory == NULL) {
- free(mData);
- mData = NULL;
- }
-
- if (mOriginal != NULL) {
- mOriginal->release();
- mOriginal = NULL;
- }
-
- if (mMemory.get() != nullptr) {
- getSharedControl()->setDeadObject();
- }
-}
-
-void MediaBuffer::setObserver(MediaBufferObserver *observer) {
- CHECK(observer == NULL || mObserver == NULL);
- mObserver = observer;
-}
-
-MediaBuffer *MediaBuffer::clone() {
- MediaBuffer *buffer = new MediaBuffer(mData, mSize);
- buffer->set_range(mRangeOffset, mRangeLength);
- buffer->mMetaData = new MetaData(*mMetaData.get());
-
- add_ref();
- buffer->mOriginal = this;
-
- return buffer;
-}
-
-} // namespace android
diff --git a/media/libstagefright/foundation/MetaData.cpp b/media/libstagefright/foundation/MetaData.cpp
index a8965f0..2415c61 100644
--- a/media/libstagefright/foundation/MetaData.cpp
+++ b/media/libstagefright/foundation/MetaData.cpp
@@ -17,6 +17,7 @@
//#define LOG_NDEBUG 0
#define LOG_TAG "MetaData"
#include <inttypes.h>
+#include <utils/KeyedVector.h>
#include <utils/Log.h>
#include <stdlib.h>
@@ -29,30 +30,81 @@
namespace android {
-MetaData::MetaData() {
+struct MetaData::typed_data {
+ typed_data();
+ ~typed_data();
+
+ typed_data(const MetaData::typed_data &);
+ typed_data &operator=(const MetaData::typed_data &);
+
+ void clear();
+ void setData(uint32_t type, const void *data, size_t size);
+ void getData(uint32_t *type, const void **data, size_t *size) const;
+ // may include hexdump of binary data if verbose=true
+ String8 asString(bool verbose) const;
+
+private:
+ uint32_t mType;
+ size_t mSize;
+
+ union {
+ void *ext_data;
+ float reservoir;
+ } u;
+
+ bool usesReservoir() const {
+ return mSize <= sizeof(u.reservoir);
+ }
+
+ void *allocateStorage(size_t size);
+ void freeStorage();
+
+ void *storage() {
+ return usesReservoir() ? &u.reservoir : u.ext_data;
+ }
+
+ const void *storage() const {
+ return usesReservoir() ? &u.reservoir : u.ext_data;
+ }
+};
+
+struct MetaData::Rect {
+ int32_t mLeft, mTop, mRight, mBottom;
+};
+
+
+struct MetaData::MetaDataInternal {
+ KeyedVector<uint32_t, MetaData::typed_data> mItems;
+};
+
+
+MetaData::MetaData()
+ : mInternalData(new MetaDataInternal()) {
}
MetaData::MetaData(const MetaData &from)
: RefBase(),
- mItems(from.mItems) {
+ mInternalData(new MetaDataInternal()) {
+ mInternalData->mItems = from.mInternalData->mItems;
}
MetaData::~MetaData() {
clear();
+ delete mInternalData;
}
void MetaData::clear() {
- mItems.clear();
+ mInternalData->mItems.clear();
}
bool MetaData::remove(uint32_t key) {
- ssize_t i = mItems.indexOfKey(key);
+ ssize_t i = mInternalData->mItems.indexOfKey(key);
if (i < 0) {
return false;
}
- mItems.removeItemsAt(i);
+ mInternalData->mItems.removeItemsAt(i);
return true;
}
@@ -192,15 +244,15 @@
uint32_t key, uint32_t type, const void *data, size_t size) {
bool overwrote_existing = true;
- ssize_t i = mItems.indexOfKey(key);
+ ssize_t i = mInternalData->mItems.indexOfKey(key);
if (i < 0) {
typed_data item;
- i = mItems.add(key, item);
+ i = mInternalData->mItems.add(key, item);
overwrote_existing = false;
}
- typed_data &item = mItems.editValueAt(i);
+ typed_data &item = mInternalData->mItems.editValueAt(i);
item.setData(type, data, size);
@@ -209,13 +261,13 @@
bool MetaData::findData(uint32_t key, uint32_t *type,
const void **data, size_t *size) const {
- ssize_t i = mItems.indexOfKey(key);
+ ssize_t i = mInternalData->mItems.indexOfKey(key);
if (i < 0) {
return false;
}
- const typed_data &item = mItems.valueAt(i);
+ const typed_data &item = mInternalData->mItems.valueAt(i);
item.getData(type, data, size);
@@ -223,7 +275,7 @@
}
bool MetaData::hasData(uint32_t key) const {
- ssize_t i = mItems.indexOfKey(key);
+ ssize_t i = mInternalData->mItems.indexOfKey(key);
if (i < 0) {
return false;
@@ -369,11 +421,11 @@
String8 MetaData::toString() const {
String8 s;
- for (int i = mItems.size(); --i >= 0;) {
- int32_t key = mItems.keyAt(i);
+ for (int i = mInternalData->mItems.size(); --i >= 0;) {
+ int32_t key = mInternalData->mItems.keyAt(i);
char cc[5];
MakeFourCCString(key, cc);
- const typed_data &item = mItems.valueAt(i);
+ const typed_data &item = mInternalData->mItems.valueAt(i);
s.appendFormat("%s: %s", cc, item.asString(false).string());
if (i != 0) {
s.append(", ");
@@ -382,25 +434,25 @@
return s;
}
void MetaData::dumpToLog() const {
- for (int i = mItems.size(); --i >= 0;) {
- int32_t key = mItems.keyAt(i);
+ for (int i = mInternalData->mItems.size(); --i >= 0;) {
+ int32_t key = mInternalData->mItems.keyAt(i);
char cc[5];
MakeFourCCString(key, cc);
- const typed_data &item = mItems.valueAt(i);
+ const typed_data &item = mInternalData->mItems.valueAt(i);
ALOGI("%s: %s", cc, item.asString(true /* verbose */).string());
}
}
status_t MetaData::writeToParcel(Parcel &parcel) {
status_t ret;
- size_t numItems = mItems.size();
+ size_t numItems = mInternalData->mItems.size();
ret = parcel.writeUint32(uint32_t(numItems));
if (ret) {
return ret;
}
for (size_t i = 0; i < numItems; i++) {
- int32_t key = mItems.keyAt(i);
- const typed_data &item = mItems.valueAt(i);
+ int32_t key = mInternalData->mItems.keyAt(i);
+ const typed_data &item = mInternalData->mItems.valueAt(i);
uint32_t type;
const void *data;
size_t size;
diff --git a/media/libstagefright/foundation/include/media/stagefright/foundation/ABuffer.h b/media/libstagefright/foundation/include/media/stagefright/foundation/ABuffer.h
index ef11434..8fe9f8d 100644
--- a/media/libstagefright/foundation/include/media/stagefright/foundation/ABuffer.h
+++ b/media/libstagefright/foundation/include/media/stagefright/foundation/ABuffer.h
@@ -27,7 +27,6 @@
namespace android {
struct AMessage;
-class MediaBufferBase;
struct ABuffer : public RefBase {
explicit ABuffer(size_t capacity);
@@ -49,17 +48,12 @@
sp<AMessage> meta();
- MediaBufferBase *getMediaBufferBase();
- void setMediaBufferBase(MediaBufferBase *mediaBuffer);
-
protected:
virtual ~ABuffer();
private:
sp<AMessage> mMeta;
- MediaBufferBase *mMediaBufferBase;
-
void *mData;
size_t mCapacity;
size_t mRangeOffset;
diff --git a/media/libstagefright/foundation/include/media/stagefright/foundation/ByteUtils.h b/media/libstagefright/foundation/include/media/stagefright/foundation/ByteUtils.h
index dc4125f..a434f81 100644
--- a/media/libstagefright/foundation/include/media/stagefright/foundation/ByteUtils.h
+++ b/media/libstagefright/foundation/include/media/stagefright/foundation/ByteUtils.h
@@ -22,8 +22,20 @@
namespace android {
-#define FOURCC(c1, c2, c3, c4) \
- ((c1) << 24 | (c2) << 16 | (c3) << 8 | (c4))
+constexpr int FOURCC(unsigned char c1, unsigned char c2, unsigned char c3, unsigned char c4) {
+ return ((c1) << 24 | (c2) << 16 | (c3) << 8 | (c4));
+}
+
+template <size_t N>
+constexpr int32_t FOURCC(const char (&s) [N]) {
+ static_assert(N == 5, "fourcc: wrong length");
+ return
+ (unsigned char) s[0] << 24 |
+ (unsigned char) s[1] << 16 |
+ (unsigned char) s[2] << 8 |
+ (unsigned char) s[3] << 0;
+}
+
uint16_t U16_AT(const uint8_t *ptr);
uint32_t U32_AT(const uint8_t *ptr);
diff --git a/media/libstagefright/foundation/include/media/stagefright/foundation/MediaBufferBase.h b/media/libstagefright/foundation/include/media/stagefright/foundation/MediaBufferBase.h
deleted file mode 100644
index 99418fb..0000000
--- a/media/libstagefright/foundation/include/media/stagefright/foundation/MediaBufferBase.h
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright 2014 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 MEDIA_BUFFER_BASE_H_
-
-#define MEDIA_BUFFER_BASE_H_
-
-namespace android {
-
-class MediaBufferBase {
-public:
- MediaBufferBase() {}
-
- virtual void release() = 0;
- virtual void add_ref() = 0;
-
-protected:
- virtual ~MediaBufferBase() {}
-
-private:
- MediaBufferBase(const MediaBufferBase &);
- MediaBufferBase &operator=(const MediaBufferBase &);
-};
-
-} // namespace android
-
-#endif // MEDIA_BUFFER_BASE_H_
diff --git a/media/libstagefright/gbs/Android.bp b/media/libstagefright/gbs/Android.bp
new file mode 100644
index 0000000..a53b7b7
--- /dev/null
+++ b/media/libstagefright/gbs/Android.bp
@@ -0,0 +1,58 @@
+cc_library_shared {
+ name: "libstagefright_gbs",
+ vendor_available: true,
+ vndk: {
+ enabled: true,
+ },
+
+ srcs: [
+ "FrameDropper.cpp",
+ "GraphicBufferSource.cpp",
+ ],
+
+ export_include_dirs: [
+ "include",
+ ],
+
+ header_libs: [
+ "media_plugin_headers",
+ ],
+
+ export_header_lib_headers: [
+ "media_plugin_headers",
+ ],
+
+ shared_libs: [
+ "libbase",
+ "libbinder",
+ "libutils",
+ "liblog",
+ "libui",
+ "libgui",
+ "libcutils",
+ "libstagefright_foundation",
+ "libnativewindow", // TODO(b/62923479): use header library
+ ],
+
+ export_shared_lib_headers: [
+ "libstagefright_foundation",
+ ],
+
+ cflags: [
+ "-Werror",
+ "-Wall",
+ "-Wno-unused-parameter",
+ "-Wno-documentation",
+ ],
+
+ sanitize: {
+ misc_undefined: [
+ "signed-integer-overflow",
+ "unsigned-integer-overflow",
+ ],
+ cfi: true,
+ diag: {
+ cfi: true,
+ },
+ },
+}
diff --git a/media/libstagefright/gbs/FrameDropper.cpp b/media/libstagefright/gbs/FrameDropper.cpp
new file mode 100644
index 0000000..9f0b8cc
--- /dev/null
+++ b/media/libstagefright/gbs/FrameDropper.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2015 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 "FrameDropper"
+#include <utils/Log.h>
+
+#include <media/stagefright/gbs/FrameDropper.h>
+#include <media/stagefright/foundation/ADebug.h>
+
+namespace android {
+
+static const int64_t kMaxJitterUs = 2000;
+
+FrameDropper::FrameDropper()
+ : mDesiredMinTimeUs(-1),
+ mMinIntervalUs(0) {
+}
+
+FrameDropper::~FrameDropper() {
+}
+
+status_t FrameDropper::setMaxFrameRate(float maxFrameRate) {
+ if (maxFrameRate <= 0) {
+ ALOGE("framerate should be positive but got %f.", maxFrameRate);
+ return BAD_VALUE;
+ }
+ mMinIntervalUs = (int64_t) (1000000.0f / maxFrameRate);
+ return OK;
+}
+
+bool FrameDropper::shouldDrop(int64_t timeUs) {
+ if (mMinIntervalUs <= 0) {
+ return false;
+ }
+
+ if (mDesiredMinTimeUs < 0) {
+ mDesiredMinTimeUs = timeUs + mMinIntervalUs;
+ ALOGV("first frame %lld, next desired frame %lld",
+ (long long)timeUs, (long long)mDesiredMinTimeUs);
+ return false;
+ }
+
+ if (timeUs < (mDesiredMinTimeUs - kMaxJitterUs)) {
+ ALOGV("drop frame %lld, desired frame %lld, diff %lld",
+ (long long)timeUs, (long long)mDesiredMinTimeUs,
+ (long long)(mDesiredMinTimeUs - timeUs));
+ return true;
+ }
+
+ int64_t n = (timeUs - mDesiredMinTimeUs + kMaxJitterUs) / mMinIntervalUs;
+ mDesiredMinTimeUs += (n + 1) * mMinIntervalUs;
+ ALOGV("keep frame %lld, next desired frame %lld, diff %lld",
+ (long long)timeUs, (long long)mDesiredMinTimeUs,
+ (long long)(mDesiredMinTimeUs - timeUs));
+ return false;
+}
+
+} // namespace android
diff --git a/media/libstagefright/gbs/GraphicBufferSource.cpp b/media/libstagefright/gbs/GraphicBufferSource.cpp
new file mode 100644
index 0000000..139c916
--- /dev/null
+++ b/media/libstagefright/gbs/GraphicBufferSource.cpp
@@ -0,0 +1,1365 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <inttypes.h>
+
+#define LOG_TAG "GraphicBufferSource"
+//#define LOG_NDEBUG 0
+#include <utils/Log.h>
+
+#define STRINGIFY_ENUMS // for asString in HardwareAPI.h/VideoAPI.h
+
+#include <media/stagefright/gbs/GraphicBufferSource.h>
+#include <media/stagefright/gbs/FrameDropper.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/foundation/ColorUtils.h>
+#include <media/stagefright/foundation/FileDescriptor.h>
+
+#include <media/hardware/MetadataBufferType.h>
+#include <ui/GraphicBuffer.h>
+#include <gui/BufferItem.h>
+#include <media/hardware/HardwareAPI.h>
+
+#include <inttypes.h>
+
+#include <functional>
+#include <memory>
+#include <cmath>
+
+namespace android {
+
+namespace {
+// kTimestampFluctuation is an upper bound of timestamp fluctuation from the
+// source that GraphicBufferSource allows. The unit of kTimestampFluctuation is
+// frames. More specifically, GraphicBufferSource will drop a frame if
+//
+// expectedNewFrametimestamp - actualNewFrameTimestamp <
+// (0.5 - kTimestampFluctuation) * expectedtimePeriodBetweenFrames
+//
+// where
+// - expectedNewFrameTimestamp is the calculated ideal timestamp of the new
+// incoming frame
+// - actualNewFrameTimestamp is the timestamp received from the source
+// - expectedTimePeriodBetweenFrames is the ideal difference of the timestamps
+// of two adjacent frames
+//
+// See GraphicBufferSource::calculateCodecTimestamp_l() for more detail about
+// how kTimestampFluctuation is used.
+//
+// kTimestampFluctuation should be non-negative. A higher value causes a smaller
+// chance of dropping frames, but at the same time a higher bound on the
+// difference between the source timestamp and the interpreted (snapped)
+// timestamp.
+//
+// The value of 0.05 means that GraphicBufferSource expects the input timestamps
+// to fluctuate no more than 5% from the regular time period.
+//
+// TODO: Justify the choice of this value, or make it configurable.
+constexpr double kTimestampFluctuation = 0.05;
+}
+
+/**
+ * A copiable object managing a buffer in the buffer cache managed by the producer. This object
+ * holds a reference to the buffer, and maintains which buffer slot it belongs to (if any), and
+ * whether it is still in a buffer slot. It also maintains whether there are any outstanging acquire
+ * references to it (by buffers acquired from the slot) mainly so that we can keep a debug
+ * count of how many buffers we need to still release back to the producer.
+ */
+struct GraphicBufferSource::CachedBuffer {
+ /**
+ * Token that is used to track acquire counts (as opposed to all references to this object).
+ */
+ struct Acquirable { };
+
+ /**
+ * Create using a buffer cached in a slot.
+ */
+ CachedBuffer(slot_id slot, const sp<GraphicBuffer> &graphicBuffer)
+ : mIsCached(true),
+ mSlot(slot),
+ mGraphicBuffer(graphicBuffer),
+ mAcquirable(std::make_shared<Acquirable>()) {
+ }
+
+ /**
+ * Returns the cache slot that this buffer is cached in, or -1 if it is no longer cached.
+ *
+ * This assumes that -1 slot id is invalid; though, it is just a benign collision used for
+ * debugging. This object explicitly manages whether it is still cached.
+ */
+ slot_id getSlot() const {
+ return mIsCached ? mSlot : -1;
+ }
+
+ /**
+ * Returns the cached buffer.
+ */
+ sp<GraphicBuffer> getGraphicBuffer() const {
+ return mGraphicBuffer;
+ }
+
+ /**
+ * Checks whether this buffer is still in the buffer cache.
+ */
+ bool isCached() const {
+ return mIsCached;
+ }
+
+ /**
+ * Checks whether this buffer has an acquired reference.
+ */
+ bool isAcquired() const {
+ return mAcquirable.use_count() > 1;
+ }
+
+ /**
+ * Gets and returns a shared acquired reference.
+ */
+ std::shared_ptr<Acquirable> getAcquirable() {
+ return mAcquirable;
+ }
+
+private:
+ friend void GraphicBufferSource::discardBufferAtSlotIndex_l(ssize_t);
+
+ /**
+ * This method to be called when the buffer is no longer in the buffer cache.
+ * Called from discardBufferAtSlotIndex_l.
+ */
+ void onDroppedFromCache() {
+ CHECK_DBG(mIsCached);
+ mIsCached = false;
+ }
+
+ bool mIsCached;
+ slot_id mSlot;
+ sp<GraphicBuffer> mGraphicBuffer;
+ std::shared_ptr<Acquirable> mAcquirable;
+};
+
+/**
+ * A copiable object managing a buffer acquired from the producer. This must always be a cached
+ * buffer. This objects also manages its acquire fence and any release fences that may be returned
+ * by the encoder for this buffer (this buffer may be queued to the encoder multiple times).
+ * If no release fences are added by the encoder, the acquire fence is returned as the release
+ * fence for this - as it is assumed that noone waited for the acquire fence. Otherwise, it is
+ * assumed that the encoder has waited for the acquire fence (or returned it as the release
+ * fence).
+ */
+struct GraphicBufferSource::AcquiredBuffer {
+ AcquiredBuffer(
+ const std::shared_ptr<CachedBuffer> &buffer,
+ std::function<void(AcquiredBuffer *)> onReleased,
+ const sp<Fence> &acquireFence)
+ : mBuffer(buffer),
+ mAcquirable(buffer->getAcquirable()),
+ mAcquireFence(acquireFence),
+ mGotReleaseFences(false),
+ mOnReleased(onReleased) {
+ }
+
+ /**
+ * Adds a release fence returned by the encoder to this object. If this is called with an
+ * valid file descriptor, it is added to the list of release fences. These are returned to the
+ * producer on release() as a merged fence. Regardless of the validity of the file descriptor,
+ * we take note that a release fence was attempted to be added and the acquire fence can now be
+ * assumed as acquired.
+ */
+ void addReleaseFenceFd(int fenceFd) {
+ // save all release fences - these will be propagated to the producer if this buffer is
+ // ever released to it
+ if (fenceFd >= 0) {
+ mReleaseFenceFds.push_back(fenceFd);
+ }
+ mGotReleaseFences = true;
+ }
+
+ /**
+ * Returns the acquire fence file descriptor associated with this object.
+ */
+ int getAcquireFenceFd() {
+ if (mAcquireFence == nullptr || !mAcquireFence->isValid()) {
+ return -1;
+ }
+ return mAcquireFence->dup();
+ }
+
+ /**
+ * Returns whether the buffer is still in the buffer cache.
+ */
+ bool isCached() const {
+ return mBuffer->isCached();
+ }
+
+ /**
+ * Returns the acquired buffer.
+ */
+ sp<GraphicBuffer> getGraphicBuffer() const {
+ return mBuffer->getGraphicBuffer();
+ }
+
+ /**
+ * Returns the slot that this buffer is cached at, or -1 otherwise.
+ *
+ * This assumes that -1 slot id is invalid; though, it is just a benign collision used for
+ * debugging. This object explicitly manages whether it is still cached.
+ */
+ slot_id getSlot() const {
+ return mBuffer->getSlot();
+ }
+
+ /**
+ * Creates and returns a release fence object from the acquire fence and/or any release fences
+ * added. If no release fences were added (even if invalid), returns the acquire fence.
+ * Otherwise, it returns a merged fence from all the valid release fences added.
+ */
+ sp<Fence> getReleaseFence() {
+ // If did not receive release fences, we assume this buffer was not consumed (it was
+ // discarded or dropped). In this case release the acquire fence as the release fence.
+ // We do this here to avoid a dup, close and recreation of the Fence object.
+ if (!mGotReleaseFences) {
+ return mAcquireFence;
+ }
+ sp<Fence> ret = getReleaseFence(0, mReleaseFenceFds.size());
+ // clear fds as fence took ownership of them
+ mReleaseFenceFds.clear();
+ return ret;
+ }
+
+ // this video buffer is no longer referenced by the codec (or kept for later encoding)
+ // it is now safe to release to the producer
+ ~AcquiredBuffer() {
+ //mAcquirable.clear();
+ mOnReleased(this);
+ // mOnRelease method should call getReleaseFence() that releases all fds but just in case
+ ALOGW_IF(!mReleaseFenceFds.empty(), "release fences were not obtained, closing fds");
+ for (int fildes : mReleaseFenceFds) {
+ ::close(fildes);
+ TRESPASS_DBG();
+ }
+ }
+
+private:
+ std::shared_ptr<GraphicBufferSource::CachedBuffer> mBuffer;
+ std::shared_ptr<GraphicBufferSource::CachedBuffer::Acquirable> mAcquirable;
+ sp<Fence> mAcquireFence;
+ Vector<int> mReleaseFenceFds;
+ bool mGotReleaseFences;
+ std::function<void(AcquiredBuffer *)> mOnReleased;
+
+ /**
+ * Creates and returns a release fence from 0 or more release fence file descriptors in from
+ * the specified range in the array.
+ *
+ * @param start start index
+ * @param num number of release fds to merge
+ */
+ sp<Fence> getReleaseFence(size_t start, size_t num) const {
+ if (num == 0) {
+ return Fence::NO_FENCE;
+ } else if (num == 1) {
+ return new Fence(mReleaseFenceFds[start]);
+ } else {
+ return Fence::merge("GBS::AB",
+ getReleaseFence(start, num >> 1),
+ getReleaseFence(start + (num >> 1), num - (num >> 1)));
+ }
+ }
+};
+
+GraphicBufferSource::GraphicBufferSource() :
+ mInitCheck(UNKNOWN_ERROR),
+ mNumAvailableUnacquiredBuffers(0),
+ mNumOutstandingAcquires(0),
+ mEndOfStream(false),
+ mEndOfStreamSent(false),
+ mLastDataspace(HAL_DATASPACE_UNKNOWN),
+ mExecuting(false),
+ mSuspended(false),
+ mLastFrameTimestampUs(-1),
+ mStopTimeUs(-1),
+ mLastActionTimeUs(-1ll),
+ mSkipFramesBeforeNs(-1ll),
+ mFrameRepeatIntervalUs(-1ll),
+ mRepeatLastFrameGeneration(0),
+ mOutstandingFrameRepeatCount(0),
+ mFrameRepeatBlockedOnCodecBuffer(false),
+ mFps(-1.0),
+ mCaptureFps(-1.0),
+ mBaseCaptureUs(-1ll),
+ mBaseFrameUs(-1ll),
+ mFrameCount(0),
+ mPrevCaptureUs(-1ll),
+ mPrevFrameUs(-1ll),
+ mInputBufferTimeOffsetUs(0ll) {
+ ALOGV("GraphicBufferSource");
+
+ String8 name("GraphicBufferSource");
+
+ BufferQueue::createBufferQueue(&mProducer, &mConsumer);
+ mConsumer->setConsumerName(name);
+
+ // Note that we can't create an sp<...>(this) in a ctor that will not keep a
+ // reference once the ctor ends, as that would cause the refcount of 'this'
+ // dropping to 0 at the end of the ctor. Since all we need is a wp<...>
+ // that's what we create.
+ wp<BufferQueue::ConsumerListener> listener =
+ static_cast<BufferQueue::ConsumerListener*>(this);
+ sp<IConsumerListener> proxy =
+ new BufferQueue::ProxyConsumerListener(listener);
+
+ mInitCheck = mConsumer->consumerConnect(proxy, false);
+ if (mInitCheck != NO_ERROR) {
+ ALOGE("Error connecting to BufferQueue: %s (%d)",
+ strerror(-mInitCheck), mInitCheck);
+ return;
+ }
+
+ memset(&mDefaultColorAspectsPacked, 0, sizeof(mDefaultColorAspectsPacked));
+
+ CHECK(mInitCheck == NO_ERROR);
+}
+
+GraphicBufferSource::~GraphicBufferSource() {
+ ALOGV("~GraphicBufferSource");
+ {
+ // all acquired buffers must be freed with the mutex locked otherwise our debug assertion
+ // may trigger
+ Mutex::Autolock autoLock(mMutex);
+ mAvailableBuffers.clear();
+ mSubmittedCodecBuffers.clear();
+ mLatestBuffer.mBuffer.reset();
+ }
+
+ if (mNumOutstandingAcquires != 0) {
+ ALOGW("potential buffer leak: acquired=%d", mNumOutstandingAcquires);
+ TRESPASS_DBG();
+ }
+ if (mConsumer != NULL) {
+ status_t err = mConsumer->consumerDisconnect();
+ if (err != NO_ERROR) {
+ ALOGW("consumerDisconnect failed: %d", err);
+ }
+ }
+}
+
+Status GraphicBufferSource::start() {
+ Mutex::Autolock autoLock(mMutex);
+ ALOGV("--> start; available=%zu, submittable=%zd",
+ mAvailableBuffers.size(), mFreeCodecBuffers.size());
+ CHECK(!mExecuting);
+ mExecuting = true;
+ mLastDataspace = HAL_DATASPACE_UNKNOWN;
+ ALOGV("clearing last dataSpace");
+
+ // Start by loading up as many buffers as possible. We want to do this,
+ // rather than just submit the first buffer, to avoid a degenerate case:
+ // if all BQ buffers arrive before we start executing, and we only submit
+ // one here, the other BQ buffers will just sit until we get notified
+ // that the codec buffer has been released. We'd then acquire and
+ // submit a single additional buffer, repeatedly, never using more than
+ // one codec buffer simultaneously. (We could instead try to submit
+ // all BQ buffers whenever any codec buffer is freed, but if we get the
+ // initial conditions right that will never be useful.)
+ while (haveAvailableBuffers_l()) {
+ if (!fillCodecBuffer_l()) {
+ ALOGV("stop load with available=%zu+%d",
+ mAvailableBuffers.size(), mNumAvailableUnacquiredBuffers);
+ break;
+ }
+ }
+
+ ALOGV("done loading initial frames, available=%zu+%d",
+ mAvailableBuffers.size(), mNumAvailableUnacquiredBuffers);
+
+ // If EOS has already been signaled, and there are no more frames to
+ // submit, try to send EOS now as well.
+ if (mStopTimeUs == -1 && mEndOfStream && !haveAvailableBuffers_l()) {
+ submitEndOfInputStream_l();
+ }
+
+ if (mFrameRepeatIntervalUs > 0ll && mLooper == NULL) {
+ mReflector = new AHandlerReflector<GraphicBufferSource>(this);
+
+ mLooper = new ALooper;
+ mLooper->registerHandler(mReflector);
+ mLooper->start();
+
+ if (mLatestBuffer.mBuffer != nullptr) {
+ queueFrameRepeat_l();
+ }
+ }
+
+ return Status::ok();
+}
+
+Status GraphicBufferSource::stop() {
+ ALOGV("stop");
+
+ Mutex::Autolock autoLock(mMutex);
+
+ if (mExecuting) {
+ // We are only interested in the transition from executing->idle,
+ // not loaded->idle.
+ mExecuting = false;
+ }
+ return Status::ok();
+}
+
+Status GraphicBufferSource::release(){
+ Mutex::Autolock autoLock(mMutex);
+ if (mLooper != NULL) {
+ mLooper->unregisterHandler(mReflector->id());
+ mReflector.clear();
+
+ mLooper->stop();
+ 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;
+
+ return Status::ok();
+}
+
+Status GraphicBufferSource::onInputBufferAdded(codec_buffer_id bufferId) {
+ Mutex::Autolock autoLock(mMutex);
+
+ if (mExecuting) {
+ // This should never happen -- buffers can only be allocated when
+ // transitioning from "loaded" to "idle".
+ ALOGE("addCodecBuffer: buffer added while executing");
+ return Status::fromServiceSpecificError(INVALID_OPERATION);
+ }
+
+ ALOGV("addCodecBuffer: bufferId=%u", bufferId);
+
+ mFreeCodecBuffers.push_back(bufferId);
+ return Status::ok();
+}
+
+Status GraphicBufferSource::onInputBufferEmptied(codec_buffer_id bufferId, int fenceFd) {
+ Mutex::Autolock autoLock(mMutex);
+ FileDescriptor::Autoclose fence(fenceFd);
+
+ ssize_t cbi = mSubmittedCodecBuffers.indexOfKey(bufferId);
+ if (cbi < 0) {
+ // This should never happen.
+ ALOGE("onInputBufferEmptied: buffer not recognized (bufferId=%u)", bufferId);
+ return Status::fromServiceSpecificError(BAD_VALUE);
+ }
+
+ std::shared_ptr<AcquiredBuffer> buffer = mSubmittedCodecBuffers.valueAt(cbi);
+
+ // Move buffer to available buffers
+ mSubmittedCodecBuffers.removeItemsAt(cbi);
+ mFreeCodecBuffers.push_back(bufferId);
+
+ // header->nFilledLen may not be the original value, so we can't compare
+ // that to zero to see of this was the EOS buffer. Instead we just
+ // see if there is a null AcquiredBuffer, which should only ever happen for EOS.
+ if (buffer == nullptr) {
+ if (!(mEndOfStream && mEndOfStreamSent)) {
+ // This can happen when broken code sends us the same buffer twice in a row.
+ ALOGE("onInputBufferEmptied: non-EOS null buffer (bufferId=%u)", bufferId);
+ } else {
+ ALOGV("onInputBufferEmptied: EOS null buffer (bufferId=%u@%zd)", bufferId, cbi);
+ }
+ // No GraphicBuffer to deal with, no additional input or output is expected, so just return.
+ return Status::fromServiceSpecificError(BAD_VALUE);
+ }
+
+ if (!mExecuting) {
+ // this is fine since this could happen when going from Idle to Loaded
+ ALOGV("onInputBufferEmptied: no longer executing (bufferId=%u@%zd)", bufferId, cbi);
+ return Status::fromServiceSpecificError(OK);
+ }
+
+ ALOGV("onInputBufferEmptied: bufferId=%d@%zd [slot=%d, useCount=%ld, handle=%p] acquired=%d",
+ bufferId, cbi, buffer->getSlot(), buffer.use_count(), buffer->getGraphicBuffer()->handle,
+ mNumOutstandingAcquires);
+
+ buffer->addReleaseFenceFd(fence.release());
+ // release codec reference for video buffer just in case remove does not it
+ buffer.reset();
+
+ if (haveAvailableBuffers_l()) {
+ // Fill this codec buffer.
+ CHECK(!mEndOfStreamSent);
+ ALOGV("onInputBufferEmptied: buffer freed, feeding codec (available=%zu+%d, eos=%d)",
+ mAvailableBuffers.size(), mNumAvailableUnacquiredBuffers, mEndOfStream);
+ fillCodecBuffer_l();
+ } else if (mEndOfStream && mStopTimeUs == -1) {
+ // No frames available, but EOS is pending and no stop time, so use this buffer to
+ // send that.
+ ALOGV("onInputBufferEmptied: buffer freed, submitting EOS");
+ submitEndOfInputStream_l();
+ } else if (mFrameRepeatBlockedOnCodecBuffer) {
+ bool success = repeatLatestBuffer_l();
+ ALOGV("onInputBufferEmptied: completing deferred repeatLatestBuffer_l %s",
+ success ? "SUCCESS" : "FAILURE");
+ mFrameRepeatBlockedOnCodecBuffer = false;
+ }
+
+ // releaseReleasableBuffers_l();
+ return Status::ok();
+}
+
+void GraphicBufferSource::onDataspaceChanged_l(
+ android_dataspace dataspace, android_pixel_format pixelFormat) {
+ ALOGD("got buffer with new dataSpace #%x", dataspace);
+ mLastDataspace = dataspace;
+
+ if (ColorUtils::convertDataSpaceToV0(dataspace)) {
+ mComponent->dispatchDataSpaceChanged(
+ mLastDataspace, mDefaultColorAspectsPacked, pixelFormat);
+ }
+}
+
+bool GraphicBufferSource::fillCodecBuffer_l() {
+ CHECK(mExecuting && haveAvailableBuffers_l());
+
+ if (mFreeCodecBuffers.empty()) {
+ // No buffers available, bail.
+ ALOGV("fillCodecBuffer_l: no codec buffers, available=%zu+%d",
+ mAvailableBuffers.size(), mNumAvailableUnacquiredBuffers);
+ return false;
+ }
+
+ VideoBuffer item;
+ if (mAvailableBuffers.empty()) {
+ ALOGV("fillCodecBuffer_l: acquiring available buffer, available=%zu+%d",
+ mAvailableBuffers.size(), mNumAvailableUnacquiredBuffers);
+ if (acquireBuffer_l(&item) != OK) {
+ ALOGE("fillCodecBuffer_l: failed to acquire available buffer");
+ return false;
+ }
+ } else {
+ ALOGV("fillCodecBuffer_l: getting available buffer, available=%zu+%d",
+ mAvailableBuffers.size(), mNumAvailableUnacquiredBuffers);
+ item = *mAvailableBuffers.begin();
+ mAvailableBuffers.erase(mAvailableBuffers.begin());
+ }
+
+ int64_t itemTimeUs = item.mTimestampNs / 1000;
+
+ // Process ActionItem in the Queue if there is any. If a buffer's timestamp
+ // is smaller than the first action's timestamp, no action need to be performed.
+ // If buffer's timestamp is larger or equal than the last action's timestamp,
+ // only the last action needs to be performed as all the acitions before the
+ // the action are overridden by the last action. For the other cases, traverse
+ // the Queue to find the newest action that with timestamp smaller or equal to
+ // the buffer's timestamp. For example, an action queue like
+ // [pause 1us], [resume 2us], [pause 3us], [resume 4us], [pause 5us].... Upon
+ // receiving a buffer with timestamp 3.5us, only the action [pause, 3us] needs
+ // to be handled and [pause, 1us], [resume 2us] will be discarded.
+ bool done = false;
+ bool seeStopAction = false;
+ if (!mActionQueue.empty()) {
+ // First scan to check if bufferTimestamp is smaller than first action's timestamp.
+ ActionItem nextAction = *(mActionQueue.begin());
+ if (itemTimeUs < nextAction.mActionTimeUs) {
+ ALOGV("No action. buffer timestamp %lld us < action timestamp: %lld us",
+ (long long)itemTimeUs, (long long)nextAction.mActionTimeUs);
+ // All the actions are ahead. No action need to perform now.
+ // Release the buffer if is in suspended state, or process the buffer
+ // if not in suspended state.
+ done = true;
+ }
+
+ if (!done) {
+ // Find the newest action that with timestamp smaller than itemTimeUs. Then
+ // remove all the actions before and include the newest action.
+ List<ActionItem>::iterator it = mActionQueue.begin();
+ while (it != mActionQueue.end() && it->mActionTimeUs <= itemTimeUs
+ && nextAction.mAction != ActionItem::STOP) {
+ nextAction = *it;
+ ++it;
+ }
+ mActionQueue.erase(mActionQueue.begin(), it);
+
+ CHECK(itemTimeUs >= nextAction.mActionTimeUs);
+ switch (nextAction.mAction) {
+ case ActionItem::PAUSE:
+ {
+ mSuspended = true;
+ ALOGV("RUNNING/PAUSE -> PAUSE at buffer %lld us PAUSE Time: %lld us",
+ (long long)itemTimeUs, (long long)nextAction.mActionTimeUs);
+ break;
+ }
+ case ActionItem::RESUME:
+ {
+ mSuspended = false;
+ ALOGV("PAUSE/RUNNING -> RUNNING at buffer %lld us RESUME Time: %lld us",
+ (long long)itemTimeUs, (long long)nextAction.mActionTimeUs);
+ break;
+ }
+ case ActionItem::STOP:
+ {
+ ALOGV("RUNNING/PAUSE -> STOP at buffer %lld us STOP Time: %lld us",
+ (long long)itemTimeUs, (long long)nextAction.mActionTimeUs);
+ // Clear the whole ActionQueue as recording is done
+ mActionQueue.clear();
+ seeStopAction = true;
+ break;
+ }
+ default:
+ TRESPASS_DBG("Unknown action type");
+ // return true here because we did consume an available buffer, so the
+ // loop in start will eventually terminate even if we hit this.
+ return false;
+ }
+ }
+ }
+
+ if (seeStopAction) {
+ // Clear all the buffers before setting mEndOfStream and signal EndOfInputStream.
+ releaseAllAvailableBuffers_l();
+ mEndOfStream = true;
+ submitEndOfInputStream_l();
+ return true;
+ }
+
+ if (mSuspended) {
+ return true;
+ }
+
+ int err = UNKNOWN_ERROR;
+
+ // only submit sample if start time is unspecified, or sample
+ // is queued after the specified start time
+ if (mSkipFramesBeforeNs < 0ll || item.mTimestampNs >= mSkipFramesBeforeNs) {
+ // if start time is set, offset time stamp by start time
+ if (mSkipFramesBeforeNs > 0) {
+ item.mTimestampNs -= mSkipFramesBeforeNs;
+ }
+
+ int64_t timeUs = item.mTimestampNs / 1000;
+ if (mFrameDropper != NULL && mFrameDropper->shouldDrop(timeUs)) {
+ ALOGV("skipping frame (%lld) to meet max framerate", static_cast<long long>(timeUs));
+ // set err to OK so that the skipped frame can still be saved as the lastest frame
+ err = OK;
+ } else {
+ err = submitBuffer_l(item); // this takes shared ownership of the acquired buffer on succeess
+ }
+ }
+
+ if (err != OK) {
+ ALOGV("submitBuffer_l failed, will release bq slot %d", item.mBuffer->getSlot());
+ return true;
+ } else {
+ // Don't set the last buffer id if we're not repeating,
+ // we'll be holding on to the last buffer for nothing.
+ if (mFrameRepeatIntervalUs > 0ll) {
+ setLatestBuffer_l(item);
+ }
+ ALOGV("buffer submitted [slot=%d, useCount=%ld] acquired=%d",
+ item.mBuffer->getSlot(), item.mBuffer.use_count(), mNumOutstandingAcquires);
+ mLastFrameTimestampUs = itemTimeUs;
+ }
+
+ return true;
+}
+
+bool GraphicBufferSource::repeatLatestBuffer_l() {
+ CHECK(mExecuting && !haveAvailableBuffers_l());
+
+ if (mLatestBuffer.mBuffer == nullptr || mSuspended) {
+ return false;
+ }
+
+ if (mFreeCodecBuffers.empty()) {
+ // No buffers available, bail.
+ ALOGV("repeatLatestBuffer_l: no codec buffers.");
+ return false;
+ }
+
+ if (!mLatestBuffer.mBuffer->isCached()) {
+ ALOGV("repeatLatestBuffer_l: slot was discarded, but repeating our own reference");
+ }
+
+ // it is ok to update the timestamp of latest buffer as it is only used for submission
+ status_t err = submitBuffer_l(mLatestBuffer);
+ if (err != OK) {
+ return false;
+ }
+
+ /* repeat last frame up to kRepeatLastFrameCount times.
+ * in case of static scene, a single repeat might not get rid of encoder
+ * ghosting completely, refresh a couple more times to get better quality
+ */
+ if (--mOutstandingFrameRepeatCount > 0) {
+ // set up timestamp for repeat frame
+ mLatestBuffer.mTimestampNs += mFrameRepeatIntervalUs * 1000;
+ queueFrameRepeat_l();
+ }
+
+ return true;
+}
+
+void GraphicBufferSource::setLatestBuffer_l(const VideoBuffer &item) {
+ mLatestBuffer = item;
+
+ ALOGV("setLatestBuffer_l: [slot=%d, useCount=%ld]",
+ mLatestBuffer.mBuffer->getSlot(), mLatestBuffer.mBuffer.use_count());
+
+ mOutstandingFrameRepeatCount = kRepeatLastFrameCount;
+ // set up timestamp for repeat frame
+ mLatestBuffer.mTimestampNs += mFrameRepeatIntervalUs * 1000;
+ queueFrameRepeat_l();
+}
+
+void GraphicBufferSource::queueFrameRepeat_l() {
+ mFrameRepeatBlockedOnCodecBuffer = false;
+
+ if (mReflector != NULL) {
+ sp<AMessage> msg = new AMessage(kWhatRepeatLastFrame, mReflector);
+ msg->setInt32("generation", ++mRepeatLastFrameGeneration);
+ msg->post(mFrameRepeatIntervalUs);
+ }
+}
+
+bool GraphicBufferSource::calculateCodecTimestamp_l(
+ nsecs_t bufferTimeNs, int64_t *codecTimeUs) {
+ int64_t timeUs = bufferTimeNs / 1000;
+ timeUs += mInputBufferTimeOffsetUs;
+
+ if (mCaptureFps > 0.
+ && (mFps > 2 * mCaptureFps
+ || mCaptureFps > 2 * mFps)) {
+ // Time lapse or slow motion mode
+ if (mPrevCaptureUs < 0ll) {
+ // first capture
+ mPrevCaptureUs = mBaseCaptureUs = timeUs;
+ // adjust the first sample timestamp.
+ mPrevFrameUs = mBaseFrameUs =
+ std::llround((timeUs * mCaptureFps) / mFps);
+ mFrameCount = 0;
+ } else {
+ // snap to nearest capture point
+ double nFrames = (timeUs - mPrevCaptureUs) * mCaptureFps / 1000000;
+ if (nFrames < 0.5 - kTimestampFluctuation) {
+ // skip this frame as it's too close to previous capture
+ ALOGV("skipping frame, timeUs %lld", static_cast<long long>(timeUs));
+ return false;
+ }
+ if (nFrames <= 1.0) {
+ nFrames = 1.0;
+ }
+ mFrameCount += std::llround(nFrames);
+ mPrevCaptureUs = mBaseCaptureUs + std::llround(
+ mFrameCount * 1000000 / mCaptureFps);
+ mPrevFrameUs = mBaseFrameUs + std::llround(
+ mFrameCount * 1000000 / mFps);
+ }
+
+ ALOGV("timeUs %lld, captureUs %lld, frameUs %lld",
+ static_cast<long long>(timeUs),
+ static_cast<long long>(mPrevCaptureUs),
+ static_cast<long long>(mPrevFrameUs));
+ } else {
+ if (timeUs <= mPrevFrameUs) {
+ // Drop the frame if it's going backward in time. Bad timestamp
+ // could disrupt encoder's rate control completely.
+ ALOGW("Dropping frame that's going backward in time");
+ return false;
+ }
+
+ mPrevFrameUs = timeUs;
+ }
+
+ *codecTimeUs = mPrevFrameUs;
+ return true;
+}
+
+status_t GraphicBufferSource::submitBuffer_l(const VideoBuffer &item) {
+ CHECK(!mFreeCodecBuffers.empty());
+ uint32_t codecBufferId = *mFreeCodecBuffers.begin();
+
+ ALOGV("submitBuffer_l [slot=%d, bufferId=%d]", item.mBuffer->getSlot(), codecBufferId);
+
+ int64_t codecTimeUs;
+ if (!calculateCodecTimestamp_l(item.mTimestampNs, &codecTimeUs)) {
+ return UNKNOWN_ERROR;
+ }
+
+ if ((android_dataspace)item.mDataspace != mLastDataspace) {
+ onDataspaceChanged_l(
+ item.mDataspace,
+ (android_pixel_format)item.mBuffer->getGraphicBuffer()->format);
+ }
+
+ std::shared_ptr<AcquiredBuffer> buffer = item.mBuffer;
+ // use a GraphicBuffer for now as component is using GraphicBuffers to hold references
+ // and it requires this graphic buffer to be able to hold its reference
+ // and thus we would need to create a new GraphicBuffer from an ANWBuffer separate from the
+ // acquired GraphicBuffer.
+ // TODO: this can be reworked globally to use ANWBuffer references
+ sp<GraphicBuffer> graphicBuffer = buffer->getGraphicBuffer();
+ status_t err = mComponent->submitBuffer(
+ codecBufferId, graphicBuffer, codecTimeUs, buffer->getAcquireFenceFd());
+
+ if (err != OK) {
+ ALOGW("WARNING: emptyGraphicBuffer failed: 0x%x", err);
+ return err;
+ }
+
+ mFreeCodecBuffers.erase(mFreeCodecBuffers.begin());
+
+ ssize_t cbix = mSubmittedCodecBuffers.add(codecBufferId, buffer);
+ ALOGV("emptyGraphicBuffer succeeded, bufferId=%u@%zd bufhandle=%p",
+ codecBufferId, cbix, graphicBuffer->handle);
+ return OK;
+}
+
+void GraphicBufferSource::submitEndOfInputStream_l() {
+ CHECK(mEndOfStream);
+ if (mEndOfStreamSent) {
+ ALOGV("EOS already sent");
+ return;
+ }
+
+ if (mFreeCodecBuffers.empty()) {
+ ALOGV("submitEndOfInputStream_l: no codec buffers available");
+ return;
+ }
+ uint32_t codecBufferId = *mFreeCodecBuffers.begin();
+
+ // We reject any additional incoming graphic buffers. There is no acquired buffer used for EOS
+ status_t err = mComponent->submitEos(codecBufferId);
+ if (err != OK) {
+ ALOGW("emptyDirectBuffer EOS failed: 0x%x", err);
+ } else {
+ mFreeCodecBuffers.erase(mFreeCodecBuffers.begin());
+ ssize_t cbix = mSubmittedCodecBuffers.add(codecBufferId, nullptr);
+ ALOGV("submitEndOfInputStream_l: buffer submitted, bufferId=%u@%zd", codecBufferId, cbix);
+ mEndOfStreamSent = true;
+
+ // no need to hold onto any buffers for frame repeating
+ ++mRepeatLastFrameGeneration;
+ mLatestBuffer.mBuffer.reset();
+ }
+}
+
+status_t GraphicBufferSource::acquireBuffer_l(VideoBuffer *ab) {
+ BufferItem bi;
+ status_t err = mConsumer->acquireBuffer(&bi, 0);
+ if (err == BufferQueue::NO_BUFFER_AVAILABLE) {
+ // shouldn't happen
+ ALOGW("acquireBuffer_l: frame was not available");
+ return err;
+ } else if (err != OK) {
+ ALOGW("acquireBuffer_l: failed with err=%d", err);
+ return err;
+ }
+ --mNumAvailableUnacquiredBuffers;
+
+ // Manage our buffer cache.
+ std::shared_ptr<CachedBuffer> buffer;
+ ssize_t bsi = mBufferSlots.indexOfKey(bi.mSlot);
+ if (bi.mGraphicBuffer != NULL) {
+ // replace/initialize slot with new buffer
+ ALOGV("acquireBuffer_l: %s buffer slot %d", bsi < 0 ? "setting" : "UPDATING", bi.mSlot);
+ if (bsi >= 0) {
+ discardBufferAtSlotIndex_l(bsi);
+ } else {
+ bsi = mBufferSlots.add(bi.mSlot, nullptr);
+ }
+ buffer = std::make_shared<CachedBuffer>(bi.mSlot, bi.mGraphicBuffer);
+ mBufferSlots.replaceValueAt(bsi, buffer);
+ } else {
+ buffer = mBufferSlots.valueAt(bsi);
+ }
+ int64_t frameNum = bi.mFrameNumber;
+
+ std::shared_ptr<AcquiredBuffer> acquiredBuffer =
+ std::make_shared<AcquiredBuffer>(
+ buffer,
+ [frameNum, this](AcquiredBuffer *buffer){
+ // AcquiredBuffer's destructor should always be called when mMutex is locked.
+ // If we had a reentrant mutex, we could just lock it again to ensure this.
+ if (mMutex.tryLock() == 0) {
+ TRESPASS_DBG();
+ mMutex.unlock();
+ }
+
+ // we can release buffers immediately if not using adapters
+ // alternately, we could add them to mSlotsToRelease, but we would
+ // somehow need to propagate frame number to that queue
+ if (buffer->isCached()) {
+ --mNumOutstandingAcquires;
+ mConsumer->releaseBuffer(
+ buffer->getSlot(), frameNum, EGL_NO_DISPLAY, EGL_NO_SYNC_KHR,
+ buffer->getReleaseFence());
+ }
+ },
+ bi.mFence);
+ VideoBuffer videoBuffer{acquiredBuffer, bi.mTimestamp, bi.mDataSpace};
+ *ab = videoBuffer;
+ ++mNumOutstandingAcquires;
+ return OK;
+}
+
+// BufferQueue::ConsumerListener callback
+void GraphicBufferSource::onFrameAvailable(const BufferItem& item __unused) {
+ Mutex::Autolock autoLock(mMutex);
+
+ ALOGV("onFrameAvailable: executing=%d available=%zu+%d",
+ mExecuting, mAvailableBuffers.size(), mNumAvailableUnacquiredBuffers);
+ ++mNumAvailableUnacquiredBuffers;
+
+ // For BufferQueue we cannot acquire a buffer if we cannot immediately feed it to the codec
+ // UNLESS we are discarding this buffer (acquiring and immediately releasing it), which makes
+ // this an ugly logic.
+ // NOTE: We could also rely on our debug counter but that is meant only as a debug counter.
+ if (!areWeDiscardingAvailableBuffers_l() && mFreeCodecBuffers.empty()) {
+ // we may not be allowed to acquire a possibly encodable buffer, so just note that
+ // it is available
+ ALOGV("onFrameAvailable: cannot acquire buffer right now, do it later");
+
+ ++mRepeatLastFrameGeneration; // cancel any pending frame repeat
+ return;
+ }
+
+ VideoBuffer buffer;
+ status_t err = acquireBuffer_l(&buffer);
+ if (err != OK) {
+ ALOGE("onFrameAvailable: acquireBuffer returned err=%d", err);
+ } else {
+ onBufferAcquired_l(buffer);
+ }
+}
+
+bool GraphicBufferSource::areWeDiscardingAvailableBuffers_l() {
+ return mEndOfStreamSent // already sent EOS to codec
+ || mComponent == nullptr // there is no codec connected
+ || (mSuspended && mActionQueue.empty()) // we are suspended and not waiting for
+ // any further action
+ || !mExecuting;
+}
+
+void GraphicBufferSource::onBufferAcquired_l(const VideoBuffer &buffer) {
+ if (mEndOfStreamSent) {
+ // This should only be possible if a new buffer was queued after
+ // EOS was signaled, i.e. the app is misbehaving.
+ ALOGW("onFrameAvailable: EOS is sent, ignoring frame");
+ } else if (mComponent == NULL || (mSuspended && mActionQueue.empty())) {
+ // FIXME: if we are suspended but have a resume queued we will stop repeating the last
+ // frame. Is that the desired behavior?
+ ALOGV("onFrameAvailable: suspended, ignoring frame");
+ } else {
+ ++mRepeatLastFrameGeneration; // cancel any pending frame repeat
+ mAvailableBuffers.push_back(buffer);
+ if (mExecuting) {
+ fillCodecBuffer_l();
+ }
+ }
+}
+
+// BufferQueue::ConsumerListener callback
+void GraphicBufferSource::onBuffersReleased() {
+ Mutex::Autolock lock(mMutex);
+
+ uint64_t slotMask;
+ uint64_t releaseMask;
+ if (mConsumer->getReleasedBuffers(&releaseMask) != NO_ERROR) {
+ slotMask = 0xffffffffffffffffULL;
+ ALOGW("onBuffersReleased: unable to get released buffer set");
+ } else {
+ slotMask = releaseMask;
+ ALOGV("onBuffersReleased: 0x%016" PRIx64, slotMask);
+ }
+
+ AString unpopulated;
+ for (int i = 0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) {
+ if ((slotMask & 0x01) != 0) {
+ if (!discardBufferInSlot_l(i)) {
+ if (!unpopulated.empty()) {
+ unpopulated.append(", ");
+ }
+ unpopulated.append(i);
+ }
+ }
+ slotMask >>= 1;
+ }
+ if (!unpopulated.empty()) {
+ ALOGW("released unpopulated slots: [%s]", unpopulated.c_str());
+ }
+}
+
+bool GraphicBufferSource::discardBufferInSlot_l(GraphicBufferSource::slot_id i) {
+ ssize_t bsi = mBufferSlots.indexOfKey(i);
+ if (bsi < 0) {
+ return false;
+ } else {
+ discardBufferAtSlotIndex_l(bsi);
+ mBufferSlots.removeItemsAt(bsi);
+ return true;
+ }
+}
+
+void GraphicBufferSource::discardBufferAtSlotIndex_l(ssize_t bsi) {
+ const std::shared_ptr<CachedBuffer>& buffer = mBufferSlots.valueAt(bsi);
+ // use -2 if there is no latest buffer, and -1 if it is no longer cached
+ slot_id latestBufferSlot =
+ mLatestBuffer.mBuffer == nullptr ? -2 : mLatestBuffer.mBuffer->getSlot();
+ ALOGV("releasing acquired buffer: [slot=%d, useCount=%ld], latest: [slot=%d]",
+ mBufferSlots.keyAt(bsi), buffer.use_count(), latestBufferSlot);
+ mBufferSlots.valueAt(bsi)->onDroppedFromCache();
+
+ // If the slot of an acquired buffer is discarded, that buffer will not have to be
+ // released to the producer, so account it here. However, it is possible that the
+ // acquired buffer has already been discarded so check if it still is.
+ if (buffer->isAcquired()) {
+ --mNumOutstandingAcquires;
+ }
+
+ // clear the buffer reference (not technically needed as caller either replaces or deletes
+ // it; done here for safety).
+ mBufferSlots.editValueAt(bsi).reset();
+ CHECK_DBG(buffer == nullptr);
+}
+
+void GraphicBufferSource::releaseAllAvailableBuffers_l() {
+ mAvailableBuffers.clear();
+ while (mNumAvailableUnacquiredBuffers > 0) {
+ VideoBuffer item;
+ if (acquireBuffer_l(&item) != OK) {
+ ALOGW("releaseAllAvailableBuffers: failed to acquire available unacquired buffer");
+ break;
+ }
+ }
+}
+
+// BufferQueue::ConsumerListener callback
+void GraphicBufferSource::onSidebandStreamChanged() {
+ ALOG_ASSERT(false, "GraphicBufferSource can't consume sideband streams");
+}
+
+status_t GraphicBufferSource::configure(
+ const sp<ComponentWrapper>& component,
+ int32_t dataSpace,
+ int32_t bufferCount,
+ uint32_t frameWidth,
+ uint32_t frameHeight,
+ uint32_t consumerUsage) {
+ if (component == NULL) {
+ return BAD_VALUE;
+ }
+
+
+ // Call setMaxAcquiredBufferCount without lock.
+ // setMaxAcquiredBufferCount could call back to onBuffersReleased
+ // if the buffer count change results in releasing of existing buffers,
+ // which would lead to deadlock.
+ status_t err = mConsumer->setMaxAcquiredBufferCount(bufferCount);
+ if (err != NO_ERROR) {
+ ALOGE("Unable to set BQ max acquired buffer count to %u: %d",
+ bufferCount, err);
+ return err;
+ }
+
+ {
+ Mutex::Autolock autoLock(mMutex);
+ mComponent = component;
+
+ err = mConsumer->setDefaultBufferSize(frameWidth, frameHeight);
+ if (err != NO_ERROR) {
+ ALOGE("Unable to set BQ default buffer size to %ux%u: %d",
+ frameWidth, frameHeight, err);
+ return err;
+ }
+
+ consumerUsage |= GRALLOC_USAGE_HW_VIDEO_ENCODER;
+ mConsumer->setConsumerUsageBits(consumerUsage);
+
+ // Sets the default buffer data space
+ ALOGD("setting dataspace: %#x, acquired=%d", dataSpace, mNumOutstandingAcquires);
+ mConsumer->setDefaultBufferDataSpace((android_dataspace)dataSpace);
+ mLastDataspace = (android_dataspace)dataSpace;
+
+ mExecuting = false;
+ mSuspended = false;
+ mEndOfStream = false;
+ mEndOfStreamSent = false;
+ mSkipFramesBeforeNs = -1ll;
+ mFrameRepeatIntervalUs = -1ll;
+ mRepeatLastFrameGeneration = 0;
+ mOutstandingFrameRepeatCount = 0;
+ mLatestBuffer.mBuffer.reset();
+ mFrameRepeatBlockedOnCodecBuffer = false;
+ mFps = -1.0;
+ mCaptureFps = -1.0;
+ mBaseCaptureUs = -1ll;
+ mBaseFrameUs = -1ll;
+ mPrevCaptureUs = -1ll;
+ mPrevFrameUs = -1ll;
+ mFrameCount = 0;
+ mInputBufferTimeOffsetUs = 0;
+ mStopTimeUs = -1;
+ mActionQueue.clear();
+ }
+
+ return OK;
+}
+
+status_t GraphicBufferSource::setSuspend(bool suspend, int64_t suspendStartTimeUs) {
+ ALOGV("setSuspend=%d at time %lld us", suspend, (long long)suspendStartTimeUs);
+
+ Mutex::Autolock autoLock(mMutex);
+
+ if (mStopTimeUs != -1) {
+ ALOGE("setSuspend failed as STOP action is pending");
+ return INVALID_OPERATION;
+ }
+
+ // Push the action to the queue.
+ if (suspendStartTimeUs != -1) {
+ // suspendStartTimeUs must be smaller or equal to current systemTime.
+ int64_t currentSystemTimeUs = systemTime() / 1000;
+ if (suspendStartTimeUs > currentSystemTimeUs) {
+ ALOGE("setSuspend failed. %lld is larger than current system time %lld us",
+ (long long)suspendStartTimeUs, (long long)currentSystemTimeUs);
+ return INVALID_OPERATION;
+ }
+ if (mLastActionTimeUs != -1 && suspendStartTimeUs < mLastActionTimeUs) {
+ ALOGE("setSuspend failed. %lld is smaller than last action time %lld us",
+ (long long)suspendStartTimeUs, (long long)mLastActionTimeUs);
+ return INVALID_OPERATION;
+ }
+ mLastActionTimeUs = suspendStartTimeUs;
+ ActionItem action;
+ action.mAction = suspend ? ActionItem::PAUSE : ActionItem::RESUME;
+ action.mActionTimeUs = suspendStartTimeUs;
+ ALOGV("Push %s action into actionQueue", suspend ? "PAUSE" : "RESUME");
+ mActionQueue.push_back(action);
+ } else {
+ if (suspend) {
+ mSuspended = true;
+ releaseAllAvailableBuffers_l();
+ return OK;
+ } else {
+ mSuspended = false;
+ if (mExecuting && !haveAvailableBuffers_l()
+ && mFrameRepeatBlockedOnCodecBuffer) {
+ if (repeatLatestBuffer_l()) {
+ ALOGV("suspend/deferred repeatLatestBuffer_l SUCCESS");
+ mFrameRepeatBlockedOnCodecBuffer = false;
+ } else {
+ ALOGV("suspend/deferred repeatLatestBuffer_l FAILURE");
+ }
+ }
+ }
+ }
+ return OK;
+}
+
+status_t GraphicBufferSource::setRepeatPreviousFrameDelayUs(int64_t repeatAfterUs) {
+ ALOGV("setRepeatPreviousFrameDelayUs: delayUs=%lld", (long long)repeatAfterUs);
+
+ Mutex::Autolock autoLock(mMutex);
+
+ if (mExecuting || repeatAfterUs <= 0ll) {
+ return INVALID_OPERATION;
+ }
+
+ mFrameRepeatIntervalUs = repeatAfterUs;
+ return OK;
+}
+
+status_t GraphicBufferSource::setTimeOffsetUs(int64_t timeOffsetUs) {
+ Mutex::Autolock autoLock(mMutex);
+
+ // timeOffsetUs must be negative for adjustment.
+ if (timeOffsetUs >= 0ll) {
+ return INVALID_OPERATION;
+ }
+
+ mInputBufferTimeOffsetUs = timeOffsetUs;
+ return OK;
+}
+
+status_t GraphicBufferSource::setMaxFps(float maxFps) {
+ ALOGV("setMaxFps: maxFps=%lld", (long long)maxFps);
+
+ Mutex::Autolock autoLock(mMutex);
+
+ if (mExecuting) {
+ return INVALID_OPERATION;
+ }
+
+ mFrameDropper = new FrameDropper();
+ status_t err = mFrameDropper->setMaxFrameRate(maxFps);
+ if (err != OK) {
+ mFrameDropper.clear();
+ return err;
+ }
+
+ return OK;
+}
+
+status_t GraphicBufferSource::setStartTimeUs(int64_t skipFramesBeforeUs) {
+ ALOGV("setStartTimeUs: skipFramesBeforeUs=%lld", (long long)skipFramesBeforeUs);
+
+ Mutex::Autolock autoLock(mMutex);
+
+ mSkipFramesBeforeNs =
+ (skipFramesBeforeUs > 0 && skipFramesBeforeUs <= INT64_MAX / 1000) ?
+ (skipFramesBeforeUs * 1000) : -1ll;
+
+ return OK;
+}
+
+status_t GraphicBufferSource::setStopTimeUs(int64_t stopTimeUs) {
+ ALOGV("setStopTimeUs: %lld us", (long long)stopTimeUs);
+ Mutex::Autolock autoLock(mMutex);
+
+ if (mStopTimeUs != -1) {
+ // Ignore if stop time has already been set
+ return OK;
+ }
+
+ // stopTimeUs must be smaller or equal to current systemTime.
+ int64_t currentSystemTimeUs = systemTime() / 1000;
+ if (stopTimeUs > currentSystemTimeUs) {
+ ALOGE("setStopTimeUs failed. %lld is larger than current system time %lld us",
+ (long long)stopTimeUs, (long long)currentSystemTimeUs);
+ return INVALID_OPERATION;
+ }
+ if (mLastActionTimeUs != -1 && stopTimeUs < mLastActionTimeUs) {
+ ALOGE("setSuspend failed. %lld is smaller than last action time %lld us",
+ (long long)stopTimeUs, (long long)mLastActionTimeUs);
+ return INVALID_OPERATION;
+ }
+ mLastActionTimeUs = stopTimeUs;
+ ActionItem action;
+ action.mAction = ActionItem::STOP;
+ action.mActionTimeUs = stopTimeUs;
+ mActionQueue.push_back(action);
+ mStopTimeUs = stopTimeUs;
+ return OK;
+}
+
+status_t GraphicBufferSource::getStopTimeOffsetUs(int64_t *stopTimeOffsetUs) {
+ ALOGV("getStopTimeOffsetUs");
+ Mutex::Autolock autoLock(mMutex);
+ if (mStopTimeUs == -1) {
+ ALOGW("Fail to return stopTimeOffsetUs as stop time is not set");
+ return INVALID_OPERATION;
+ }
+ *stopTimeOffsetUs =
+ mLastFrameTimestampUs == -1 ? 0 : mStopTimeUs - mLastFrameTimestampUs;
+ return OK;
+}
+
+status_t GraphicBufferSource::setTimeLapseConfig(double fps, double captureFps) {
+ ALOGV("setTimeLapseConfig: fps=%lg, captureFps=%lg",
+ fps, captureFps);
+ Mutex::Autolock autoLock(mMutex);
+
+ if (mExecuting || !(fps > 0) || !(captureFps > 0)) {
+ return INVALID_OPERATION;
+ }
+
+ mFps = fps;
+ mCaptureFps = captureFps;
+
+ return OK;
+}
+
+status_t GraphicBufferSource::setColorAspects(int32_t aspectsPacked) {
+ Mutex::Autolock autoLock(mMutex);
+ mDefaultColorAspectsPacked = aspectsPacked;
+ ColorAspects colorAspects = ColorUtils::unpackToColorAspects(aspectsPacked);
+ ALOGD("requesting color aspects (R:%d(%s), P:%d(%s), M:%d(%s), T:%d(%s))",
+ colorAspects.mRange, asString(colorAspects.mRange),
+ colorAspects.mPrimaries, asString(colorAspects.mPrimaries),
+ colorAspects.mMatrixCoeffs, asString(colorAspects.mMatrixCoeffs),
+ colorAspects.mTransfer, asString(colorAspects.mTransfer));
+
+ return OK;
+}
+
+status_t GraphicBufferSource::signalEndOfInputStream() {
+ Mutex::Autolock autoLock(mMutex);
+ ALOGV("signalEndOfInputStream: executing=%d available=%zu+%d eos=%d",
+ mExecuting, mAvailableBuffers.size(), mNumAvailableUnacquiredBuffers, mEndOfStream);
+
+ if (mEndOfStream) {
+ ALOGE("EOS was already signaled");
+ return INVALID_OPERATION;
+ }
+
+ // Set the end-of-stream flag. If no frames are pending from the
+ // BufferQueue, and a codec buffer is available, and we're executing,
+ // and there is no stop timestamp, we initiate the EOS from here.
+ // Otherwise, we'll let codecBufferEmptied() (or start) do it.
+ //
+ // Note: if there are no pending frames and all codec buffers are
+ // available, we *must* submit the EOS from here or we'll just
+ // stall since no future events are expected.
+ mEndOfStream = true;
+
+ if (mStopTimeUs == -1 && mExecuting && !haveAvailableBuffers_l()) {
+ submitEndOfInputStream_l();
+ }
+
+ return OK;
+}
+
+void GraphicBufferSource::onMessageReceived(const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatRepeatLastFrame:
+ {
+ Mutex::Autolock autoLock(mMutex);
+
+ int32_t generation;
+ CHECK(msg->findInt32("generation", &generation));
+
+ if (generation != mRepeatLastFrameGeneration) {
+ // stale
+ break;
+ }
+
+ if (!mExecuting || haveAvailableBuffers_l()) {
+ break;
+ }
+
+ bool success = repeatLatestBuffer_l();
+ if (success) {
+ ALOGV("repeatLatestBuffer_l SUCCESS");
+ } else {
+ ALOGV("repeatLatestBuffer_l FAILURE");
+ mFrameRepeatBlockedOnCodecBuffer = true;
+ }
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+}
+
+} // namespace android
diff --git a/media/libstagefright/gbs/include/media/stagefright/gbs/ComponentWrapper.h b/media/libstagefright/gbs/include/media/stagefright/gbs/ComponentWrapper.h
new file mode 100644
index 0000000..e27829b
--- /dev/null
+++ b/media/libstagefright/gbs/include/media/stagefright/gbs/ComponentWrapper.h
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+#ifndef COMPONENT_WRAPPER_H_
+#define COMPONENT_WRAPPER_H_
+
+#include <utils/RefBase.h>
+#include <utils/StrongPointer.h>
+#include <ui/GraphicBuffer.h>
+
+#include <stdint.h>
+
+namespace android {
+
+struct ComponentWrapper : public RefBase {
+ virtual status_t submitBuffer(
+ int32_t bufferId, const sp<GraphicBuffer> &buffer = nullptr,
+ int64_t timestamp = 0, int fenceFd = -1) = 0;
+ virtual status_t submitEos(int32_t bufferId) = 0;
+ virtual void dispatchDataSpaceChanged(
+ int32_t dataSpace, int32_t aspects, int32_t pixelFormat) = 0;
+};
+
+} // namespace android
+
+#endif // COMPONENT_WRAPPER_H_
diff --git a/media/libstagefright/omx/include/media/stagefright/omx/FrameDropper.h b/media/libstagefright/gbs/include/media/stagefright/gbs/FrameDropper.h
similarity index 100%
rename from media/libstagefright/omx/include/media/stagefright/omx/FrameDropper.h
rename to media/libstagefright/gbs/include/media/stagefright/gbs/FrameDropper.h
diff --git a/media/libstagefright/gbs/include/media/stagefright/gbs/GraphicBufferSource.h b/media/libstagefright/gbs/include/media/stagefright/gbs/GraphicBufferSource.h
new file mode 100644
index 0000000..89f6cf8
--- /dev/null
+++ b/media/libstagefright/gbs/include/media/stagefright/gbs/GraphicBufferSource.h
@@ -0,0 +1,478 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef GRAPHIC_BUFFER_SOURCE_H_
+
+#define GRAPHIC_BUFFER_SOURCE_H_
+
+#include <binder/Status.h>
+#include <gui/BufferQueue.h>
+#include <gui/IGraphicBufferProducer.h>
+#include <utils/RefBase.h>
+
+#include <media/hardware/VideoAPI.h>
+#include <media/stagefright/foundation/ABase.h>
+#include <media/stagefright/foundation/AHandlerReflector.h>
+#include <media/stagefright/foundation/ALooper.h>
+#include <media/stagefright/gbs/ComponentWrapper.h>
+
+namespace android {
+
+using ::android::binder::Status;
+
+struct FrameDropper;
+
+/*
+ * This class is used to feed codecs from a Surface via BufferQueue or
+ * HW producer.
+ *
+ * Instances of the class don't run on a dedicated thread. Instead,
+ * various events trigger data movement:
+ *
+ * - Availability of a new frame of data from the BufferQueue (notified
+ * via the onFrameAvailable callback).
+ * - The return of a codec buffer.
+ * - Application signaling end-of-stream.
+ * - Transition to or from "executing" state.
+ *
+ * Frames of data (and, perhaps, the end-of-stream indication) can arrive
+ * before the codec is in the "executing" state, so we need to queue
+ * things up until we're ready to go.
+ *
+ * The GraphicBufferSource can be configure dynamically to discard frames
+ * from the source:
+ *
+ * - if their timestamp is less than a start time
+ * - if the source is suspended or stopped and the suspend/stop-time is reached
+ * - if EOS was signaled
+ * - if there is no encoder connected to it
+ *
+ * The source, furthermore, may choose to not encode (drop) frames if:
+ *
+ * - to throttle the frame rate (keep it under a certain limit)
+ *
+ * Finally the source may optionally hold onto the last non-discarded frame
+ * (even if it was dropped) to reencode it after an interval if no further
+ * frames are sent by the producer.
+ */
+class GraphicBufferSource : public BufferQueue::ConsumerListener {
+public:
+ GraphicBufferSource();
+
+ virtual ~GraphicBufferSource();
+
+ // We can't throw an exception if the constructor fails, so we just set
+ // this and require that the caller test the value.
+ status_t initCheck() const {
+ return mInitCheck;
+ }
+
+ // Returns the handle to the producer side of the BufferQueue. Buffers
+ // queued on this will be received by GraphicBufferSource.
+ sp<IGraphicBufferProducer> getIGraphicBufferProducer() const {
+ return mProducer;
+ }
+
+ // This is called when component transitions to running state, which means
+ // we can start handing it buffers. If we already have buffers of data
+ // sitting in the BufferQueue, this will send them to the codec.
+ Status start();
+
+ // This is called when component transitions to stopped, indicating that
+ // the codec is meant to return all buffers back to the client for them
+ // to be freed. Do NOT submit any more buffers to the component.
+ Status stop();
+
+ // This is called when component transitions to released, indicating that
+ // we are shutting down.
+ Status release();
+
+ // A "codec buffer", i.e. a buffer that can be used to pass data into
+ // the encoder, has been allocated. (This call does not call back into
+ // component.)
+ Status onInputBufferAdded(int32_t bufferId);
+
+ // Called when encoder is no longer using the buffer. If we have a BQ
+ // buffer available, fill it with a new frame of data; otherwise, just mark
+ // it as available.
+ Status onInputBufferEmptied(int32_t bufferId, int fenceFd);
+
+ // IGraphicBufferSource interface
+ // ------------------------------
+
+ // Configure the buffer source to be used with a component with the default
+ // data space.
+ status_t configure(
+ const sp<ComponentWrapper> &component,
+ int32_t dataSpace,
+ int32_t bufferCount,
+ uint32_t frameWidth,
+ uint32_t frameHeight,
+ uint32_t consumerUsage);
+
+ // This is called after the last input frame has been submitted or buffer
+ // timestamp is greater or equal than stopTimeUs. We need to submit an empty
+ // buffer with the EOS flag set. If we don't have a codec buffer ready,
+ // we just set the mEndOfStream flag.
+ status_t signalEndOfInputStream();
+
+ // If suspend is true, all incoming buffers (including those currently
+ // in the BufferQueue) with timestamp larger than timeUs will be discarded
+ // until the suspension is lifted. If suspend is false, all incoming buffers
+ // including those currently in the BufferQueue) with timestamp larger than
+ // timeUs will be processed. timeUs uses SYSTEM_TIME_MONOTONIC time base.
+ status_t setSuspend(bool suspend, int64_t timeUs);
+
+ // Specifies the interval after which we requeue the buffer previously
+ // queued to the encoder. This is useful in the case of surface flinger
+ // providing the input surface if the resulting encoded stream is to
+ // be displayed "live". If we were not to push through the extra frame
+ // the decoder on the remote end would be unable to decode the latest frame.
+ // This API must be called before transitioning the encoder to "executing"
+ // state and once this behaviour is specified it cannot be reset.
+ status_t setRepeatPreviousFrameDelayUs(int64_t repeatAfterUs);
+
+ // Sets the input buffer timestamp offset.
+ // When set, the sample's timestamp will be adjusted with the timeOffsetUs.
+ status_t setTimeOffsetUs(int64_t timeOffsetUs);
+
+ // When set, the max frame rate fed to the encoder will be capped at maxFps.
+ status_t setMaxFps(float maxFps);
+
+ // Sets the time lapse (or slow motion) parameters.
+ // When set, the sample's timestamp will be modified to playback framerate,
+ // and capture timestamp will be modified to capture rate.
+ status_t setTimeLapseConfig(double fps, double captureFps);
+
+ // Sets the start time us (in system time), samples before which should
+ // be dropped and not submitted to encoder
+ status_t setStartTimeUs(int64_t startTimeUs);
+
+ // Sets the stop time us (in system time), samples after which should be dropped
+ // and not submitted to encoder. timeUs uses SYSTEM_TIME_MONOTONIC time base.
+ status_t setStopTimeUs(int64_t stopTimeUs);
+
+ // Gets the stop time offset in us. This is the time offset between latest buffer
+ // time and the stopTimeUs. If stop time is not set, INVALID_OPERATION will be returned.
+ // If return is OK, *stopTimeOffsetUs will contain the valid offset. Otherwise,
+ // *stopTimeOffsetUs will not be modified. Positive stopTimeOffsetUs means buffer time
+ // larger than stopTimeUs.
+ status_t getStopTimeOffsetUs(int64_t *stopTimeOffsetUs);
+
+ // Sets the desired color aspects, e.g. to be used when producer does not specify a dataspace.
+ status_t setColorAspects(int32_t aspectsPacked);
+
+protected:
+ // BQ::ConsumerListener interface
+ // ------------------------------
+
+ // BufferQueue::ConsumerListener interface, called when a new frame of
+ // data is available. If we're executing and a codec buffer is
+ // available, we acquire the buffer, copy the GraphicBuffer reference
+ // into the codec buffer, and call Empty[This]Buffer. If we're not yet
+ // executing or there's no codec buffer available, we just increment
+ // mNumFramesAvailable and return.
+ void onFrameAvailable(const BufferItem& item) override;
+
+ // BufferQueue::ConsumerListener interface, called when the client has
+ // released one or more GraphicBuffers. We clear out the appropriate
+ // set of mBufferSlot entries.
+ void onBuffersReleased() override;
+
+ // BufferQueue::ConsumerListener interface, called when the client has
+ // changed the sideband stream. GraphicBufferSource doesn't handle sideband
+ // streams so this is a no-op (and should never be called).
+ void onSidebandStreamChanged() override;
+
+private:
+ // Lock, covers all member variables.
+ mutable Mutex mMutex;
+
+ // Used to report constructor failure.
+ status_t mInitCheck;
+
+ // Graphic buffer reference objects
+ // --------------------------------
+
+ // These are used to keep a shared reference to GraphicBuffers and gralloc handles owned by the
+ // GraphicBufferSource as well as to manage the cache slots. Separate references are owned by
+ // the buffer cache (controlled by the buffer queue/buffer producer) and the codec.
+
+ // When we get a buffer from the producer (BQ) it designates them to be cached into specific
+ // slots. Each slot owns a shared reference to the graphic buffer (we track these using
+ // CachedBuffer) that is in that slot, but the producer controls the slots.
+ struct CachedBuffer;
+
+ // When we acquire a buffer, we must release it back to the producer once we (or the codec)
+ // no longer uses it (as long as the buffer is still in the cache slot). We use shared
+ // AcquiredBuffer instances for this purpose - and we call release buffer when the last
+ // reference is relinquished.
+ struct AcquiredBuffer;
+
+ // We also need to keep some extra metadata (other than the buffer reference) for acquired
+ // buffers. These are tracked in VideoBuffer struct.
+ struct VideoBuffer {
+ std::shared_ptr<AcquiredBuffer> mBuffer;
+ nsecs_t mTimestampNs;
+ android_dataspace_t mDataspace;
+ };
+
+ // Cached and aquired buffers
+ // --------------------------------
+
+ typedef int slot_id;
+
+ // Maps a slot to the cached buffer in that slot
+ KeyedVector<slot_id, std::shared_ptr<CachedBuffer>> mBufferSlots;
+
+ // Queue of buffers acquired in chronological order that are not yet submitted to the codec
+ List<VideoBuffer> mAvailableBuffers;
+
+ // Number of buffers that have been signaled by the producer that they are available, but
+ // we've been unable to acquire them due to our max acquire count
+ int32_t mNumAvailableUnacquiredBuffers;
+
+ // Number of frames acquired from consumer (debug only)
+ // (as in aquireBuffer called, and release needs to be called)
+ int32_t mNumOutstandingAcquires;
+
+ // Acquire a buffer from the BQ and store it in |item| if successful
+ // \return OK on success, or error on failure.
+ status_t acquireBuffer_l(VideoBuffer *item);
+
+ // Called when a buffer was acquired from the producer
+ void onBufferAcquired_l(const VideoBuffer &buffer);
+
+ // marks the buffer at the slot no longer cached, and accounts for the outstanding
+ // acquire count. Returns true if the slot was populated; otherwise, false.
+ bool discardBufferInSlot_l(slot_id i);
+
+ // marks the buffer at the slot index no longer cached, and accounts for the outstanding
+ // acquire count
+ void discardBufferAtSlotIndex_l(ssize_t bsi);
+
+ // release all acquired and unacquired available buffers
+ // This method will return if it fails to acquire an unacquired available buffer, which will
+ // leave mNumAvailableUnacquiredBuffers positive on return.
+ void releaseAllAvailableBuffers_l();
+
+ // returns whether we have any available buffers (acquired or not-yet-acquired)
+ bool haveAvailableBuffers_l() const {
+ return !mAvailableBuffers.empty() || mNumAvailableUnacquiredBuffers > 0;
+ }
+
+ // Codec buffers
+ // -------------
+
+ // When we queue buffers to the encoder, we must hold the references to the graphic buffers
+ // in those buffers - as the producer may free the slots.
+
+ typedef int32_t codec_buffer_id;
+
+ // set of codec buffer ID-s of buffers available to fill
+ List<codec_buffer_id> mFreeCodecBuffers;
+
+ // maps codec buffer ID-s to buffer info submitted to the codec. Used to keep a reference for
+ // the graphics buffer.
+ KeyedVector<codec_buffer_id, std::shared_ptr<AcquiredBuffer>> mSubmittedCodecBuffers;
+
+ // Processes the next acquired frame. If there is no available codec buffer, it returns false
+ // without any further action.
+ //
+ // Otherwise, it consumes the next acquired frame and determines if it needs to be discarded or
+ // dropped. If neither are needed, it submits it to the codec. It also saves the latest
+ // non-dropped frame and submits it for repeat encoding (if this is enabled).
+ //
+ // \require there must be an acquired frame (i.e. we're in the onFrameAvailable callback,
+ // or if we're in codecBufferEmptied and mNumFramesAvailable is nonzero).
+ // \require codec must be executing
+ // \returns true if acquired (and handled) the next frame. Otherwise, false.
+ bool fillCodecBuffer_l();
+
+ // Calculates the media timestamp for |item| and on success it submits the buffer to the codec,
+ // while also keeping a reference for it in mSubmittedCodecBuffers.
+ // Returns UNKNOWN_ERROR if the buffer was not submitted due to buffer timestamp. Otherwise,
+ // it returns any submit success or error value returned by the codec.
+ status_t submitBuffer_l(const VideoBuffer &item);
+
+ // Submits an empty buffer, with the EOS flag set if there is an available codec buffer and
+ // sets mEndOfStreamSent flag. Does nothing if there is no codec buffer available.
+ void submitEndOfInputStream_l();
+
+ // Set to true if we want to send end-of-stream after we run out of available frames from the
+ // producer
+ bool mEndOfStream;
+
+ // Flag that the EOS was submitted to the encoder
+ bool mEndOfStreamSent;
+
+ // Dataspace for the last frame submitted to the codec
+ android_dataspace mLastDataspace;
+
+ // Default color aspects for this source
+ int32_t mDefaultColorAspectsPacked;
+
+ // called when the data space of the input buffer changes
+ void onDataspaceChanged_l(android_dataspace dataspace, android_pixel_format pixelFormat);
+
+ // Pointer back to the component that created us. We send buffers here.
+ sp<ComponentWrapper> mComponent;
+
+ // Set by start() / stop().
+ bool mExecuting;
+
+ bool mSuspended;
+
+ // returns true if this source is unconditionally discarding acquired buffers at the moment
+ // regardless of the metadata of those buffers
+ bool areWeDiscardingAvailableBuffers_l();
+
+ int64_t mLastFrameTimestampUs;
+
+ // Our BufferQueue interfaces. mProducer is passed to the producer through
+ // getIGraphicBufferProducer, and mConsumer is used internally to retrieve
+ // the buffers queued by the producer.
+ sp<IGraphicBufferProducer> mProducer;
+ sp<IGraphicBufferConsumer> mConsumer;
+
+ // The time to stop sending buffers.
+ int64_t mStopTimeUs;
+
+ struct ActionItem {
+ typedef enum {
+ PAUSE,
+ RESUME,
+ STOP
+ } ActionType;
+ ActionType mAction;
+ int64_t mActionTimeUs;
+ };
+
+ // Maintain last action timestamp to ensure all the action timestamps are
+ // monotonically increasing.
+ int64_t mLastActionTimeUs;
+
+ // An action queue that queue up all the actions sent to GraphicBufferSource.
+ // STOP action should only show up at the end of the list as all the actions
+ // after a STOP action will be discarded. mActionQueue is protected by mMutex.
+ List<ActionItem> mActionQueue;
+
+ ////
+ friend struct AHandlerReflector<GraphicBufferSource>;
+
+ enum {
+ kWhatRepeatLastFrame, ///< queue last frame for reencoding
+ };
+ enum {
+ kRepeatLastFrameCount = 10,
+ };
+
+ int64_t mSkipFramesBeforeNs;
+
+ sp<FrameDropper> mFrameDropper;
+
+ sp<ALooper> mLooper;
+ sp<AHandlerReflector<GraphicBufferSource> > mReflector;
+
+ // Repeat last frame feature
+ // -------------------------
+ // configuration parameter: repeat interval for frame repeating (<0 if repeating is disabled)
+ int64_t mFrameRepeatIntervalUs;
+
+ // current frame repeat generation - used to cancel a pending frame repeat
+ int32_t mRepeatLastFrameGeneration;
+
+ // number of times to repeat latest frame (0 = none)
+ int32_t mOutstandingFrameRepeatCount;
+
+ // The previous buffer should've been repeated but
+ // no codec buffer was available at the time.
+ bool mFrameRepeatBlockedOnCodecBuffer;
+
+ // hold a reference to the last acquired (and not discarded) frame for frame repeating
+ VideoBuffer mLatestBuffer;
+
+ // queue last frame for reencode after the repeat interval.
+ void queueFrameRepeat_l();
+
+ // save |item| as the latest buffer and queue it for reencode (repeat)
+ void setLatestBuffer_l(const VideoBuffer &item);
+
+ // submit last frame to encoder and queue it for reencode
+ // \return true if buffer was submitted, false if it wasn't (e.g. source is suspended, there
+ // is no available codec buffer)
+ bool repeatLatestBuffer_l();
+
+ // Time lapse / slow motion configuration
+ // --------------------------------------
+
+ // desired frame rate for encoding - value <= 0 if undefined
+ double mFps;
+
+ // desired frame rate for capture - value <= 0 if undefined
+ double mCaptureFps;
+
+ // Time lapse mode is enabled if the capture frame rate is defined and it is
+ // smaller than half the encoding frame rate (if defined). In this mode,
+ // frames that come in between the capture interval (the reciprocal of the
+ // capture frame rate) are dropped and the encoding timestamp is adjusted to
+ // match the desired encoding frame rate.
+ //
+ // Slow motion mode is enabled if both encoding and capture frame rates are
+ // defined and the encoding frame rate is less than half the capture frame
+ // rate. In this mode, the source is expected to produce frames with an even
+ // timestamp interval (after rounding) with the configured capture fps. The
+ // first source timestamp is used as the source base time. Afterwards, the
+ // timestamp of each source frame is snapped to the nearest expected capture
+ // timestamp and scaled to match the configured encoding frame rate.
+
+ // These modes must be enabled before using this source.
+
+ // adjusted capture timestamp of the base frame
+ int64_t mBaseCaptureUs;
+
+ // adjusted encoding timestamp of the base frame
+ int64_t mBaseFrameUs;
+
+ // number of frames from the base time
+ int64_t mFrameCount;
+
+ // adjusted capture timestamp for previous frame (negative if there were
+ // none)
+ int64_t mPrevCaptureUs;
+
+ // adjusted media timestamp for previous frame (negative if there were none)
+ int64_t mPrevFrameUs;
+
+ // desired offset between media time and capture time
+ int64_t mInputBufferTimeOffsetUs;
+
+ // Calculates and outputs the timestamp to use for a buffer with a specific buffer timestamp
+ // |bufferTimestampNs|. Returns false on failure (buffer too close or timestamp is moving
+ // backwards). Otherwise, stores the media timestamp in |*codecTimeUs| and returns true.
+ //
+ // This method takes into account the start time offset and any time lapse or slow motion time
+ // adjustment requests.
+ bool calculateCodecTimestamp_l(nsecs_t bufferTimeNs, int64_t *codecTimeUs);
+
+ void onMessageReceived(const sp<AMessage> &msg);
+
+ DISALLOW_EVIL_CONSTRUCTORS(GraphicBufferSource);
+};
+
+} // namespace android
+
+#endif // GRAPHIC_BUFFER_SOURCE_H_
diff --git a/media/libstagefright/gbs/tests/Android.bp b/media/libstagefright/gbs/tests/Android.bp
new file mode 100644
index 0000000..1463e73
--- /dev/null
+++ b/media/libstagefright/gbs/tests/Android.bp
@@ -0,0 +1,15 @@
+cc_test {
+ name: "FrameDropper_test",
+
+ srcs: ["FrameDropper_test.cpp"],
+
+ shared_libs: [
+ "libstagefright_gbs",
+ "libutils",
+ ],
+
+ cflags: [
+ "-Werror",
+ "-Wall",
+ ],
+}
diff --git a/media/libstagefright/gbs/tests/FrameDropper_test.cpp b/media/libstagefright/gbs/tests/FrameDropper_test.cpp
new file mode 100644
index 0000000..54a84d3
--- /dev/null
+++ b/media/libstagefright/gbs/tests/FrameDropper_test.cpp
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2015 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 "FrameDropper_test"
+#include <utils/Log.h>
+
+#include <gtest/gtest.h>
+
+#include <media/stagefright/gbs/FrameDropper.h>
+#include <media/stagefright/foundation/ADebug.h>
+
+namespace android {
+
+struct TestFrame {
+ int64_t timeUs;
+ bool shouldDrop;
+};
+
+static const TestFrame testFrames20Fps[] = {
+ {1000000, false}, {1050000, false}, {1100000, false}, {1150000, false},
+ {1200000, false}, {1250000, false}, {1300000, false}, {1350000, false},
+ {1400000, false}, {1450000, false}, {1500000, false}, {1550000, false},
+ {1600000, false}, {1650000, false}, {1700000, false}, {1750000, false},
+ {1800000, false}, {1850000, false}, {1900000, false}, {1950000, false},
+};
+
+static const TestFrame testFrames30Fps[] = {
+ {1000000, false}, {1033333, false}, {1066667, false}, {1100000, false},
+ {1133333, false}, {1166667, false}, {1200000, false}, {1233333, false},
+ {1266667, false}, {1300000, false}, {1333333, false}, {1366667, false},
+ {1400000, false}, {1433333, false}, {1466667, false}, {1500000, false},
+ {1533333, false}, {1566667, false}, {1600000, false}, {1633333, false},
+};
+
+static const TestFrame testFrames40Fps[] = {
+ {1000000, false}, {1025000, true}, {1050000, false}, {1075000, false},
+ {1100000, false}, {1125000, true}, {1150000, false}, {1175000, false},
+ {1200000, false}, {1225000, true}, {1250000, false}, {1275000, false},
+ {1300000, false}, {1325000, true}, {1350000, false}, {1375000, false},
+ {1400000, false}, {1425000, true}, {1450000, false}, {1475000, false},
+};
+
+static const TestFrame testFrames60Fps[] = {
+ {1000000, false}, {1016667, true}, {1033333, false}, {1050000, true},
+ {1066667, false}, {1083333, true}, {1100000, false}, {1116667, true},
+ {1133333, false}, {1150000, true}, {1166667, false}, {1183333, true},
+ {1200000, false}, {1216667, true}, {1233333, false}, {1250000, true},
+ {1266667, false}, {1283333, true}, {1300000, false}, {1316667, true},
+};
+
+static const TestFrame testFramesVariableFps[] = {
+ // 40fps
+ {1000000, false}, {1025000, true}, {1050000, false}, {1075000, false},
+ {1100000, false}, {1125000, true}, {1150000, false}, {1175000, false},
+ {1200000, false}, {1225000, true}, {1250000, false}, {1275000, false},
+ {1300000, false}, {1325000, true}, {1350000, false}, {1375000, false},
+ {1400000, false}, {1425000, true}, {1450000, false}, {1475000, false},
+ // a timestamp jump plus switch to 20fps
+ {2000000, false}, {2050000, false}, {2100000, false}, {2150000, false},
+ {2200000, false}, {2250000, false}, {2300000, false}, {2350000, false},
+ {2400000, false}, {2450000, false}, {2500000, false}, {2550000, false},
+ {2600000, false}, {2650000, false}, {2700000, false}, {2750000, false},
+ {2800000, false}, {2850000, false}, {2900000, false}, {2950000, false},
+ // 60fps
+ {2966667, false}, {2983333, true}, {3000000, false}, {3016667, true},
+ {3033333, false}, {3050000, true}, {3066667, false}, {3083333, true},
+ {3100000, false}, {3116667, true}, {3133333, false}, {3150000, true},
+ {3166667, false}, {3183333, true}, {3200000, false}, {3216667, true},
+ {3233333, false}, {3250000, true}, {3266667, false}, {3283333, true},
+};
+
+static const int kMaxTestJitterUs = 2000;
+// return one of 1000, 0, -1000 as jitter.
+static int GetJitter(size_t i) {
+ return (1 - (i % 3)) * (kMaxTestJitterUs / 2);
+}
+
+class FrameDropperTest : public ::testing::Test {
+public:
+ FrameDropperTest() : mFrameDropper(new FrameDropper()) {
+ EXPECT_EQ(OK, mFrameDropper->setMaxFrameRate(30.0));
+ }
+
+protected:
+ void RunTest(const TestFrame* frames, size_t size) {
+ for (size_t i = 0; i < size; ++i) {
+ int jitter = GetJitter(i);
+ int64_t testTimeUs = frames[i].timeUs + jitter;
+ printf("time %lld, testTime %lld, jitter %d\n",
+ (long long)frames[i].timeUs, (long long)testTimeUs, jitter);
+ EXPECT_EQ(frames[i].shouldDrop, mFrameDropper->shouldDrop(testTimeUs));
+ }
+ }
+
+ sp<FrameDropper> mFrameDropper;
+};
+
+TEST_F(FrameDropperTest, TestInvalidMaxFrameRate) {
+ EXPECT_NE(OK, mFrameDropper->setMaxFrameRate(-1.0));
+ EXPECT_NE(OK, mFrameDropper->setMaxFrameRate(0));
+}
+
+TEST_F(FrameDropperTest, Test20Fps) {
+ RunTest(testFrames20Fps, ARRAY_SIZE(testFrames20Fps));
+}
+
+TEST_F(FrameDropperTest, Test30Fps) {
+ RunTest(testFrames30Fps, ARRAY_SIZE(testFrames30Fps));
+}
+
+TEST_F(FrameDropperTest, Test40Fps) {
+ RunTest(testFrames40Fps, ARRAY_SIZE(testFrames40Fps));
+}
+
+TEST_F(FrameDropperTest, Test60Fps) {
+ RunTest(testFrames60Fps, ARRAY_SIZE(testFrames60Fps));
+}
+
+TEST_F(FrameDropperTest, TestVariableFps) {
+ RunTest(testFramesVariableFps, ARRAY_SIZE(testFramesVariableFps));
+}
+
+} // namespace android
diff --git a/media/libstagefright/httplive/Android.bp b/media/libstagefright/httplive/Android.bp
index ac113b8..de560b6 100644
--- a/media/libstagefright/httplive/Android.bp
+++ b/media/libstagefright/httplive/Android.bp
@@ -1,4 +1,4 @@
-cc_library_shared {
+cc_library {
name: "libstagefright_httplive",
srcs: [
diff --git a/media/libstagefright/include/CCodecBufferChannel.h b/media/libstagefright/include/CCodecBufferChannel.h
index c5062d6..57a1374 100644
--- a/media/libstagefright/include/CCodecBufferChannel.h
+++ b/media/libstagefright/include/CCodecBufferChannel.h
@@ -27,6 +27,7 @@
#include <C2Component.h>
#include <media/stagefright/foundation/Mutexed.h>
+#include <media/stagefright/gbs/GraphicBufferSource.h>
#include <media/stagefright/CodecBase.h>
#include <media/ICrypto.h>
@@ -35,134 +36,9 @@
/**
* BufferChannelBase implementation for CCodec.
*/
-class CCodecBufferChannel : public BufferChannelBase {
+class CCodecBufferChannel
+ : public BufferChannelBase, public std::enable_shared_from_this<CCodecBufferChannel> {
public:
- /**
- * Base class for representation of buffers at one port.
- */
- class Buffers {
- public:
- Buffers() = default;
- virtual ~Buffers() = default;
-
- /**
- * Set format for MediaCodec-facing buffers.
- */
- inline void setFormat(const sp<AMessage> &format) { mFormat = format; }
-
- /**
- * Returns true if the buffers are operating under array mode.
- */
- virtual bool isArrayMode() { return false; }
-
- /**
- * Fills the vector with MediaCodecBuffer's if in array mode; otherwise,
- * no-op.
- */
- virtual void getArray(Vector<sp<MediaCodecBuffer>> *) {}
-
- protected:
- // Format to be used for creating MediaCodec-facing buffers.
- sp<AMessage> mFormat;
-
- private:
- DISALLOW_EVIL_CONSTRUCTORS(Buffers);
- };
-
- class InputBuffers : public Buffers {
- public:
- using Buffers::Buffers;
- virtual ~InputBuffers() = default;
-
- /**
- * Set a block pool to obtain input memory blocks.
- */
- inline void setPool(const std::shared_ptr<C2BlockPool> &pool) { mPool = pool; }
-
- /**
- * Get a new MediaCodecBuffer for input and its corresponding index.
- * Returns false if no new buffer can be obtained at the moment.
- */
- virtual bool requestNewBuffer(size_t *index, sp<MediaCodecBuffer> *buffer) = 0;
-
- /**
- * Release the buffer obtained from requestNewBuffer() and get the
- * associated C2Buffer object back. Returns empty shared_ptr if the
- * buffer is not on file.
- */
- virtual std::shared_ptr<C2Buffer> releaseBuffer(const sp<MediaCodecBuffer> &buffer) = 0;
-
- /**
- * Flush internal state. After this call, no index or buffer previously
- * returned from requestNewBuffer() is valid.
- */
- virtual void flush() = 0;
-
- /**
- * Return array-backed version of input buffers. The returned object
- * shall retain the internal state so that it will honor index and
- * buffer from previous calls of requestNewBuffer().
- */
- virtual std::unique_ptr<InputBuffers> toArrayMode() = 0;
-
- protected:
- // Pool to obtain blocks for input buffers.
- std::shared_ptr<C2BlockPool> mPool;
-
- private:
- DISALLOW_EVIL_CONSTRUCTORS(InputBuffers);
- };
-
- class OutputBuffers : public Buffers {
- public:
- using Buffers::Buffers;
- virtual ~OutputBuffers() = default;
-
- /**
- * Register output C2Buffer from the component and obtain corresponding
- * index and MediaCodecBuffer object. Returns false if registration
- * fails.
- */
- virtual bool registerBuffer(
- const std::shared_ptr<C2Buffer> &buffer,
- size_t *index,
- sp<MediaCodecBuffer> *codecBuffer) = 0;
-
- /**
- * Register codec specific data as a buffer to be consistent with
- * MediaCodec behavior.
- */
- virtual bool registerCsd(
- const C2StreamCsdInfo::output * /* csd */,
- size_t * /* index */,
- sp<MediaCodecBuffer> * /* codecBuffer */) {
- return false;
- }
-
- /**
- * Release the buffer obtained from registerBuffer() and get the
- * associated C2Buffer object back. Returns empty shared_ptr if the
- * buffer is not on file.
- */
- virtual std::shared_ptr<C2Buffer> releaseBuffer(const sp<MediaCodecBuffer> &buffer) = 0;
-
- /**
- * Flush internal state. After this call, no index or buffer previously
- * returned from registerBuffer() is valid.
- */
- virtual void flush(const std::list<std::unique_ptr<C2Work>> &flushedWork) = 0;
-
- /**
- * Return array-backed version of output buffers. The returned object
- * shall retain the internal state so that it will honor index and
- * buffer from previous calls of registerBuffer().
- */
- virtual std::unique_ptr<OutputBuffers> toArrayMode() = 0;
-
- private:
- DISALLOW_EVIL_CONSTRUCTORS(OutputBuffers);
- };
-
CCodecBufferChannel(const std::function<void(status_t, enum ActionCode)> &onError);
virtual ~CCodecBufferChannel();
@@ -186,23 +62,21 @@
// Methods below are interface for CCodec to use.
+ /**
+ * Set the component object for buffer processing.
+ */
void setComponent(const std::shared_ptr<C2Component> &component);
+
+ /**
+ * Set output graphic surface for rendering.
+ */
status_t setSurface(const sp<Surface> &surface);
/**
- * Set C2BlockPool for input buffers.
- *
- * TODO: start timestamp?
+ * Set GraphicBufferSource object from which the component extracts input
+ * buffers.
*/
- void setInputBufferAllocator(const sp<C2BlockPool> &inAlloc);
-
- /**
- * Set C2BlockPool for output buffers. This object shall never use the
- * allocator itself; it's just passed
- *
- * TODO: start timestamp?
- */
- void setOutputBufferAllocator(const sp<C2BlockPool> &outAlloc);
+ status_t setGraphicBufferSource(const sp<GraphicBufferSource> &source);
/**
* Start queueing buffers to the component. This object should never queue
@@ -219,11 +93,17 @@
void flush(const std::list<std::unique_ptr<C2Work>> &flushedWork);
/**
- * Notify MediaCodec about work done.
+ * Notify input client about work done.
*
- * @param workItems finished work items.
+ * @param workItems finished work item.
*/
- void onWorkDone(std::vector<std::unique_ptr<C2Work>> workItems);
+ void onWorkDone(const std::unique_ptr<C2Work> &work);
+
+ // Internal classes
+ class Buffers;
+ class InputBuffers;
+ class OutputBuffers;
+ class InputBufferClient;
private:
class QueueGuard;
@@ -276,12 +156,17 @@
bool mRunning;
};
+ class C2ComponentWrapper;
+
+ void feedInputBufferIfAvailable();
+
QueueSync mSync;
sp<MemoryDealer> mDealer;
sp<IMemory> mDecryptDestination;
int32_t mHeapSeqNum;
std::shared_ptr<C2Component> mComponent;
+ std::shared_ptr<InputBufferClient> mInputClient;
std::function<void(status_t, enum ActionCode)> mOnError;
std::shared_ptr<C2BlockPool> mInputAllocator;
QueueSync mQueueSync;
diff --git a/media/libstagefright/include/media/stagefright/AACWriter.h b/media/libstagefright/include/media/stagefright/AACWriter.h
index aa60a19..7c63ddd 100644
--- a/media/libstagefright/include/media/stagefright/AACWriter.h
+++ b/media/libstagefright/include/media/stagefright/AACWriter.h
@@ -24,7 +24,6 @@
namespace android {
struct MediaSource;
-class MetaData;
struct AACWriter : public MediaWriter {
AACWriter(int fd);
diff --git a/media/libstagefright/include/media/stagefright/ACodec.h b/media/libstagefright/include/media/stagefright/ACodec.h
index 3196b10..fa22003 100644
--- a/media/libstagefright/include/media/stagefright/ACodec.h
+++ b/media/libstagefright/include/media/stagefright/ACodec.h
@@ -495,7 +495,7 @@
status_t verifySupportForProfileAndLevel(int32_t profile, int32_t level);
status_t configureBitrate(
- int32_t bitrate, OMX_VIDEO_CONTROLRATETYPE bitrateMode);
+ OMX_VIDEO_CONTROLRATETYPE bitrateMode, int32_t bitrate, int32_t quality = 0);
void configureEncoderLatency(const sp<AMessage> &msg);
status_t setupErrorCorrectionParameters();
diff --git a/media/libstagefright/include/media/stagefright/AMRWriter.h b/media/libstagefright/include/media/stagefright/AMRWriter.h
index 7d2c879..2ea2f78 100644
--- a/media/libstagefright/include/media/stagefright/AMRWriter.h
+++ b/media/libstagefright/include/media/stagefright/AMRWriter.h
@@ -25,8 +25,6 @@
namespace android {
-class MetaData;
-
struct AMRWriter : public MediaWriter {
AMRWriter(int fd);
diff --git a/media/libstagefright/include/media/stagefright/CCodec.h b/media/libstagefright/include/media/stagefright/CCodec.h
index 3e24bbe..c689761 100644
--- a/media/libstagefright/include/media/stagefright/CCodec.h
+++ b/media/libstagefright/include/media/stagefright/CCodec.h
@@ -24,6 +24,7 @@
#include <android/native_window.h>
#include <media/hardware/MetadataBufferType.h>
#include <media/stagefright/foundation/Mutexed.h>
+#include <media/stagefright/gbs/GraphicBufferSource.h>
#include <media/stagefright/CodecBase.h>
#include <media/stagefright/FrameRenderTracker.h>
#include <media/stagefright/MediaDefs.h>
@@ -58,6 +59,7 @@
virtual void signalRequestIDRFrame() override;
void initiateReleaseIfStuck();
+ void onWorkDone(std::vector<std::unique_ptr<C2Work>> &workItems);
protected:
virtual ~CCodec();
@@ -77,6 +79,10 @@
void flush();
void release(bool sendCallback);
+ void createInputSurface();
+ void setInputSurface(const sp<PersistentSurface> &surface);
+ status_t setupInputSurface(const sp<GraphicBufferSource> &source);
+
void setDeadline(const TimePoint &deadline);
enum {
@@ -86,6 +92,9 @@
kWhatFlush,
kWhatStop,
kWhatRelease,
+ kWhatCreateInputSurface,
+ kWhatSetInputSurface,
+ kWhatWorkDone,
};
enum {
@@ -104,14 +113,17 @@
struct State {
inline State() : mState(RELEASED) {}
+ inline int get() const { return mState; }
+ inline void set(int newState) { mState = newState; }
+ std::shared_ptr<C2Component> comp;
+ private:
int mState;
- std::shared_ptr<C2Component> mComp;
};
struct Formats {
- sp<AMessage> mInputFormat;
- sp<AMessage> mOutputFormat;
+ sp<AMessage> inputFormat;
+ sp<AMessage> outputFormat;
};
Mutexed<State> mState;
@@ -119,6 +131,7 @@
std::shared_ptr<C2Component::Listener> mListener;
Mutexed<TimePoint> mDeadline;
Mutexed<Formats> mFormats;
+ Mutexed<std::list<std::unique_ptr<C2Work>>> mWorkDoneQueue;
DISALLOW_EVIL_CONSTRUCTORS(CCodec);
};
diff --git a/media/libstagefright/include/media/stagefright/ColorConverter.h b/media/libstagefright/include/media/stagefright/ColorConverter.h
index f6bd353..a6c8981 100644
--- a/media/libstagefright/include/media/stagefright/ColorConverter.h
+++ b/media/libstagefright/include/media/stagefright/ColorConverter.h
@@ -33,6 +33,8 @@
bool isValid() const;
+ bool isDstRGB() const;
+
status_t convert(
const void *srcBits,
size_t srcWidth, size_t srcHeight,
diff --git a/media/libstagefright/include/media/stagefright/MPEG4Writer.h b/media/libstagefright/include/media/stagefright/MPEG4Writer.h
index 5d2c120..2a062cc 100644
--- a/media/libstagefright/include/media/stagefright/MPEG4Writer.h
+++ b/media/libstagefright/include/media/stagefright/MPEG4Writer.h
@@ -30,7 +30,6 @@
struct AMessage;
class MediaBuffer;
-class MetaData;
struct ABuffer;
class MPEG4Writer : public MediaWriter {
diff --git a/media/libstagefright/include/media/stagefright/MediaBuffer.h b/media/libstagefright/include/media/stagefright/MediaBuffer.h
deleted file mode 100644
index 367a467..0000000
--- a/media/libstagefright/include/media/stagefright/MediaBuffer.h
+++ /dev/null
@@ -1,231 +0,0 @@
-/*
- * Copyright (C) 2009 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 MEDIA_BUFFER_H_
-
-#define MEDIA_BUFFER_H_
-
-#include <atomic>
-#include <list>
-#include <media/stagefright/foundation/MediaBufferBase.h>
-
-#include <pthread.h>
-
-#include <binder/MemoryDealer.h>
-#include <utils/Errors.h>
-#include <utils/RefBase.h>
-
-namespace android {
-
-struct ABuffer;
-class GraphicBuffer;
-class MediaBuffer;
-class MediaBufferObserver;
-class MetaData;
-
-class MediaBufferObserver {
-public:
- MediaBufferObserver() {}
- virtual ~MediaBufferObserver() {}
-
- virtual void signalBufferReturned(MediaBuffer *buffer) = 0;
-
-private:
- MediaBufferObserver(const MediaBufferObserver &);
- MediaBufferObserver &operator=(const MediaBufferObserver &);
-};
-
-class MediaBuffer : public MediaBufferBase {
-public:
- // allocations larger than or equal to this will use shared memory.
- static const size_t kSharedMemThreshold = 64 * 1024;
-
- // The underlying data remains the responsibility of the caller!
- MediaBuffer(void *data, size_t size);
-
- explicit MediaBuffer(size_t size);
-
- explicit MediaBuffer(const sp<ABuffer> &buffer);
-
- MediaBuffer(const sp<IMemory> &mem) :
- MediaBuffer((uint8_t *)mem->pointer() + sizeof(SharedControl), mem->size()) {
- // delegate and override mMemory
- mMemory = mem;
- }
-
- // If MediaBufferGroup is set, decrement the local reference count;
- // if the local reference count drops to 0, return the buffer to the
- // associated MediaBufferGroup.
- //
- // If no MediaBufferGroup is set, the local reference count must be zero
- // when called, whereupon the MediaBuffer is deleted.
- virtual void release();
-
- // Increments the local reference count.
- // Use only when MediaBufferGroup is set.
- virtual void add_ref();
-
- void *data() const;
- size_t size() const;
-
- size_t range_offset() const;
- size_t range_length() const;
-
- void set_range(size_t offset, size_t length);
-
- sp<MetaData> meta_data();
-
- // Clears meta data and resets the range to the full extent.
- void reset();
-
- void setObserver(MediaBufferObserver *group);
-
- // Returns a clone of this MediaBuffer increasing its reference count.
- // The clone references the same data but has its own range and
- // MetaData.
- MediaBuffer *clone();
-
- // sum of localRefcount() and remoteRefcount()
- int refcount() const {
- return localRefcount() + remoteRefcount();
- }
-
- int localRefcount() const {
- return mRefCount;
- }
-
- int remoteRefcount() const {
- if (mMemory.get() == nullptr || mMemory->pointer() == nullptr) return 0;
- int32_t remoteRefcount =
- reinterpret_cast<SharedControl *>(mMemory->pointer())->getRemoteRefcount();
- // Sanity check so that remoteRefCount() is non-negative.
- return remoteRefcount >= 0 ? remoteRefcount : 0; // do not allow corrupted data.
- }
-
- // returns old value
- int addRemoteRefcount(int32_t value) {
- if (mMemory.get() == nullptr || mMemory->pointer() == nullptr) return 0;
- return reinterpret_cast<SharedControl *>(mMemory->pointer())->addRemoteRefcount(value);
- }
-
- bool isDeadObject() const {
- return isDeadObject(mMemory);
- }
-
- static bool isDeadObject(const sp<IMemory> &memory) {
- if (memory.get() == nullptr || memory->pointer() == nullptr) return false;
- return reinterpret_cast<SharedControl *>(memory->pointer())->isDeadObject();
- }
-
- // Sticky on enabling of shared memory MediaBuffers. By default we don't use
- // shared memory for MediaBuffers, but we enable this for those processes
- // that export MediaBuffers.
- static void useSharedMemory() {
- std::atomic_store_explicit(
- &mUseSharedMemory, (int_least32_t)1, std::memory_order_seq_cst);
- }
-
-protected:
- // true if MediaBuffer is observed (part of a MediaBufferGroup).
- inline bool isObserved() const {
- return mObserver != nullptr;
- }
-
- virtual ~MediaBuffer();
-
- sp<IMemory> mMemory;
-
-private:
- friend class MediaBufferGroup;
- friend class OMXDecoder;
- friend class BnMediaSource;
- friend class BpMediaSource;
-
- // For use by OMXDecoder, reference count must be 1, drop reference
- // count to 0 without signalling the observer.
- void claim();
-
- MediaBufferObserver *mObserver;
- int mRefCount;
-
- void *mData;
- size_t mSize, mRangeOffset, mRangeLength;
- sp<ABuffer> mBuffer;
-
- bool mOwnsData;
-
- sp<MetaData> mMetaData;
-
- MediaBuffer *mOriginal;
-
- static std::atomic_int_least32_t mUseSharedMemory;
-
- MediaBuffer(const MediaBuffer &);
- MediaBuffer &operator=(const MediaBuffer &);
-
- // SharedControl block at the start of IMemory.
- struct SharedControl {
- enum {
- FLAG_DEAD_OBJECT = (1 << 0),
- };
-
- // returns old value
- inline int32_t addRemoteRefcount(int32_t value) {
- return std::atomic_fetch_add_explicit(
- &mRemoteRefcount, (int_least32_t)value, std::memory_order_seq_cst);
- }
-
- inline int32_t getRemoteRefcount() const {
- return std::atomic_load_explicit(&mRemoteRefcount, std::memory_order_seq_cst);
- }
-
- inline void setRemoteRefcount(int32_t value) {
- std::atomic_store_explicit(
- &mRemoteRefcount, (int_least32_t)value, std::memory_order_seq_cst);
- }
-
- inline bool isDeadObject() const {
- return (std::atomic_load_explicit(
- &mFlags, std::memory_order_seq_cst) & FLAG_DEAD_OBJECT) != 0;
- }
-
- inline void setDeadObject() {
- (void)std::atomic_fetch_or_explicit(
- &mFlags, (int_least32_t)FLAG_DEAD_OBJECT, std::memory_order_seq_cst);
- }
-
- inline void clear() {
- std::atomic_store_explicit(
- &mFlags, (int_least32_t)0, std::memory_order_seq_cst);
- std::atomic_store_explicit(
- &mRemoteRefcount, (int_least32_t)0, std::memory_order_seq_cst);
- }
-
- private:
- // Caution: atomic_int_fast32_t is 64 bits on LP64.
- std::atomic_int_least32_t mFlags;
- std::atomic_int_least32_t mRemoteRefcount;
- int32_t unused[6] __attribute__((__unused__)); // additional buffer space
- };
-
- inline SharedControl *getSharedControl() const {
- return reinterpret_cast<SharedControl *>(mMemory->pointer());
- }
-};
-
-} // namespace android
-
-#endif // MEDIA_BUFFER_H_
diff --git a/media/libstagefright/include/media/stagefright/MediaBuffer.h b/media/libstagefright/include/media/stagefright/MediaBuffer.h
new file mode 120000
index 0000000..1d49c1a
--- /dev/null
+++ b/media/libstagefright/include/media/stagefright/MediaBuffer.h
@@ -0,0 +1 @@
+../../../../libmediaextractor/include/media/stagefright/MediaBuffer.h
\ No newline at end of file
diff --git a/media/libstagefright/include/media/stagefright/MediaBufferGroup.h b/media/libstagefright/include/media/stagefright/MediaBufferGroup.h
deleted file mode 100644
index 3051406..0000000
--- a/media/libstagefright/include/media/stagefright/MediaBufferGroup.h
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2009 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 MEDIA_BUFFER_GROUP_H_
-
-#define MEDIA_BUFFER_GROUP_H_
-
-#include <media/stagefright/MediaBuffer.h>
-#include <utils/Errors.h>
-#include <utils/threads.h>
-
-namespace android {
-
-class MediaBuffer;
-class MetaData;
-
-class MediaBufferGroup : public MediaBufferObserver {
-public:
- MediaBufferGroup(size_t growthLimit = 0);
-
- // create a media buffer group with preallocated buffers
- MediaBufferGroup(size_t buffers, size_t buffer_size, size_t growthLimit = 0);
-
- ~MediaBufferGroup();
-
- void add_buffer(MediaBuffer *buffer);
-
- bool has_buffers();
-
- // If nonBlocking is false, it blocks until a buffer is available and
- // passes it to the caller in *buffer, while returning OK.
- // The returned buffer will have a reference count of 1.
- // If nonBlocking is true and a buffer is not immediately available,
- // buffer is set to NULL and it returns WOULD_BLOCK.
- // If requestedSize is 0, any free MediaBuffer will be returned.
- // If requestedSize is > 0, the returned MediaBuffer should have buffer
- // size of at least requstedSize.
- status_t acquire_buffer(
- MediaBuffer **buffer, bool nonBlocking = false, size_t requestedSize = 0);
-
- size_t buffers() const { return mBuffers.size(); }
-
- // If buffer is nullptr, have acquire_buffer() check for remote release.
- virtual void signalBufferReturned(MediaBuffer *buffer);
-
-private:
- friend class MediaBuffer;
-
- Mutex mLock;
- Condition mCondition;
- size_t mGrowthLimit; // Do not automatically grow group larger than this.
- std::list<MediaBuffer *> mBuffers;
-
- MediaBufferGroup(const MediaBufferGroup &);
- MediaBufferGroup &operator=(const MediaBufferGroup &);
-};
-
-} // namespace android
-
-#endif // MEDIA_BUFFER_GROUP_H_
diff --git a/media/libstagefright/include/media/stagefright/MediaBufferGroup.h b/media/libstagefright/include/media/stagefright/MediaBufferGroup.h
new file mode 120000
index 0000000..009b3d9
--- /dev/null
+++ b/media/libstagefright/include/media/stagefright/MediaBufferGroup.h
@@ -0,0 +1 @@
+../../../../libmediaextractor/include/media/stagefright/MediaBufferGroup.h
\ No newline at end of file
diff --git a/media/libstagefright/include/media/stagefright/MediaCodecSource.h b/media/libstagefright/include/media/stagefright/MediaCodecSource.h
index bc0653d..eec115e 100644
--- a/media/libstagefright/include/media/stagefright/MediaCodecSource.h
+++ b/media/libstagefright/include/media/stagefright/MediaCodecSource.h
@@ -30,7 +30,6 @@
struct AReplyToken;
class IGraphicBufferProducer;
struct MediaCodec;
-class MetaData;
struct MediaCodecSource : public MediaSource,
public MediaBufferObserver {
diff --git a/media/libstagefright/include/media/stagefright/MediaExtractorFactory.h b/media/libstagefright/include/media/stagefright/MediaExtractorFactory.h
index 7fddf80..e5c67e1 100644
--- a/media/libstagefright/include/media/stagefright/MediaExtractorFactory.h
+++ b/media/libstagefright/include/media/stagefright/MediaExtractorFactory.h
@@ -41,14 +41,17 @@
static sp<IMediaExtractor> CreateFromService(
const sp<DataSource> &source, const char *mime = NULL);
static void LoadPlugins(const ::std::string& apkPath);
+ static status_t dump(int fd, const Vector<String16>& args);
private:
static Mutex gPluginMutex;
static std::shared_ptr<List<sp<ExtractorPlugin>>> gPlugins;
static bool gPluginsRegistered;
- static void RegisterExtractors(
+ static void RegisterExtractorsInApk(
const char *apkPath, List<sp<ExtractorPlugin>> &pluginList);
+ static void RegisterExtractorsInSystem(
+ const char *libDirPath, List<sp<ExtractorPlugin>> &pluginList);
static void RegisterExtractor(
const sp<ExtractorPlugin> &plugin, List<sp<ExtractorPlugin>> &pluginList);
diff --git a/media/libstagefright/include/media/stagefright/MediaWriter.h b/media/libstagefright/include/media/stagefright/MediaWriter.h
index c4bba0e..2c12a87 100644
--- a/media/libstagefright/include/media/stagefright/MediaWriter.h
+++ b/media/libstagefright/include/media/stagefright/MediaWriter.h
@@ -24,8 +24,6 @@
namespace android {
-class MetaData;
-
struct MediaWriter : public RefBase {
MediaWriter()
: mMaxFileSizeLimitBytes(0),
diff --git a/media/libstagefright/include/media/stagefright/MetaData.h b/media/libstagefright/include/media/stagefright/MetaData.h
index 3438c56..5959e86 100644
--- a/media/libstagefright/include/media/stagefright/MetaData.h
+++ b/media/libstagefright/include/media/stagefright/MetaData.h
@@ -24,7 +24,6 @@
#include <binder/Parcel.h>
#include <utils/RefBase.h>
-#include <utils/KeyedVector.h>
#include <utils/String8.h>
namespace android {
@@ -229,7 +228,7 @@
kTypeD263 = 'd263',
};
-class MetaData : public RefBase {
+class MetaData final : public RefBase {
public:
MetaData();
MetaData(const MetaData &from);
@@ -279,59 +278,22 @@
String8 toString() const;
void dumpToLog() const;
- status_t writeToParcel(Parcel &parcel);
- status_t updateFromParcel(const Parcel &parcel);
- static sp<MetaData> createFromParcel(const Parcel &parcel);
-
protected:
virtual ~MetaData();
private:
- struct typed_data {
- typed_data();
- ~typed_data();
+ friend class BpMediaSource;
+ friend class BnMediaSource;
+ friend class BpMediaExtractor;
+ friend class BnMediaExtractor;
- typed_data(const MetaData::typed_data &);
- typed_data &operator=(const MetaData::typed_data &);
-
- void clear();
- void setData(uint32_t type, const void *data, size_t size);
- void getData(uint32_t *type, const void **data, size_t *size) const;
- // may include hexdump of binary data if verbose=true
- String8 asString(bool verbose) const;
-
- private:
- uint32_t mType;
- size_t mSize;
-
- union {
- void *ext_data;
- float reservoir;
- } u;
-
- bool usesReservoir() const {
- return mSize <= sizeof(u.reservoir);
- }
-
- void *allocateStorage(size_t size);
- void freeStorage();
-
- void *storage() {
- return usesReservoir() ? &u.reservoir : u.ext_data;
- }
-
- const void *storage() const {
- return usesReservoir() ? &u.reservoir : u.ext_data;
- }
- };
-
- struct Rect {
- int32_t mLeft, mTop, mRight, mBottom;
- };
-
- KeyedVector<uint32_t, typed_data> mItems;
-
- // MetaData &operator=(const MetaData &);
+ status_t writeToParcel(Parcel &parcel);
+ status_t updateFromParcel(const Parcel &parcel);
+ static sp<MetaData> createFromParcel(const Parcel &parcel);
+ struct typed_data;
+ struct Rect;
+ struct MetaDataInternal;
+ MetaDataInternal *mInternalData;
};
} // namespace android
diff --git a/media/libstagefright/include/media/stagefright/NuMediaExtractor.h b/media/libstagefright/include/media/stagefright/NuMediaExtractor.h
index eed0f05..6a2e39b 100644
--- a/media/libstagefright/include/media/stagefright/NuMediaExtractor.h
+++ b/media/libstagefright/include/media/stagefright/NuMediaExtractor.h
@@ -85,6 +85,7 @@
// readSampleData() reads the sample with the lowest timestamp.
status_t readSampleData(const sp<ABuffer> &buffer);
+ status_t getSampleSize(size_t *sampleSize);
status_t getSampleTrackIndex(size_t *trackIndex);
status_t getSampleTime(int64_t *sampleTimeUs);
status_t getSampleMeta(sp<MetaData> *sampleMeta);
diff --git a/media/libstagefright/include/media/stagefright/Utils.h b/media/libstagefright/include/media/stagefright/Utils.h
index 7d4a611..6a28e0b 100644
--- a/media/libstagefright/include/media/stagefright/Utils.h
+++ b/media/libstagefright/include/media/stagefright/Utils.h
@@ -28,7 +28,6 @@
namespace android {
-class MetaData;
struct AMessage;
status_t convertMetaDataToMessage(
const sp<MetaData> &meta, sp<AMessage> *format);
diff --git a/media/libstagefright/omx/1.0/Omx.cpp b/media/libstagefright/omx/1.0/Omx.cpp
index fe50656..4e2d398 100644
--- a/media/libstagefright/omx/1.0/Omx.cpp
+++ b/media/libstagefright/omx/1.0/Omx.cpp
@@ -24,7 +24,7 @@
#include <media/stagefright/omx/OMXUtils.h>
#include <media/stagefright/omx/OMXMaster.h>
-#include <media/stagefright/omx/GraphicBufferSource.h>
+#include <media/stagefright/omx/OmxGraphicBufferSource.h>
#include <media/stagefright/omx/1.0/WOmxNode.h>
#include <media/stagefright/omx/1.0/WOmxObserver.h>
@@ -148,7 +148,7 @@
Return<void> Omx::createInputSurface(createInputSurface_cb _hidl_cb) {
sp<::android::IGraphicBufferProducer> bufferProducer;
- sp<GraphicBufferSource> graphicBufferSource = new GraphicBufferSource();
+ sp<OmxGraphicBufferSource> graphicBufferSource = new OmxGraphicBufferSource();
status_t err = graphicBufferSource->initCheck();
if (err != OK) {
LOG(ERROR) << "Failed to create persistent input surface: "
diff --git a/media/libstagefright/omx/1.0/WGraphicBufferSource.cpp b/media/libstagefright/omx/1.0/WGraphicBufferSource.cpp
index 3201c32..ed272bb 100644
--- a/media/libstagefright/omx/1.0/WGraphicBufferSource.cpp
+++ b/media/libstagefright/omx/1.0/WGraphicBufferSource.cpp
@@ -79,9 +79,9 @@
};
struct TWGraphicBufferSource::TWOmxBufferSource : public IOmxBufferSource {
- sp<GraphicBufferSource> mSource;
+ sp<OmxGraphicBufferSource> mSource;
- TWOmxBufferSource(const sp<GraphicBufferSource> &source): mSource(source) {
+ TWOmxBufferSource(const sp<OmxGraphicBufferSource> &source): mSource(source) {
}
Return<void> onOmxExecuting() override {
@@ -115,7 +115,7 @@
// TWGraphicBufferSource
TWGraphicBufferSource::TWGraphicBufferSource(
- sp<GraphicBufferSource> const& base) :
+ sp<OmxGraphicBufferSource> const& base) :
mBase(base),
mOmxBufferSource(new TWOmxBufferSource(base)) {
}
diff --git a/media/libstagefright/omx/Android.bp b/media/libstagefright/omx/Android.bp
index 8539864..306a8eb 100644
--- a/media/libstagefright/omx/Android.bp
+++ b/media/libstagefright/omx/Android.bp
@@ -6,12 +6,11 @@
},
srcs: [
- "FrameDropper.cpp",
- "GraphicBufferSource.cpp",
"BWGraphicBufferSource.cpp",
"OMXMaster.cpp",
"OMXNodeInstance.cpp",
"OMXUtils.cpp",
+ "OmxGraphicBufferSource.cpp",
"SimpleSoftOMXComponent.cpp",
"SoftOMXComponent.cpp",
"SoftOMXPlugin.cpp",
@@ -49,6 +48,7 @@
"libgui",
"libcutils",
"libstagefright_foundation",
+ "libstagefright_gbs",
"libstagefright_xmlparser",
"libdl",
"libhidlbase",
diff --git a/media/libstagefright/omx/BWGraphicBufferSource.cpp b/media/libstagefright/omx/BWGraphicBufferSource.cpp
index 94ef598..fa30a46 100644
--- a/media/libstagefright/omx/BWGraphicBufferSource.cpp
+++ b/media/libstagefright/omx/BWGraphicBufferSource.cpp
@@ -55,9 +55,9 @@
};
struct BWGraphicBufferSource::BWOMXBufferSource : public BnOMXBufferSource {
- sp<GraphicBufferSource> mSource;
+ sp<OmxGraphicBufferSource> mSource;
- BWOMXBufferSource(const sp<GraphicBufferSource> &source): mSource(source) {
+ BWOMXBufferSource(const sp<OmxGraphicBufferSource> &source): mSource(source) {
}
Status onOmxExecuting() override {
@@ -83,7 +83,7 @@
};
BWGraphicBufferSource::BWGraphicBufferSource(
- sp<GraphicBufferSource> const& base) :
+ sp<OmxGraphicBufferSource> const& base) :
mBase(base),
mOMXBufferSource(new BWOMXBufferSource(base)) {
}
diff --git a/media/libstagefright/omx/FrameDropper.cpp b/media/libstagefright/omx/FrameDropper.cpp
deleted file mode 100644
index 0c50c58..0000000
--- a/media/libstagefright/omx/FrameDropper.cpp
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2015 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 "FrameDropper"
-#include <utils/Log.h>
-
-#include <media/stagefright/omx/FrameDropper.h>
-#include <media/stagefright/foundation/ADebug.h>
-
-namespace android {
-
-static const int64_t kMaxJitterUs = 2000;
-
-FrameDropper::FrameDropper()
- : mDesiredMinTimeUs(-1),
- mMinIntervalUs(0) {
-}
-
-FrameDropper::~FrameDropper() {
-}
-
-status_t FrameDropper::setMaxFrameRate(float maxFrameRate) {
- if (maxFrameRate <= 0) {
- ALOGE("framerate should be positive but got %f.", maxFrameRate);
- return BAD_VALUE;
- }
- mMinIntervalUs = (int64_t) (1000000.0f / maxFrameRate);
- return OK;
-}
-
-bool FrameDropper::shouldDrop(int64_t timeUs) {
- if (mMinIntervalUs <= 0) {
- return false;
- }
-
- if (mDesiredMinTimeUs < 0) {
- mDesiredMinTimeUs = timeUs + mMinIntervalUs;
- ALOGV("first frame %lld, next desired frame %lld",
- (long long)timeUs, (long long)mDesiredMinTimeUs);
- return false;
- }
-
- if (timeUs < (mDesiredMinTimeUs - kMaxJitterUs)) {
- ALOGV("drop frame %lld, desired frame %lld, diff %lld",
- (long long)timeUs, (long long)mDesiredMinTimeUs,
- (long long)(mDesiredMinTimeUs - timeUs));
- return true;
- }
-
- int64_t n = (timeUs - mDesiredMinTimeUs + kMaxJitterUs) / mMinIntervalUs;
- mDesiredMinTimeUs += (n + 1) * mMinIntervalUs;
- ALOGV("keep frame %lld, next desired frame %lld, diff %lld",
- (long long)timeUs, (long long)mDesiredMinTimeUs,
- (long long)(mDesiredMinTimeUs - timeUs));
- return false;
-}
-
-} // namespace android
diff --git a/media/libstagefright/omx/GraphicBufferSource.cpp b/media/libstagefright/omx/GraphicBufferSource.cpp
deleted file mode 100644
index f331dbb..0000000
--- a/media/libstagefright/omx/GraphicBufferSource.cpp
+++ /dev/null
@@ -1,1370 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <inttypes.h>
-
-#define LOG_TAG "GraphicBufferSource"
-//#define LOG_NDEBUG 0
-#include <utils/Log.h>
-
-#define STRINGIFY_ENUMS // for asString in HardwareAPI.h/VideoAPI.h
-
-#include <media/stagefright/omx/GraphicBufferSource.h>
-#include <media/stagefright/omx/FrameDropper.h>
-#include <media/stagefright/omx/OMXUtils.h>
-#include <media/stagefright/foundation/ADebug.h>
-#include <media/stagefright/foundation/AMessage.h>
-#include <media/stagefright/foundation/ColorUtils.h>
-#include <media/stagefright/foundation/FileDescriptor.h>
-
-#include <media/hardware/MetadataBufferType.h>
-#include <ui/GraphicBuffer.h>
-#include <gui/BufferItem.h>
-#include <media/hardware/HardwareAPI.h>
-#include <media/openmax/OMX_Component.h>
-#include <media/openmax/OMX_IndexExt.h>
-#include <media/OMXBuffer.h>
-
-#include <inttypes.h>
-
-#include <functional>
-#include <memory>
-#include <cmath>
-
-namespace android {
-
-namespace {
-// kTimestampFluctuation is an upper bound of timestamp fluctuation from the
-// source that GraphicBufferSource allows. The unit of kTimestampFluctuation is
-// frames. More specifically, GraphicBufferSource will drop a frame if
-//
-// expectedNewFrametimestamp - actualNewFrameTimestamp <
-// (0.5 - kTimestampFluctuation) * expectedtimePeriodBetweenFrames
-//
-// where
-// - expectedNewFrameTimestamp is the calculated ideal timestamp of the new
-// incoming frame
-// - actualNewFrameTimestamp is the timestamp received from the source
-// - expectedTimePeriodBetweenFrames is the ideal difference of the timestamps
-// of two adjacent frames
-//
-// See GraphicBufferSource::calculateCodecTimestamp_l() for more detail about
-// how kTimestampFluctuation is used.
-//
-// kTimestampFluctuation should be non-negative. A higher value causes a smaller
-// chance of dropping frames, but at the same time a higher bound on the
-// difference between the source timestamp and the interpreted (snapped)
-// timestamp.
-//
-// The value of 0.05 means that GraphicBufferSource expects the input timestamps
-// to fluctuate no more than 5% from the regular time period.
-//
-// TODO: Justify the choice of this value, or make it configurable.
-constexpr double kTimestampFluctuation = 0.05;
-}
-
-/**
- * A copiable object managing a buffer in the buffer cache managed by the producer. This object
- * holds a reference to the buffer, and maintains which buffer slot it belongs to (if any), and
- * whether it is still in a buffer slot. It also maintains whether there are any outstanging acquire
- * references to it (by buffers acquired from the slot) mainly so that we can keep a debug
- * count of how many buffers we need to still release back to the producer.
- */
-struct GraphicBufferSource::CachedBuffer {
- /**
- * Token that is used to track acquire counts (as opposed to all references to this object).
- */
- struct Acquirable { };
-
- /**
- * Create using a buffer cached in a slot.
- */
- CachedBuffer(slot_id slot, const sp<GraphicBuffer> &graphicBuffer)
- : mIsCached(true),
- mSlot(slot),
- mGraphicBuffer(graphicBuffer),
- mAcquirable(std::make_shared<Acquirable>()) {
- }
-
- /**
- * Returns the cache slot that this buffer is cached in, or -1 if it is no longer cached.
- *
- * This assumes that -1 slot id is invalid; though, it is just a benign collision used for
- * debugging. This object explicitly manages whether it is still cached.
- */
- slot_id getSlot() const {
- return mIsCached ? mSlot : -1;
- }
-
- /**
- * Returns the cached buffer.
- */
- sp<GraphicBuffer> getGraphicBuffer() const {
- return mGraphicBuffer;
- }
-
- /**
- * Checks whether this buffer is still in the buffer cache.
- */
- bool isCached() const {
- return mIsCached;
- }
-
- /**
- * Checks whether this buffer has an acquired reference.
- */
- bool isAcquired() const {
- return mAcquirable.use_count() > 1;
- }
-
- /**
- * Gets and returns a shared acquired reference.
- */
- std::shared_ptr<Acquirable> getAcquirable() {
- return mAcquirable;
- }
-
-private:
- friend void GraphicBufferSource::discardBufferAtSlotIndex_l(ssize_t);
-
- /**
- * This method to be called when the buffer is no longer in the buffer cache.
- * Called from discardBufferAtSlotIndex_l.
- */
- void onDroppedFromCache() {
- CHECK_DBG(mIsCached);
- mIsCached = false;
- }
-
- bool mIsCached;
- slot_id mSlot;
- sp<GraphicBuffer> mGraphicBuffer;
- std::shared_ptr<Acquirable> mAcquirable;
-};
-
-/**
- * A copiable object managing a buffer acquired from the producer. This must always be a cached
- * buffer. This objects also manages its acquire fence and any release fences that may be returned
- * by the encoder for this buffer (this buffer may be queued to the encoder multiple times).
- * If no release fences are added by the encoder, the acquire fence is returned as the release
- * fence for this - as it is assumed that noone waited for the acquire fence. Otherwise, it is
- * assumed that the encoder has waited for the acquire fence (or returned it as the release
- * fence).
- */
-struct GraphicBufferSource::AcquiredBuffer {
- AcquiredBuffer(
- const std::shared_ptr<CachedBuffer> &buffer,
- std::function<void(AcquiredBuffer *)> onReleased,
- const sp<Fence> &acquireFence)
- : mBuffer(buffer),
- mAcquirable(buffer->getAcquirable()),
- mAcquireFence(acquireFence),
- mGotReleaseFences(false),
- mOnReleased(onReleased) {
- }
-
- /**
- * Adds a release fence returned by the encoder to this object. If this is called with an
- * valid file descriptor, it is added to the list of release fences. These are returned to the
- * producer on release() as a merged fence. Regardless of the validity of the file descriptor,
- * we take note that a release fence was attempted to be added and the acquire fence can now be
- * assumed as acquired.
- */
- void addReleaseFenceFd(int fenceFd) {
- // save all release fences - these will be propagated to the producer if this buffer is
- // ever released to it
- if (fenceFd >= 0) {
- mReleaseFenceFds.push_back(fenceFd);
- }
- mGotReleaseFences = true;
- }
-
- /**
- * Returns the acquire fence file descriptor associated with this object.
- */
- int getAcquireFenceFd() {
- if (mAcquireFence == nullptr || !mAcquireFence->isValid()) {
- return -1;
- }
- return mAcquireFence->dup();
- }
-
- /**
- * Returns whether the buffer is still in the buffer cache.
- */
- bool isCached() const {
- return mBuffer->isCached();
- }
-
- /**
- * Returns the acquired buffer.
- */
- sp<GraphicBuffer> getGraphicBuffer() const {
- return mBuffer->getGraphicBuffer();
- }
-
- /**
- * Returns the slot that this buffer is cached at, or -1 otherwise.
- *
- * This assumes that -1 slot id is invalid; though, it is just a benign collision used for
- * debugging. This object explicitly manages whether it is still cached.
- */
- slot_id getSlot() const {
- return mBuffer->getSlot();
- }
-
- /**
- * Creates and returns a release fence object from the acquire fence and/or any release fences
- * added. If no release fences were added (even if invalid), returns the acquire fence.
- * Otherwise, it returns a merged fence from all the valid release fences added.
- */
- sp<Fence> getReleaseFence() {
- // If did not receive release fences, we assume this buffer was not consumed (it was
- // discarded or dropped). In this case release the acquire fence as the release fence.
- // We do this here to avoid a dup, close and recreation of the Fence object.
- if (!mGotReleaseFences) {
- return mAcquireFence;
- }
- sp<Fence> ret = getReleaseFence(0, mReleaseFenceFds.size());
- // clear fds as fence took ownership of them
- mReleaseFenceFds.clear();
- return ret;
- }
-
- // this video buffer is no longer referenced by the codec (or kept for later encoding)
- // it is now safe to release to the producer
- ~AcquiredBuffer() {
- //mAcquirable.clear();
- mOnReleased(this);
- // mOnRelease method should call getReleaseFence() that releases all fds but just in case
- ALOGW_IF(!mReleaseFenceFds.empty(), "release fences were not obtained, closing fds");
- for (int fildes : mReleaseFenceFds) {
- ::close(fildes);
- TRESPASS_DBG();
- }
- }
-
-private:
- std::shared_ptr<GraphicBufferSource::CachedBuffer> mBuffer;
- std::shared_ptr<GraphicBufferSource::CachedBuffer::Acquirable> mAcquirable;
- sp<Fence> mAcquireFence;
- Vector<int> mReleaseFenceFds;
- bool mGotReleaseFences;
- std::function<void(AcquiredBuffer *)> mOnReleased;
-
- /**
- * Creates and returns a release fence from 0 or more release fence file descriptors in from
- * the specified range in the array.
- *
- * @param start start index
- * @param num number of release fds to merge
- */
- sp<Fence> getReleaseFence(size_t start, size_t num) const {
- if (num == 0) {
- return Fence::NO_FENCE;
- } else if (num == 1) {
- return new Fence(mReleaseFenceFds[start]);
- } else {
- return Fence::merge("GBS::AB",
- getReleaseFence(start, num >> 1),
- getReleaseFence(start + (num >> 1), num - (num >> 1)));
- }
- }
-};
-
-GraphicBufferSource::GraphicBufferSource() :
- mInitCheck(UNKNOWN_ERROR),
- mNumAvailableUnacquiredBuffers(0),
- mNumOutstandingAcquires(0),
- mEndOfStream(false),
- mEndOfStreamSent(false),
- mLastDataspace(HAL_DATASPACE_UNKNOWN),
- mExecuting(false),
- mSuspended(false),
- mLastFrameTimestampUs(-1),
- mStopTimeUs(-1),
- mLastActionTimeUs(-1ll),
- mSkipFramesBeforeNs(-1ll),
- mFrameRepeatIntervalUs(-1ll),
- mRepeatLastFrameGeneration(0),
- mOutstandingFrameRepeatCount(0),
- mFrameRepeatBlockedOnCodecBuffer(false),
- mFps(-1.0),
- mCaptureFps(-1.0),
- mBaseCaptureUs(-1ll),
- mBaseFrameUs(-1ll),
- mFrameCount(0),
- mPrevCaptureUs(-1ll),
- mPrevFrameUs(-1ll),
- mInputBufferTimeOffsetUs(0ll) {
- ALOGV("GraphicBufferSource");
-
- String8 name("GraphicBufferSource");
-
- BufferQueue::createBufferQueue(&mProducer, &mConsumer);
- mConsumer->setConsumerName(name);
-
- // Note that we can't create an sp<...>(this) in a ctor that will not keep a
- // reference once the ctor ends, as that would cause the refcount of 'this'
- // dropping to 0 at the end of the ctor. Since all we need is a wp<...>
- // that's what we create.
- wp<BufferQueue::ConsumerListener> listener =
- static_cast<BufferQueue::ConsumerListener*>(this);
- sp<IConsumerListener> proxy =
- new BufferQueue::ProxyConsumerListener(listener);
-
- mInitCheck = mConsumer->consumerConnect(proxy, false);
- if (mInitCheck != NO_ERROR) {
- ALOGE("Error connecting to BufferQueue: %s (%d)",
- strerror(-mInitCheck), mInitCheck);
- return;
- }
-
- memset(&mDefaultColorAspectsPacked, 0, sizeof(mDefaultColorAspectsPacked));
-
- CHECK(mInitCheck == NO_ERROR);
-}
-
-GraphicBufferSource::~GraphicBufferSource() {
- ALOGV("~GraphicBufferSource");
- {
- // all acquired buffers must be freed with the mutex locked otherwise our debug assertion
- // may trigger
- Mutex::Autolock autoLock(mMutex);
- mAvailableBuffers.clear();
- mSubmittedCodecBuffers.clear();
- mLatestBuffer.mBuffer.reset();
- }
-
- if (mNumOutstandingAcquires != 0) {
- ALOGW("potential buffer leak: acquired=%d", mNumOutstandingAcquires);
- TRESPASS_DBG();
- }
- if (mConsumer != NULL) {
- status_t err = mConsumer->consumerDisconnect();
- if (err != NO_ERROR) {
- ALOGW("consumerDisconnect failed: %d", err);
- }
- }
-}
-
-Status GraphicBufferSource::onOmxExecuting() {
- Mutex::Autolock autoLock(mMutex);
- ALOGV("--> executing; available=%zu, submittable=%zd",
- mAvailableBuffers.size(), mFreeCodecBuffers.size());
- CHECK(!mExecuting);
- mExecuting = true;
- mLastDataspace = HAL_DATASPACE_UNKNOWN;
- ALOGV("clearing last dataSpace");
-
- // Start by loading up as many buffers as possible. We want to do this,
- // rather than just submit the first buffer, to avoid a degenerate case:
- // if all BQ buffers arrive before we start executing, and we only submit
- // one here, the other BQ buffers will just sit until we get notified
- // that the codec buffer has been released. We'd then acquire and
- // submit a single additional buffer, repeatedly, never using more than
- // one codec buffer simultaneously. (We could instead try to submit
- // all BQ buffers whenever any codec buffer is freed, but if we get the
- // initial conditions right that will never be useful.)
- while (haveAvailableBuffers_l()) {
- if (!fillCodecBuffer_l()) {
- ALOGV("stop load with available=%zu+%d",
- mAvailableBuffers.size(), mNumAvailableUnacquiredBuffers);
- break;
- }
- }
-
- ALOGV("done loading initial frames, available=%zu+%d",
- mAvailableBuffers.size(), mNumAvailableUnacquiredBuffers);
-
- // If EOS has already been signaled, and there are no more frames to
- // submit, try to send EOS now as well.
- if (mStopTimeUs == -1 && mEndOfStream && !haveAvailableBuffers_l()) {
- submitEndOfInputStream_l();
- }
-
- if (mFrameRepeatIntervalUs > 0ll && mLooper == NULL) {
- mReflector = new AHandlerReflector<GraphicBufferSource>(this);
-
- mLooper = new ALooper;
- mLooper->registerHandler(mReflector);
- mLooper->start();
-
- if (mLatestBuffer.mBuffer != nullptr) {
- queueFrameRepeat_l();
- }
- }
-
- return Status::ok();
-}
-
-Status GraphicBufferSource::onOmxIdle() {
- ALOGV("omxIdle");
-
- Mutex::Autolock autoLock(mMutex);
-
- if (mExecuting) {
- // We are only interested in the transition from executing->idle,
- // not loaded->idle.
- mExecuting = false;
- }
- return Status::ok();
-}
-
-Status GraphicBufferSource::onOmxLoaded(){
- Mutex::Autolock autoLock(mMutex);
- if (mLooper != NULL) {
- mLooper->unregisterHandler(mReflector->id());
- mReflector.clear();
-
- mLooper->stop();
- mLooper.clear();
- }
-
- ALOGV("--> loaded; 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();
- mOMXNode.clear();
- mExecuting = false;
-
- return Status::ok();
-}
-
-Status GraphicBufferSource::onInputBufferAdded(codec_buffer_id bufferId) {
- Mutex::Autolock autoLock(mMutex);
-
- if (mExecuting) {
- // This should never happen -- buffers can only be allocated when
- // transitioning from "loaded" to "idle".
- ALOGE("addCodecBuffer: buffer added while executing");
- return Status::fromServiceSpecificError(INVALID_OPERATION);
- }
-
- ALOGV("addCodecBuffer: bufferId=%u", bufferId);
-
- mFreeCodecBuffers.push_back(bufferId);
- return Status::ok();
-}
-
-Status GraphicBufferSource::onInputBufferEmptied(codec_buffer_id bufferId, int fenceFd) {
- Mutex::Autolock autoLock(mMutex);
- FileDescriptor::Autoclose fence(fenceFd);
-
- ssize_t cbi = mSubmittedCodecBuffers.indexOfKey(bufferId);
- if (cbi < 0) {
- // This should never happen.
- ALOGE("onInputBufferEmptied: buffer not recognized (bufferId=%u)", bufferId);
- return Status::fromServiceSpecificError(BAD_VALUE);
- }
-
- std::shared_ptr<AcquiredBuffer> buffer = mSubmittedCodecBuffers.valueAt(cbi);
-
- // Move buffer to available buffers
- mSubmittedCodecBuffers.removeItemsAt(cbi);
- mFreeCodecBuffers.push_back(bufferId);
-
- // header->nFilledLen may not be the original value, so we can't compare
- // that to zero to see of this was the EOS buffer. Instead we just
- // see if there is a null AcquiredBuffer, which should only ever happen for EOS.
- if (buffer == nullptr) {
- if (!(mEndOfStream && mEndOfStreamSent)) {
- // This can happen when broken code sends us the same buffer twice in a row.
- ALOGE("onInputBufferEmptied: non-EOS null buffer (bufferId=%u)", bufferId);
- } else {
- ALOGV("onInputBufferEmptied: EOS null buffer (bufferId=%u@%zd)", bufferId, cbi);
- }
- // No GraphicBuffer to deal with, no additional input or output is expected, so just return.
- return Status::fromServiceSpecificError(BAD_VALUE);
- }
-
- if (!mExecuting) {
- // this is fine since this could happen when going from Idle to Loaded
- ALOGV("onInputBufferEmptied: no longer executing (bufferId=%u@%zd)", bufferId, cbi);
- return Status::fromServiceSpecificError(OK);
- }
-
- ALOGV("onInputBufferEmptied: bufferId=%d@%zd [slot=%d, useCount=%ld, handle=%p] acquired=%d",
- bufferId, cbi, buffer->getSlot(), buffer.use_count(), buffer->getGraphicBuffer()->handle,
- mNumOutstandingAcquires);
-
- buffer->addReleaseFenceFd(fence.release());
- // release codec reference for video buffer just in case remove does not it
- buffer.reset();
-
- if (haveAvailableBuffers_l()) {
- // Fill this codec buffer.
- CHECK(!mEndOfStreamSent);
- ALOGV("onInputBufferEmptied: buffer freed, feeding codec (available=%zu+%d, eos=%d)",
- mAvailableBuffers.size(), mNumAvailableUnacquiredBuffers, mEndOfStream);
- fillCodecBuffer_l();
- } else if (mEndOfStream && mStopTimeUs == -1) {
- // No frames available, but EOS is pending and no stop time, so use this buffer to
- // send that.
- ALOGV("onInputBufferEmptied: buffer freed, submitting EOS");
- submitEndOfInputStream_l();
- } else if (mFrameRepeatBlockedOnCodecBuffer) {
- bool success = repeatLatestBuffer_l();
- ALOGV("onInputBufferEmptied: completing deferred repeatLatestBuffer_l %s",
- success ? "SUCCESS" : "FAILURE");
- mFrameRepeatBlockedOnCodecBuffer = false;
- }
-
- // releaseReleasableBuffers_l();
- return Status::ok();
-}
-
-void GraphicBufferSource::onDataspaceChanged_l(
- android_dataspace dataspace, android_pixel_format pixelFormat) {
- ALOGD("got buffer with new dataSpace #%x", dataspace);
- mLastDataspace = dataspace;
-
- if (ColorUtils::convertDataSpaceToV0(dataspace)) {
- mOMXNode->dispatchDataSpaceChanged(mLastDataspace, mDefaultColorAspectsPacked, pixelFormat);
- }
-}
-
-bool GraphicBufferSource::fillCodecBuffer_l() {
- CHECK(mExecuting && haveAvailableBuffers_l());
-
- if (mFreeCodecBuffers.empty()) {
- // No buffers available, bail.
- ALOGV("fillCodecBuffer_l: no codec buffers, available=%zu+%d",
- mAvailableBuffers.size(), mNumAvailableUnacquiredBuffers);
- return false;
- }
-
- VideoBuffer item;
- if (mAvailableBuffers.empty()) {
- ALOGV("fillCodecBuffer_l: acquiring available buffer, available=%zu+%d",
- mAvailableBuffers.size(), mNumAvailableUnacquiredBuffers);
- if (acquireBuffer_l(&item) != OK) {
- ALOGE("fillCodecBuffer_l: failed to acquire available buffer");
- return false;
- }
- } else {
- ALOGV("fillCodecBuffer_l: getting available buffer, available=%zu+%d",
- mAvailableBuffers.size(), mNumAvailableUnacquiredBuffers);
- item = *mAvailableBuffers.begin();
- mAvailableBuffers.erase(mAvailableBuffers.begin());
- }
-
- int64_t itemTimeUs = item.mTimestampNs / 1000;
-
- // Process ActionItem in the Queue if there is any. If a buffer's timestamp
- // is smaller than the first action's timestamp, no action need to be performed.
- // If buffer's timestamp is larger or equal than the last action's timestamp,
- // only the last action needs to be performed as all the acitions before the
- // the action are overridden by the last action. For the other cases, traverse
- // the Queue to find the newest action that with timestamp smaller or equal to
- // the buffer's timestamp. For example, an action queue like
- // [pause 1us], [resume 2us], [pause 3us], [resume 4us], [pause 5us].... Upon
- // receiving a buffer with timestamp 3.5us, only the action [pause, 3us] needs
- // to be handled and [pause, 1us], [resume 2us] will be discarded.
- bool done = false;
- bool seeStopAction = false;
- if (!mActionQueue.empty()) {
- // First scan to check if bufferTimestamp is smaller than first action's timestamp.
- ActionItem nextAction = *(mActionQueue.begin());
- if (itemTimeUs < nextAction.mActionTimeUs) {
- ALOGV("No action. buffer timestamp %lld us < action timestamp: %lld us",
- (long long)itemTimeUs, (long long)nextAction.mActionTimeUs);
- // All the actions are ahead. No action need to perform now.
- // Release the buffer if is in suspended state, or process the buffer
- // if not in suspended state.
- done = true;
- }
-
- if (!done) {
- // Find the newest action that with timestamp smaller than itemTimeUs. Then
- // remove all the actions before and include the newest action.
- List<ActionItem>::iterator it = mActionQueue.begin();
- while (it != mActionQueue.end() && it->mActionTimeUs <= itemTimeUs
- && nextAction.mAction != ActionItem::STOP) {
- nextAction = *it;
- ++it;
- }
- mActionQueue.erase(mActionQueue.begin(), it);
-
- CHECK(itemTimeUs >= nextAction.mActionTimeUs);
- switch (nextAction.mAction) {
- case ActionItem::PAUSE:
- {
- mSuspended = true;
- ALOGV("RUNNING/PAUSE -> PAUSE at buffer %lld us PAUSE Time: %lld us",
- (long long)itemTimeUs, (long long)nextAction.mActionTimeUs);
- break;
- }
- case ActionItem::RESUME:
- {
- mSuspended = false;
- ALOGV("PAUSE/RUNNING -> RUNNING at buffer %lld us RESUME Time: %lld us",
- (long long)itemTimeUs, (long long)nextAction.mActionTimeUs);
- break;
- }
- case ActionItem::STOP:
- {
- ALOGV("RUNNING/PAUSE -> STOP at buffer %lld us STOP Time: %lld us",
- (long long)itemTimeUs, (long long)nextAction.mActionTimeUs);
- // Clear the whole ActionQueue as recording is done
- mActionQueue.clear();
- seeStopAction = true;
- break;
- }
- default:
- TRESPASS_DBG("Unknown action type");
- // return true here because we did consume an available buffer, so the
- // loop in onOmxExecuting will eventually terminate even if we hit this.
- return false;
- }
- }
- }
-
- if (seeStopAction) {
- // Clear all the buffers before setting mEndOfStream and signal EndOfInputStream.
- releaseAllAvailableBuffers_l();
- mEndOfStream = true;
- submitEndOfInputStream_l();
- return true;
- }
-
- if (mSuspended) {
- return true;
- }
-
- int err = UNKNOWN_ERROR;
-
- // only submit sample if start time is unspecified, or sample
- // is queued after the specified start time
- if (mSkipFramesBeforeNs < 0ll || item.mTimestampNs >= mSkipFramesBeforeNs) {
- // if start time is set, offset time stamp by start time
- if (mSkipFramesBeforeNs > 0) {
- item.mTimestampNs -= mSkipFramesBeforeNs;
- }
-
- int64_t timeUs = item.mTimestampNs / 1000;
- if (mFrameDropper != NULL && mFrameDropper->shouldDrop(timeUs)) {
- ALOGV("skipping frame (%lld) to meet max framerate", static_cast<long long>(timeUs));
- // set err to OK so that the skipped frame can still be saved as the lastest frame
- err = OK;
- } else {
- err = submitBuffer_l(item); // this takes shared ownership of the acquired buffer on succeess
- }
- }
-
- if (err != OK) {
- ALOGV("submitBuffer_l failed, will release bq slot %d", item.mBuffer->getSlot());
- return true;
- } else {
- // Don't set the last buffer id if we're not repeating,
- // we'll be holding on to the last buffer for nothing.
- if (mFrameRepeatIntervalUs > 0ll) {
- setLatestBuffer_l(item);
- }
- ALOGV("buffer submitted [slot=%d, useCount=%ld] acquired=%d",
- item.mBuffer->getSlot(), item.mBuffer.use_count(), mNumOutstandingAcquires);
- mLastFrameTimestampUs = itemTimeUs;
- }
-
- return true;
-}
-
-bool GraphicBufferSource::repeatLatestBuffer_l() {
- CHECK(mExecuting && !haveAvailableBuffers_l());
-
- if (mLatestBuffer.mBuffer == nullptr || mSuspended) {
- return false;
- }
-
- if (mFreeCodecBuffers.empty()) {
- // No buffers available, bail.
- ALOGV("repeatLatestBuffer_l: no codec buffers.");
- return false;
- }
-
- if (!mLatestBuffer.mBuffer->isCached()) {
- ALOGV("repeatLatestBuffer_l: slot was discarded, but repeating our own reference");
- }
-
- // it is ok to update the timestamp of latest buffer as it is only used for submission
- status_t err = submitBuffer_l(mLatestBuffer);
- if (err != OK) {
- return false;
- }
-
- /* repeat last frame up to kRepeatLastFrameCount times.
- * in case of static scene, a single repeat might not get rid of encoder
- * ghosting completely, refresh a couple more times to get better quality
- */
- if (--mOutstandingFrameRepeatCount > 0) {
- // set up timestamp for repeat frame
- mLatestBuffer.mTimestampNs += mFrameRepeatIntervalUs * 1000;
- queueFrameRepeat_l();
- }
-
- return true;
-}
-
-void GraphicBufferSource::setLatestBuffer_l(const VideoBuffer &item) {
- mLatestBuffer = item;
-
- ALOGV("setLatestBuffer_l: [slot=%d, useCount=%ld]",
- mLatestBuffer.mBuffer->getSlot(), mLatestBuffer.mBuffer.use_count());
-
- mOutstandingFrameRepeatCount = kRepeatLastFrameCount;
- // set up timestamp for repeat frame
- mLatestBuffer.mTimestampNs += mFrameRepeatIntervalUs * 1000;
- queueFrameRepeat_l();
-}
-
-void GraphicBufferSource::queueFrameRepeat_l() {
- mFrameRepeatBlockedOnCodecBuffer = false;
-
- if (mReflector != NULL) {
- sp<AMessage> msg = new AMessage(kWhatRepeatLastFrame, mReflector);
- msg->setInt32("generation", ++mRepeatLastFrameGeneration);
- msg->post(mFrameRepeatIntervalUs);
- }
-}
-
-bool GraphicBufferSource::calculateCodecTimestamp_l(
- nsecs_t bufferTimeNs, int64_t *codecTimeUs) {
- int64_t timeUs = bufferTimeNs / 1000;
- timeUs += mInputBufferTimeOffsetUs;
-
- if (mCaptureFps > 0.
- && (mFps > 2 * mCaptureFps
- || mCaptureFps > 2 * mFps)) {
- // Time lapse or slow motion mode
- if (mPrevCaptureUs < 0ll) {
- // first capture
- mPrevCaptureUs = mBaseCaptureUs = timeUs;
- // adjust the first sample timestamp.
- mPrevFrameUs = mBaseFrameUs =
- std::llround((timeUs * mCaptureFps) / mFps);
- mFrameCount = 0;
- } else {
- // snap to nearest capture point
- double nFrames = (timeUs - mPrevCaptureUs) * mCaptureFps / 1000000;
- if (nFrames < 0.5 - kTimestampFluctuation) {
- // skip this frame as it's too close to previous capture
- ALOGV("skipping frame, timeUs %lld", static_cast<long long>(timeUs));
- return false;
- }
- if (nFrames <= 1.0) {
- nFrames = 1.0;
- }
- mFrameCount += std::llround(nFrames);
- mPrevCaptureUs = mBaseCaptureUs + std::llround(
- mFrameCount * 1000000 / mCaptureFps);
- mPrevFrameUs = mBaseFrameUs + std::llround(
- mFrameCount * 1000000 / mFps);
- }
-
- ALOGV("timeUs %lld, captureUs %lld, frameUs %lld",
- static_cast<long long>(timeUs),
- static_cast<long long>(mPrevCaptureUs),
- static_cast<long long>(mPrevFrameUs));
- } else {
- if (timeUs <= mPrevFrameUs) {
- // Drop the frame if it's going backward in time. Bad timestamp
- // could disrupt encoder's rate control completely.
- ALOGW("Dropping frame that's going backward in time");
- return false;
- }
-
- mPrevFrameUs = timeUs;
- }
-
- *codecTimeUs = mPrevFrameUs;
- return true;
-}
-
-status_t GraphicBufferSource::submitBuffer_l(const VideoBuffer &item) {
- CHECK(!mFreeCodecBuffers.empty());
- IOMX::buffer_id codecBufferId = *mFreeCodecBuffers.begin();
-
- ALOGV("submitBuffer_l [slot=%d, bufferId=%d]", item.mBuffer->getSlot(), codecBufferId);
-
- int64_t codecTimeUs;
- if (!calculateCodecTimestamp_l(item.mTimestampNs, &codecTimeUs)) {
- return UNKNOWN_ERROR;
- }
-
- if ((android_dataspace)item.mDataspace != mLastDataspace) {
- onDataspaceChanged_l(
- item.mDataspace,
- (android_pixel_format)item.mBuffer->getGraphicBuffer()->format);
- }
-
- std::shared_ptr<AcquiredBuffer> buffer = item.mBuffer;
- // use a GraphicBuffer for now as OMXNodeInstance is using GraphicBuffers to hold references
- // and it requires this graphic buffer to be able to hold its reference
- // and thus we would need to create a new GraphicBuffer from an ANWBuffer separate from the
- // acquired GraphicBuffer.
- // TODO: this can be reworked globally to use ANWBuffer references
- sp<GraphicBuffer> graphicBuffer = buffer->getGraphicBuffer();
- status_t err = mOMXNode->emptyBuffer(
- codecBufferId, OMX_BUFFERFLAG_ENDOFFRAME, graphicBuffer, codecTimeUs,
- buffer->getAcquireFenceFd());
-
- if (err != OK) {
- ALOGW("WARNING: emptyGraphicBuffer failed: 0x%x", err);
- return err;
- }
-
- mFreeCodecBuffers.erase(mFreeCodecBuffers.begin());
-
- ssize_t cbix = mSubmittedCodecBuffers.add(codecBufferId, buffer);
- ALOGV("emptyGraphicBuffer succeeded, bufferId=%u@%zd bufhandle=%p",
- codecBufferId, cbix, graphicBuffer->handle);
- return OK;
-}
-
-void GraphicBufferSource::submitEndOfInputStream_l() {
- CHECK(mEndOfStream);
- if (mEndOfStreamSent) {
- ALOGV("EOS already sent");
- return;
- }
-
- if (mFreeCodecBuffers.empty()) {
- ALOGV("submitEndOfInputStream_l: no codec buffers available");
- return;
- }
- IOMX::buffer_id codecBufferId = *mFreeCodecBuffers.begin();
-
- // We reject any additional incoming graphic buffers. There is no acquired buffer used for EOS
- status_t err = mOMXNode->emptyBuffer(
- codecBufferId, OMX_BUFFERFLAG_ENDOFFRAME | OMX_BUFFERFLAG_EOS);
- if (err != OK) {
- ALOGW("emptyDirectBuffer EOS failed: 0x%x", err);
- } else {
- mFreeCodecBuffers.erase(mFreeCodecBuffers.begin());
- ssize_t cbix = mSubmittedCodecBuffers.add(codecBufferId, nullptr);
- ALOGV("submitEndOfInputStream_l: buffer submitted, bufferId=%u@%zd", codecBufferId, cbix);
- mEndOfStreamSent = true;
-
- // no need to hold onto any buffers for frame repeating
- ++mRepeatLastFrameGeneration;
- mLatestBuffer.mBuffer.reset();
- }
-}
-
-status_t GraphicBufferSource::acquireBuffer_l(VideoBuffer *ab) {
- BufferItem bi;
- status_t err = mConsumer->acquireBuffer(&bi, 0);
- if (err == BufferQueue::NO_BUFFER_AVAILABLE) {
- // shouldn't happen
- ALOGW("acquireBuffer_l: frame was not available");
- return err;
- } else if (err != OK) {
- ALOGW("acquireBuffer_l: failed with err=%d", err);
- return err;
- }
- --mNumAvailableUnacquiredBuffers;
-
- // Manage our buffer cache.
- std::shared_ptr<CachedBuffer> buffer;
- ssize_t bsi = mBufferSlots.indexOfKey(bi.mSlot);
- if (bi.mGraphicBuffer != NULL) {
- // replace/initialize slot with new buffer
- ALOGV("acquireBuffer_l: %s buffer slot %d", bsi < 0 ? "setting" : "UPDATING", bi.mSlot);
- if (bsi >= 0) {
- discardBufferAtSlotIndex_l(bsi);
- } else {
- bsi = mBufferSlots.add(bi.mSlot, nullptr);
- }
- buffer = std::make_shared<CachedBuffer>(bi.mSlot, bi.mGraphicBuffer);
- mBufferSlots.replaceValueAt(bsi, buffer);
- } else {
- buffer = mBufferSlots.valueAt(bsi);
- }
- int64_t frameNum = bi.mFrameNumber;
-
- std::shared_ptr<AcquiredBuffer> acquiredBuffer =
- std::make_shared<AcquiredBuffer>(
- buffer,
- [frameNum, this](AcquiredBuffer *buffer){
- // AcquiredBuffer's destructor should always be called when mMutex is locked.
- // If we had a reentrant mutex, we could just lock it again to ensure this.
- if (mMutex.tryLock() == 0) {
- TRESPASS_DBG();
- mMutex.unlock();
- }
-
- // we can release buffers immediately if not using adapters
- // alternately, we could add them to mSlotsToRelease, but we would
- // somehow need to propagate frame number to that queue
- if (buffer->isCached()) {
- --mNumOutstandingAcquires;
- mConsumer->releaseBuffer(
- buffer->getSlot(), frameNum, EGL_NO_DISPLAY, EGL_NO_SYNC_KHR,
- buffer->getReleaseFence());
- }
- },
- bi.mFence);
- VideoBuffer videoBuffer{acquiredBuffer, bi.mTimestamp, bi.mDataSpace};
- *ab = videoBuffer;
- ++mNumOutstandingAcquires;
- return OK;
-}
-
-// BufferQueue::ConsumerListener callback
-void GraphicBufferSource::onFrameAvailable(const BufferItem& item __unused) {
- Mutex::Autolock autoLock(mMutex);
-
- ALOGV("onFrameAvailable: executing=%d available=%zu+%d",
- mExecuting, mAvailableBuffers.size(), mNumAvailableUnacquiredBuffers);
- ++mNumAvailableUnacquiredBuffers;
-
- // For BufferQueue we cannot acquire a buffer if we cannot immediately feed it to the codec
- // UNLESS we are discarding this buffer (acquiring and immediately releasing it), which makes
- // this an ugly logic.
- // NOTE: We could also rely on our debug counter but that is meant only as a debug counter.
- if (!areWeDiscardingAvailableBuffers_l() && mFreeCodecBuffers.empty()) {
- // we may not be allowed to acquire a possibly encodable buffer, so just note that
- // it is available
- ALOGV("onFrameAvailable: cannot acquire buffer right now, do it later");
-
- ++mRepeatLastFrameGeneration; // cancel any pending frame repeat
- return;
- }
-
- VideoBuffer buffer;
- status_t err = acquireBuffer_l(&buffer);
- if (err != OK) {
- ALOGE("onFrameAvailable: acquireBuffer returned err=%d", err);
- } else {
- onBufferAcquired_l(buffer);
- }
-}
-
-bool GraphicBufferSource::areWeDiscardingAvailableBuffers_l() {
- return mEndOfStreamSent // already sent EOS to codec
- || mOMXNode == nullptr // there is no codec connected
- || (mSuspended && mActionQueue.empty()) // we are suspended and not waiting for
- // any further action
- || !mExecuting;
-}
-
-void GraphicBufferSource::onBufferAcquired_l(const VideoBuffer &buffer) {
- if (mEndOfStreamSent) {
- // This should only be possible if a new buffer was queued after
- // EOS was signaled, i.e. the app is misbehaving.
- ALOGW("onFrameAvailable: EOS is sent, ignoring frame");
- } else if (mOMXNode == NULL || (mSuspended && mActionQueue.empty())) {
- // FIXME: if we are suspended but have a resume queued we will stop repeating the last
- // frame. Is that the desired behavior?
- ALOGV("onFrameAvailable: suspended, ignoring frame");
- } else {
- ++mRepeatLastFrameGeneration; // cancel any pending frame repeat
- mAvailableBuffers.push_back(buffer);
- if (mExecuting) {
- fillCodecBuffer_l();
- }
- }
-}
-
-// BufferQueue::ConsumerListener callback
-void GraphicBufferSource::onBuffersReleased() {
- Mutex::Autolock lock(mMutex);
-
- uint64_t slotMask;
- uint64_t releaseMask;
- if (mConsumer->getReleasedBuffers(&releaseMask) != NO_ERROR) {
- slotMask = 0xffffffffffffffffULL;
- ALOGW("onBuffersReleased: unable to get released buffer set");
- } else {
- slotMask = releaseMask;
- ALOGV("onBuffersReleased: 0x%016" PRIx64, slotMask);
- }
-
- AString unpopulated;
- for (int i = 0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) {
- if ((slotMask & 0x01) != 0) {
- if (!discardBufferInSlot_l(i)) {
- if (!unpopulated.empty()) {
- unpopulated.append(", ");
- }
- unpopulated.append(i);
- }
- }
- slotMask >>= 1;
- }
- if (!unpopulated.empty()) {
- ALOGW("released unpopulated slots: [%s]", unpopulated.c_str());
- }
-}
-
-bool GraphicBufferSource::discardBufferInSlot_l(GraphicBufferSource::slot_id i) {
- ssize_t bsi = mBufferSlots.indexOfKey(i);
- if (bsi < 0) {
- return false;
- } else {
- discardBufferAtSlotIndex_l(bsi);
- mBufferSlots.removeItemsAt(bsi);
- return true;
- }
-}
-
-void GraphicBufferSource::discardBufferAtSlotIndex_l(ssize_t bsi) {
- const std::shared_ptr<CachedBuffer>& buffer = mBufferSlots.valueAt(bsi);
- // use -2 if there is no latest buffer, and -1 if it is no longer cached
- slot_id latestBufferSlot =
- mLatestBuffer.mBuffer == nullptr ? -2 : mLatestBuffer.mBuffer->getSlot();
- ALOGV("releasing acquired buffer: [slot=%d, useCount=%ld], latest: [slot=%d]",
- mBufferSlots.keyAt(bsi), buffer.use_count(), latestBufferSlot);
- mBufferSlots.valueAt(bsi)->onDroppedFromCache();
-
- // If the slot of an acquired buffer is discarded, that buffer will not have to be
- // released to the producer, so account it here. However, it is possible that the
- // acquired buffer has already been discarded so check if it still is.
- if (buffer->isAcquired()) {
- --mNumOutstandingAcquires;
- }
-
- // clear the buffer reference (not technically needed as caller either replaces or deletes
- // it; done here for safety).
- mBufferSlots.editValueAt(bsi).reset();
- CHECK_DBG(buffer == nullptr);
-}
-
-void GraphicBufferSource::releaseAllAvailableBuffers_l() {
- mAvailableBuffers.clear();
- while (mNumAvailableUnacquiredBuffers > 0) {
- VideoBuffer item;
- if (acquireBuffer_l(&item) != OK) {
- ALOGW("releaseAllAvailableBuffers: failed to acquire available unacquired buffer");
- break;
- }
- }
-}
-
-// BufferQueue::ConsumerListener callback
-void GraphicBufferSource::onSidebandStreamChanged() {
- ALOG_ASSERT(false, "GraphicBufferSource can't consume sideband streams");
-}
-
-status_t GraphicBufferSource::configure(
- const sp<IOmxNodeWrapper>& omxNode,
- int32_t dataSpace,
- int32_t bufferCount,
- uint32_t frameWidth,
- uint32_t frameHeight,
- uint32_t consumerUsage) {
- if (omxNode == NULL) {
- return BAD_VALUE;
- }
-
-
- // Call setMaxAcquiredBufferCount without lock.
- // setMaxAcquiredBufferCount could call back to onBuffersReleased
- // if the buffer count change results in releasing of existing buffers,
- // which would lead to deadlock.
- status_t err = mConsumer->setMaxAcquiredBufferCount(bufferCount);
- if (err != NO_ERROR) {
- ALOGE("Unable to set BQ max acquired buffer count to %u: %d",
- bufferCount, err);
- return err;
- }
-
- {
- Mutex::Autolock autoLock(mMutex);
- mOMXNode = omxNode;
-
- err = mConsumer->setDefaultBufferSize(frameWidth, frameHeight);
- if (err != NO_ERROR) {
- ALOGE("Unable to set BQ default buffer size to %ux%u: %d",
- frameWidth, frameHeight, err);
- return err;
- }
-
- consumerUsage |= GRALLOC_USAGE_HW_VIDEO_ENCODER;
- mConsumer->setConsumerUsageBits(consumerUsage);
-
- // Sets the default buffer data space
- ALOGD("setting dataspace: %#x, acquired=%d", dataSpace, mNumOutstandingAcquires);
- mConsumer->setDefaultBufferDataSpace((android_dataspace)dataSpace);
- mLastDataspace = (android_dataspace)dataSpace;
-
- mExecuting = false;
- mSuspended = false;
- mEndOfStream = false;
- mEndOfStreamSent = false;
- mSkipFramesBeforeNs = -1ll;
- mFrameRepeatIntervalUs = -1ll;
- mRepeatLastFrameGeneration = 0;
- mOutstandingFrameRepeatCount = 0;
- mLatestBuffer.mBuffer.reset();
- mFrameRepeatBlockedOnCodecBuffer = false;
- mFps = -1.0;
- mCaptureFps = -1.0;
- mBaseCaptureUs = -1ll;
- mBaseFrameUs = -1ll;
- mPrevCaptureUs = -1ll;
- mPrevFrameUs = -1ll;
- mFrameCount = 0;
- mInputBufferTimeOffsetUs = 0;
- mStopTimeUs = -1;
- mActionQueue.clear();
- }
-
- return OK;
-}
-
-status_t GraphicBufferSource::setSuspend(bool suspend, int64_t suspendStartTimeUs) {
- ALOGV("setSuspend=%d at time %lld us", suspend, (long long)suspendStartTimeUs);
-
- Mutex::Autolock autoLock(mMutex);
-
- if (mStopTimeUs != -1) {
- ALOGE("setSuspend failed as STOP action is pending");
- return INVALID_OPERATION;
- }
-
- // Push the action to the queue.
- if (suspendStartTimeUs != -1) {
- // suspendStartTimeUs must be smaller or equal to current systemTime.
- int64_t currentSystemTimeUs = systemTime() / 1000;
- if (suspendStartTimeUs > currentSystemTimeUs) {
- ALOGE("setSuspend failed. %lld is larger than current system time %lld us",
- (long long)suspendStartTimeUs, (long long)currentSystemTimeUs);
- return INVALID_OPERATION;
- }
- if (mLastActionTimeUs != -1 && suspendStartTimeUs < mLastActionTimeUs) {
- ALOGE("setSuspend failed. %lld is smaller than last action time %lld us",
- (long long)suspendStartTimeUs, (long long)mLastActionTimeUs);
- return INVALID_OPERATION;
- }
- mLastActionTimeUs = suspendStartTimeUs;
- ActionItem action;
- action.mAction = suspend ? ActionItem::PAUSE : ActionItem::RESUME;
- action.mActionTimeUs = suspendStartTimeUs;
- ALOGV("Push %s action into actionQueue", suspend ? "PAUSE" : "RESUME");
- mActionQueue.push_back(action);
- } else {
- if (suspend) {
- mSuspended = true;
- releaseAllAvailableBuffers_l();
- return OK;
- } else {
- mSuspended = false;
- if (mExecuting && !haveAvailableBuffers_l()
- && mFrameRepeatBlockedOnCodecBuffer) {
- if (repeatLatestBuffer_l()) {
- ALOGV("suspend/deferred repeatLatestBuffer_l SUCCESS");
- mFrameRepeatBlockedOnCodecBuffer = false;
- } else {
- ALOGV("suspend/deferred repeatLatestBuffer_l FAILURE");
- }
- }
- }
- }
- return OK;
-}
-
-status_t GraphicBufferSource::setRepeatPreviousFrameDelayUs(int64_t repeatAfterUs) {
- ALOGV("setRepeatPreviousFrameDelayUs: delayUs=%lld", (long long)repeatAfterUs);
-
- Mutex::Autolock autoLock(mMutex);
-
- if (mExecuting || repeatAfterUs <= 0ll) {
- return INVALID_OPERATION;
- }
-
- mFrameRepeatIntervalUs = repeatAfterUs;
- return OK;
-}
-
-status_t GraphicBufferSource::setTimeOffsetUs(int64_t timeOffsetUs) {
- Mutex::Autolock autoLock(mMutex);
-
- // timeOffsetUs must be negative for adjustment.
- if (timeOffsetUs >= 0ll) {
- return INVALID_OPERATION;
- }
-
- mInputBufferTimeOffsetUs = timeOffsetUs;
- return OK;
-}
-
-status_t GraphicBufferSource::setMaxFps(float maxFps) {
- ALOGV("setMaxFps: maxFps=%lld", (long long)maxFps);
-
- Mutex::Autolock autoLock(mMutex);
-
- if (mExecuting) {
- return INVALID_OPERATION;
- }
-
- mFrameDropper = new FrameDropper();
- status_t err = mFrameDropper->setMaxFrameRate(maxFps);
- if (err != OK) {
- mFrameDropper.clear();
- return err;
- }
-
- return OK;
-}
-
-status_t GraphicBufferSource::setStartTimeUs(int64_t skipFramesBeforeUs) {
- ALOGV("setStartTimeUs: skipFramesBeforeUs=%lld", (long long)skipFramesBeforeUs);
-
- Mutex::Autolock autoLock(mMutex);
-
- mSkipFramesBeforeNs =
- (skipFramesBeforeUs > 0 && skipFramesBeforeUs <= INT64_MAX / 1000) ?
- (skipFramesBeforeUs * 1000) : -1ll;
-
- return OK;
-}
-
-status_t GraphicBufferSource::setStopTimeUs(int64_t stopTimeUs) {
- ALOGV("setStopTimeUs: %lld us", (long long)stopTimeUs);
- Mutex::Autolock autoLock(mMutex);
-
- if (mStopTimeUs != -1) {
- // Ignore if stop time has already been set
- return OK;
- }
-
- // stopTimeUs must be smaller or equal to current systemTime.
- int64_t currentSystemTimeUs = systemTime() / 1000;
- if (stopTimeUs > currentSystemTimeUs) {
- ALOGE("setStopTimeUs failed. %lld is larger than current system time %lld us",
- (long long)stopTimeUs, (long long)currentSystemTimeUs);
- return INVALID_OPERATION;
- }
- if (mLastActionTimeUs != -1 && stopTimeUs < mLastActionTimeUs) {
- ALOGE("setSuspend failed. %lld is smaller than last action time %lld us",
- (long long)stopTimeUs, (long long)mLastActionTimeUs);
- return INVALID_OPERATION;
- }
- mLastActionTimeUs = stopTimeUs;
- ActionItem action;
- action.mAction = ActionItem::STOP;
- action.mActionTimeUs = stopTimeUs;
- mActionQueue.push_back(action);
- mStopTimeUs = stopTimeUs;
- return OK;
-}
-
-status_t GraphicBufferSource::getStopTimeOffsetUs(int64_t *stopTimeOffsetUs) {
- ALOGV("getStopTimeOffsetUs");
- Mutex::Autolock autoLock(mMutex);
- if (mStopTimeUs == -1) {
- ALOGW("Fail to return stopTimeOffsetUs as stop time is not set");
- return INVALID_OPERATION;
- }
- *stopTimeOffsetUs =
- mLastFrameTimestampUs == -1 ? 0 : mStopTimeUs - mLastFrameTimestampUs;
- return OK;
-}
-
-status_t GraphicBufferSource::setTimeLapseConfig(double fps, double captureFps) {
- ALOGV("setTimeLapseConfig: fps=%lg, captureFps=%lg",
- fps, captureFps);
- Mutex::Autolock autoLock(mMutex);
-
- if (mExecuting || !(fps > 0) || !(captureFps > 0)) {
- return INVALID_OPERATION;
- }
-
- mFps = fps;
- mCaptureFps = captureFps;
-
- return OK;
-}
-
-status_t GraphicBufferSource::setColorAspects(int32_t aspectsPacked) {
- Mutex::Autolock autoLock(mMutex);
- mDefaultColorAspectsPacked = aspectsPacked;
- ColorAspects colorAspects = ColorUtils::unpackToColorAspects(aspectsPacked);
- ALOGD("requesting color aspects (R:%d(%s), P:%d(%s), M:%d(%s), T:%d(%s))",
- colorAspects.mRange, asString(colorAspects.mRange),
- colorAspects.mPrimaries, asString(colorAspects.mPrimaries),
- colorAspects.mMatrixCoeffs, asString(colorAspects.mMatrixCoeffs),
- colorAspects.mTransfer, asString(colorAspects.mTransfer));
-
- return OK;
-}
-
-status_t GraphicBufferSource::signalEndOfInputStream() {
- Mutex::Autolock autoLock(mMutex);
- ALOGV("signalEndOfInputStream: executing=%d available=%zu+%d eos=%d",
- mExecuting, mAvailableBuffers.size(), mNumAvailableUnacquiredBuffers, mEndOfStream);
-
- if (mEndOfStream) {
- ALOGE("EOS was already signaled");
- return INVALID_OPERATION;
- }
-
- // Set the end-of-stream flag. If no frames are pending from the
- // BufferQueue, and a codec buffer is available, and we're executing,
- // and there is no stop timestamp, we initiate the EOS from here.
- // Otherwise, we'll let codecBufferEmptied() (or omxExecuting) do it.
- //
- // Note: if there are no pending frames and all codec buffers are
- // available, we *must* submit the EOS from here or we'll just
- // stall since no future events are expected.
- mEndOfStream = true;
-
- if (mStopTimeUs == -1 && mExecuting && !haveAvailableBuffers_l()) {
- submitEndOfInputStream_l();
- }
-
- return OK;
-}
-
-void GraphicBufferSource::onMessageReceived(const sp<AMessage> &msg) {
- switch (msg->what()) {
- case kWhatRepeatLastFrame:
- {
- Mutex::Autolock autoLock(mMutex);
-
- int32_t generation;
- CHECK(msg->findInt32("generation", &generation));
-
- if (generation != mRepeatLastFrameGeneration) {
- // stale
- break;
- }
-
- if (!mExecuting || haveAvailableBuffers_l()) {
- break;
- }
-
- bool success = repeatLatestBuffer_l();
- if (success) {
- ALOGV("repeatLatestBuffer_l SUCCESS");
- } else {
- ALOGV("repeatLatestBuffer_l FAILURE");
- mFrameRepeatBlockedOnCodecBuffer = true;
- }
- break;
- }
-
- default:
- TRESPASS();
- }
-}
-
-} // namespace android
diff --git a/media/libstagefright/omx/OmxGraphicBufferSource.cpp b/media/libstagefright/omx/OmxGraphicBufferSource.cpp
new file mode 100644
index 0000000..83feac8
--- /dev/null
+++ b/media/libstagefright/omx/OmxGraphicBufferSource.cpp
@@ -0,0 +1,89 @@
+/*
+ * 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.
+ */
+
+#include <inttypes.h>
+
+#define LOG_TAG "OmxGraphicBufferSource"
+//#define LOG_NDEBUG 0
+#include <utils/Log.h>
+
+#include <media/stagefright/gbs/ComponentWrapper.h>
+#include <media/stagefright/gbs/GraphicBufferSource.h>
+#include <media/stagefright/omx/OmxGraphicBufferSource.h>
+
+namespace android {
+
+namespace {
+
+class OmxComponentWrapper : public ComponentWrapper {
+public:
+ explicit OmxComponentWrapper(const sp<IOmxNodeWrapper> &node)
+ : mOmxNode(node) {}
+ virtual ~OmxComponentWrapper() = default;
+
+ status_t submitBuffer(
+ int32_t bufferId, const sp<GraphicBuffer> &buffer,
+ int64_t timestamp, int fenceFd) override {
+ return mOmxNode->emptyBuffer(
+ bufferId, OMX_BUFFERFLAG_ENDOFFRAME, buffer, timestamp, fenceFd);
+ }
+
+ status_t submitEos(int32_t bufferId) override {
+ return mOmxNode->emptyBuffer(bufferId, OMX_BUFFERFLAG_ENDOFFRAME | OMX_BUFFERFLAG_EOS);
+ }
+
+ void dispatchDataSpaceChanged(
+ int32_t dataSpace, int32_t aspects, int32_t pixelFormat) override {
+ mOmxNode->dispatchDataSpaceChanged(dataSpace, aspects, pixelFormat);
+ }
+
+private:
+ sp<IOmxNodeWrapper> mOmxNode;
+
+ DISALLOW_EVIL_CONSTRUCTORS(OmxComponentWrapper);
+};
+
+} // namespace
+
+Status OmxGraphicBufferSource::onOmxExecuting() {
+ return start();
+}
+
+Status OmxGraphicBufferSource::onOmxIdle() {
+ return stop();
+}
+
+Status OmxGraphicBufferSource::onOmxLoaded(){
+ return release();
+}
+
+status_t OmxGraphicBufferSource::configure(
+ const sp<IOmxNodeWrapper>& omxNode,
+ int32_t dataSpace,
+ int32_t bufferCount,
+ uint32_t frameWidth,
+ uint32_t frameHeight,
+ uint32_t consumerUsage) {
+ if (omxNode == NULL) {
+ return BAD_VALUE;
+ }
+
+ return GraphicBufferSource::configure(
+ new OmxComponentWrapper(omxNode), dataSpace, bufferCount,
+ frameWidth, frameHeight, consumerUsage);
+}
+
+} // namespace android
diff --git a/media/libstagefright/omx/include/media/stagefright/omx/1.0/WGraphicBufferSource.h b/media/libstagefright/omx/include/media/stagefright/omx/1.0/WGraphicBufferSource.h
index b9f22ab..4e56c98 100644
--- a/media/libstagefright/omx/include/media/stagefright/omx/1.0/WGraphicBufferSource.h
+++ b/media/libstagefright/omx/include/media/stagefright/omx/1.0/WGraphicBufferSource.h
@@ -28,7 +28,7 @@
#include <android/BnGraphicBufferSource.h>
-#include <media/stagefright/omx/GraphicBufferSource.h>
+#include <media/stagefright/omx/OmxGraphicBufferSource.h>
namespace android {
namespace hardware {
@@ -37,7 +37,7 @@
namespace V1_0 {
namespace implementation {
-using ::android::GraphicBufferSource;
+using ::android::OmxGraphicBufferSource;
using ::android::hardware::graphics::common::V1_0::Dataspace;
using ::android::hardware::media::omx::V1_0::ColorAspects;
using ::android::hardware::media::omx::V1_0::IGraphicBufferSource;
@@ -69,10 +69,10 @@
struct TWGraphicBufferSource : public TGraphicBufferSource {
struct TWOmxNodeWrapper;
struct TWOmxBufferSource;
- sp<GraphicBufferSource> mBase;
+ sp<OmxGraphicBufferSource> mBase;
sp<IOmxBufferSource> mOmxBufferSource;
- TWGraphicBufferSource(sp<GraphicBufferSource> const& base);
+ TWGraphicBufferSource(sp<OmxGraphicBufferSource> const& base);
Return<Status> configure(
const sp<IOmxNode>& omxNode, Dataspace dataspace) override;
Return<Status> setSuspend(bool suspend, int64_t timeUs) override;
diff --git a/media/libstagefright/omx/include/media/stagefright/omx/BWGraphicBufferSource.h b/media/libstagefright/omx/include/media/stagefright/omx/BWGraphicBufferSource.h
index 0f78eb6..0efff22 100644
--- a/media/libstagefright/omx/include/media/stagefright/omx/BWGraphicBufferSource.h
+++ b/media/libstagefright/omx/include/media/stagefright/omx/BWGraphicBufferSource.h
@@ -23,14 +23,14 @@
#include <android/BnOMXBufferSource.h>
#include <media/IOMX.h>
-#include "GraphicBufferSource.h"
+#include "OmxGraphicBufferSource.h"
#include "IOmxNodeWrapper.h"
namespace android {
using ::android::binder::Status;
using ::android::BnGraphicBufferSource;
-using ::android::GraphicBufferSource;
+using ::android::OmxGraphicBufferSource;
using ::android::IOMXNode;
using ::android::sp;
@@ -38,10 +38,10 @@
struct BWOMXBufferSource;
struct BWOmxNodeWrapper;
- sp<GraphicBufferSource> mBase;
+ sp<OmxGraphicBufferSource> mBase;
sp<IOMXBufferSource> mOMXBufferSource;
- BWGraphicBufferSource(sp<GraphicBufferSource> const &base);
+ BWGraphicBufferSource(sp<OmxGraphicBufferSource> const &base);
Status configure(
const sp<IOMXNode>& omxNode, int32_t dataSpace) override;
diff --git a/media/libstagefright/omx/include/media/stagefright/omx/GraphicBufferSource.h b/media/libstagefright/omx/include/media/stagefright/omx/GraphicBufferSource.h
deleted file mode 100644
index 84fee6f..0000000
--- a/media/libstagefright/omx/include/media/stagefright/omx/GraphicBufferSource.h
+++ /dev/null
@@ -1,485 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef GRAPHIC_BUFFER_SOURCE_H_
-
-#define GRAPHIC_BUFFER_SOURCE_H_
-
-#include <gui/IGraphicBufferProducer.h>
-#include <gui/BufferQueue.h>
-#include <utils/RefBase.h>
-
-#include <media/hardware/VideoAPI.h>
-#include <media/IOMX.h>
-#include <media/OMXFenceParcelable.h>
-#include <media/stagefright/foundation/ABase.h>
-#include <media/stagefright/foundation/AHandlerReflector.h>
-#include <media/stagefright/foundation/ALooper.h>
-
-#include <android/BnGraphicBufferSource.h>
-#include <android/BnOMXBufferSource.h>
-
-#include "IOmxNodeWrapper.h"
-
-namespace android {
-
-using ::android::binder::Status;
-
-struct FrameDropper;
-
-/*
- * This class is used to feed OMX codecs from a Surface via BufferQueue or
- * HW producer.
- *
- * Instances of the class don't run on a dedicated thread. Instead,
- * various events trigger data movement:
- *
- * - Availability of a new frame of data from the BufferQueue (notified
- * via the onFrameAvailable callback).
- * - The return of a codec buffer (via OnEmptyBufferDone).
- * - Application signaling end-of-stream.
- * - Transition to or from "executing" state.
- *
- * Frames of data (and, perhaps, the end-of-stream indication) can arrive
- * before the codec is in the "executing" state, so we need to queue
- * things up until we're ready to go.
- *
- * The GraphicBufferSource can be configure dynamically to discard frames
- * from the source:
- *
- * - if their timestamp is less than a start time
- * - if the source is suspended or stopped and the suspend/stop-time is reached
- * - if EOS was signaled
- * - if there is no encoder connected to it
- *
- * The source, furthermore, may choose to not encode (drop) frames if:
- *
- * - to throttle the frame rate (keep it under a certain limit)
- *
- * Finally the source may optionally hold onto the last non-discarded frame
- * (even if it was dropped) to reencode it after an interval if no further
- * frames are sent by the producer.
- */
-class GraphicBufferSource : public BufferQueue::ConsumerListener {
-public:
- GraphicBufferSource();
-
- virtual ~GraphicBufferSource();
-
- // We can't throw an exception if the constructor fails, so we just set
- // this and require that the caller test the value.
- status_t initCheck() const {
- return mInitCheck;
- }
-
- // Returns the handle to the producer side of the BufferQueue. Buffers
- // queued on this will be received by GraphicBufferSource.
- sp<IGraphicBufferProducer> getIGraphicBufferProducer() const {
- return mProducer;
- }
-
- // OmxBufferSource interface
- // ------------------------------
-
- // This is called when OMX transitions to OMX_StateExecuting, which means
- // we can start handing it buffers. If we already have buffers of data
- // sitting in the BufferQueue, this will send them to the codec.
- Status onOmxExecuting();
-
- // This is called when OMX transitions to OMX_StateIdle, indicating that
- // the codec is meant to return all buffers back to the client for them
- // to be freed. Do NOT submit any more buffers to the component.
- Status onOmxIdle();
-
- // This is called when OMX transitions to OMX_StateLoaded, indicating that
- // we are shutting down.
- Status onOmxLoaded();
-
- // A "codec buffer", i.e. a buffer that can be used to pass data into
- // the encoder, has been allocated. (This call does not call back into
- // OMXNodeInstance.)
- Status onInputBufferAdded(int32_t bufferId);
-
- // Called from OnEmptyBufferDone. If we have a BQ buffer available,
- // fill it with a new frame of data; otherwise, just mark it as available.
- Status onInputBufferEmptied(int32_t bufferId, int fenceFd);
-
- // IGraphicBufferSource interface
- // ------------------------------
-
- // Configure the buffer source to be used with an OMX node with the default
- // data space.
- status_t configure(
- const sp<IOmxNodeWrapper> &omxNode,
- int32_t dataSpace,
- int32_t bufferCount,
- uint32_t frameWidth,
- uint32_t frameHeight,
- uint32_t consumerUsage);
-
- // This is called after the last input frame has been submitted or buffer
- // timestamp is greater or equal than stopTimeUs. We need to submit an empty
- // buffer with the EOS flag set. If we don't have a codec buffer ready,
- // we just set the mEndOfStream flag.
- status_t signalEndOfInputStream();
-
- // If suspend is true, all incoming buffers (including those currently
- // in the BufferQueue) with timestamp larger than timeUs will be discarded
- // until the suspension is lifted. If suspend is false, all incoming buffers
- // including those currently in the BufferQueue) with timestamp larger than
- // timeUs will be processed. timeUs uses SYSTEM_TIME_MONOTONIC time base.
- status_t setSuspend(bool suspend, int64_t timeUs);
-
- // Specifies the interval after which we requeue the buffer previously
- // queued to the encoder. This is useful in the case of surface flinger
- // providing the input surface if the resulting encoded stream is to
- // be displayed "live". If we were not to push through the extra frame
- // the decoder on the remote end would be unable to decode the latest frame.
- // This API must be called before transitioning the encoder to "executing"
- // state and once this behaviour is specified it cannot be reset.
- status_t setRepeatPreviousFrameDelayUs(int64_t repeatAfterUs);
-
- // Sets the input buffer timestamp offset.
- // When set, the sample's timestamp will be adjusted with the timeOffsetUs.
- status_t setTimeOffsetUs(int64_t timeOffsetUs);
-
- // When set, the max frame rate fed to the encoder will be capped at maxFps.
- status_t setMaxFps(float maxFps);
-
- // Sets the time lapse (or slow motion) parameters.
- // When set, the sample's timestamp will be modified to playback framerate,
- // and capture timestamp will be modified to capture rate.
- status_t setTimeLapseConfig(double fps, double captureFps);
-
- // Sets the start time us (in system time), samples before which should
- // be dropped and not submitted to encoder
- status_t setStartTimeUs(int64_t startTimeUs);
-
- // Sets the stop time us (in system time), samples after which should be dropped
- // and not submitted to encoder. timeUs uses SYSTEM_TIME_MONOTONIC time base.
- status_t setStopTimeUs(int64_t stopTimeUs);
-
- // Gets the stop time offset in us. This is the time offset between latest buffer
- // time and the stopTimeUs. If stop time is not set, INVALID_OPERATION will be returned.
- // If return is OK, *stopTimeOffsetUs will contain the valid offset. Otherwise,
- // *stopTimeOffsetUs will not be modified. Positive stopTimeOffsetUs means buffer time
- // larger than stopTimeUs.
- status_t getStopTimeOffsetUs(int64_t *stopTimeOffsetUs);
-
- // Sets the desired color aspects, e.g. to be used when producer does not specify a dataspace.
- status_t setColorAspects(int32_t aspectsPacked);
-
-protected:
- // BQ::ConsumerListener interface
- // ------------------------------
-
- // BufferQueue::ConsumerListener interface, called when a new frame of
- // data is available. If we're executing and a codec buffer is
- // available, we acquire the buffer, copy the GraphicBuffer reference
- // into the codec buffer, and call Empty[This]Buffer. If we're not yet
- // executing or there's no codec buffer available, we just increment
- // mNumFramesAvailable and return.
- void onFrameAvailable(const BufferItem& item) override;
-
- // BufferQueue::ConsumerListener interface, called when the client has
- // released one or more GraphicBuffers. We clear out the appropriate
- // set of mBufferSlot entries.
- void onBuffersReleased() override;
-
- // BufferQueue::ConsumerListener interface, called when the client has
- // changed the sideband stream. GraphicBufferSource doesn't handle sideband
- // streams so this is a no-op (and should never be called).
- void onSidebandStreamChanged() override;
-
-private:
- // Lock, covers all member variables.
- mutable Mutex mMutex;
-
- // Used to report constructor failure.
- status_t mInitCheck;
-
- // Graphic buffer reference objects
- // --------------------------------
-
- // These are used to keep a shared reference to GraphicBuffers and gralloc handles owned by the
- // GraphicBufferSource as well as to manage the cache slots. Separate references are owned by
- // the buffer cache (controlled by the buffer queue/buffer producer) and the codec.
-
- // When we get a buffer from the producer (BQ) it designates them to be cached into specific
- // slots. Each slot owns a shared reference to the graphic buffer (we track these using
- // CachedBuffer) that is in that slot, but the producer controls the slots.
- struct CachedBuffer;
-
- // When we acquire a buffer, we must release it back to the producer once we (or the codec)
- // no longer uses it (as long as the buffer is still in the cache slot). We use shared
- // AcquiredBuffer instances for this purpose - and we call release buffer when the last
- // reference is relinquished.
- struct AcquiredBuffer;
-
- // We also need to keep some extra metadata (other than the buffer reference) for acquired
- // buffers. These are tracked in VideoBuffer struct.
- struct VideoBuffer {
- std::shared_ptr<AcquiredBuffer> mBuffer;
- nsecs_t mTimestampNs;
- android_dataspace_t mDataspace;
- };
-
- // Cached and aquired buffers
- // --------------------------------
-
- typedef int slot_id;
-
- // Maps a slot to the cached buffer in that slot
- KeyedVector<slot_id, std::shared_ptr<CachedBuffer>> mBufferSlots;
-
- // Queue of buffers acquired in chronological order that are not yet submitted to the codec
- List<VideoBuffer> mAvailableBuffers;
-
- // Number of buffers that have been signaled by the producer that they are available, but
- // we've been unable to acquire them due to our max acquire count
- int32_t mNumAvailableUnacquiredBuffers;
-
- // Number of frames acquired from consumer (debug only)
- // (as in aquireBuffer called, and release needs to be called)
- int32_t mNumOutstandingAcquires;
-
- // Acquire a buffer from the BQ and store it in |item| if successful
- // \return OK on success, or error on failure.
- status_t acquireBuffer_l(VideoBuffer *item);
-
- // Called when a buffer was acquired from the producer
- void onBufferAcquired_l(const VideoBuffer &buffer);
-
- // marks the buffer at the slot no longer cached, and accounts for the outstanding
- // acquire count. Returns true if the slot was populated; otherwise, false.
- bool discardBufferInSlot_l(slot_id i);
-
- // marks the buffer at the slot index no longer cached, and accounts for the outstanding
- // acquire count
- void discardBufferAtSlotIndex_l(ssize_t bsi);
-
- // release all acquired and unacquired available buffers
- // This method will return if it fails to acquire an unacquired available buffer, which will
- // leave mNumAvailableUnacquiredBuffers positive on return.
- void releaseAllAvailableBuffers_l();
-
- // returns whether we have any available buffers (acquired or not-yet-acquired)
- bool haveAvailableBuffers_l() const {
- return !mAvailableBuffers.empty() || mNumAvailableUnacquiredBuffers > 0;
- }
-
- // Codec buffers
- // -------------
-
- // When we queue buffers to the encoder, we must hold the references to the graphic buffers
- // in those buffers - as the producer may free the slots.
-
- typedef int32_t codec_buffer_id;
-
- // set of codec buffer ID-s of buffers available to fill
- List<codec_buffer_id> mFreeCodecBuffers;
-
- // maps codec buffer ID-s to buffer info submitted to the codec. Used to keep a reference for
- // the graphics buffer.
- KeyedVector<codec_buffer_id, std::shared_ptr<AcquiredBuffer>> mSubmittedCodecBuffers;
-
- // Processes the next acquired frame. If there is no available codec buffer, it returns false
- // without any further action.
- //
- // Otherwise, it consumes the next acquired frame and determines if it needs to be discarded or
- // dropped. If neither are needed, it submits it to the codec. It also saves the latest
- // non-dropped frame and submits it for repeat encoding (if this is enabled).
- //
- // \require there must be an acquired frame (i.e. we're in the onFrameAvailable callback,
- // or if we're in codecBufferEmptied and mNumFramesAvailable is nonzero).
- // \require codec must be executing
- // \returns true if acquired (and handled) the next frame. Otherwise, false.
- bool fillCodecBuffer_l();
-
- // Calculates the media timestamp for |item| and on success it submits the buffer to the codec,
- // while also keeping a reference for it in mSubmittedCodecBuffers.
- // Returns UNKNOWN_ERROR if the buffer was not submitted due to buffer timestamp. Otherwise,
- // it returns any submit success or error value returned by the codec.
- status_t submitBuffer_l(const VideoBuffer &item);
-
- // Submits an empty buffer, with the EOS flag set if there is an available codec buffer and
- // sets mEndOfStreamSent flag. Does nothing if there is no codec buffer available.
- void submitEndOfInputStream_l();
-
- // Set to true if we want to send end-of-stream after we run out of available frames from the
- // producer
- bool mEndOfStream;
-
- // Flag that the EOS was submitted to the encoder
- bool mEndOfStreamSent;
-
- // Dataspace for the last frame submitted to the codec
- android_dataspace mLastDataspace;
-
- // Default color aspects for this source
- int32_t mDefaultColorAspectsPacked;
-
- // called when the data space of the input buffer changes
- void onDataspaceChanged_l(android_dataspace dataspace, android_pixel_format pixelFormat);
-
- // Pointer back to the Omx node that created us. We send buffers here.
- sp<IOmxNodeWrapper> mOMXNode;
-
- // Set by omxExecuting() / omxIdling().
- bool mExecuting;
-
- bool mSuspended;
-
- // returns true if this source is unconditionally discarding acquired buffers at the moment
- // regardless of the metadata of those buffers
- bool areWeDiscardingAvailableBuffers_l();
-
- int64_t mLastFrameTimestampUs;
-
- // Our BufferQueue interfaces. mProducer is passed to the producer through
- // getIGraphicBufferProducer, and mConsumer is used internally to retrieve
- // the buffers queued by the producer.
- sp<IGraphicBufferProducer> mProducer;
- sp<IGraphicBufferConsumer> mConsumer;
-
- // The time to stop sending buffers.
- int64_t mStopTimeUs;
-
- struct ActionItem {
- typedef enum {
- PAUSE,
- RESUME,
- STOP
- } ActionType;
- ActionType mAction;
- int64_t mActionTimeUs;
- };
-
- // Maintain last action timestamp to ensure all the action timestamps are
- // monotonically increasing.
- int64_t mLastActionTimeUs;
-
- // An action queue that queue up all the actions sent to GraphicBufferSource.
- // STOP action should only show up at the end of the list as all the actions
- // after a STOP action will be discarded. mActionQueue is protected by mMutex.
- List<ActionItem> mActionQueue;
-
- ////
- friend struct AHandlerReflector<GraphicBufferSource>;
-
- enum {
- kWhatRepeatLastFrame, ///< queue last frame for reencoding
- };
- enum {
- kRepeatLastFrameCount = 10,
- };
-
- int64_t mSkipFramesBeforeNs;
-
- sp<FrameDropper> mFrameDropper;
-
- sp<ALooper> mLooper;
- sp<AHandlerReflector<GraphicBufferSource> > mReflector;
-
- // Repeat last frame feature
- // -------------------------
- // configuration parameter: repeat interval for frame repeating (<0 if repeating is disabled)
- int64_t mFrameRepeatIntervalUs;
-
- // current frame repeat generation - used to cancel a pending frame repeat
- int32_t mRepeatLastFrameGeneration;
-
- // number of times to repeat latest frame (0 = none)
- int32_t mOutstandingFrameRepeatCount;
-
- // The previous buffer should've been repeated but
- // no codec buffer was available at the time.
- bool mFrameRepeatBlockedOnCodecBuffer;
-
- // hold a reference to the last acquired (and not discarded) frame for frame repeating
- VideoBuffer mLatestBuffer;
-
- // queue last frame for reencode after the repeat interval.
- void queueFrameRepeat_l();
-
- // save |item| as the latest buffer and queue it for reencode (repeat)
- void setLatestBuffer_l(const VideoBuffer &item);
-
- // submit last frame to encoder and queue it for reencode
- // \return true if buffer was submitted, false if it wasn't (e.g. source is suspended, there
- // is no available codec buffer)
- bool repeatLatestBuffer_l();
-
- // Time lapse / slow motion configuration
- // --------------------------------------
-
- // desired frame rate for encoding - value <= 0 if undefined
- double mFps;
-
- // desired frame rate for capture - value <= 0 if undefined
- double mCaptureFps;
-
- // Time lapse mode is enabled if the capture frame rate is defined and it is
- // smaller than half the encoding frame rate (if defined). In this mode,
- // frames that come in between the capture interval (the reciprocal of the
- // capture frame rate) are dropped and the encoding timestamp is adjusted to
- // match the desired encoding frame rate.
- //
- // Slow motion mode is enabled if both encoding and capture frame rates are
- // defined and the encoding frame rate is less than half the capture frame
- // rate. In this mode, the source is expected to produce frames with an even
- // timestamp interval (after rounding) with the configured capture fps. The
- // first source timestamp is used as the source base time. Afterwards, the
- // timestamp of each source frame is snapped to the nearest expected capture
- // timestamp and scaled to match the configured encoding frame rate.
-
- // These modes must be enabled before using this source.
-
- // adjusted capture timestamp of the base frame
- int64_t mBaseCaptureUs;
-
- // adjusted encoding timestamp of the base frame
- int64_t mBaseFrameUs;
-
- // number of frames from the base time
- int64_t mFrameCount;
-
- // adjusted capture timestamp for previous frame (negative if there were
- // none)
- int64_t mPrevCaptureUs;
-
- // adjusted media timestamp for previous frame (negative if there were none)
- int64_t mPrevFrameUs;
-
- // desired offset between media time and capture time
- int64_t mInputBufferTimeOffsetUs;
-
- // Calculates and outputs the timestamp to use for a buffer with a specific buffer timestamp
- // |bufferTimestampNs|. Returns false on failure (buffer too close or timestamp is moving
- // backwards). Otherwise, stores the media timestamp in |*codecTimeUs| and returns true.
- //
- // This method takes into account the start time offset and any time lapse or slow motion time
- // adjustment requests.
- bool calculateCodecTimestamp_l(nsecs_t bufferTimeNs, int64_t *codecTimeUs);
-
- void onMessageReceived(const sp<AMessage> &msg);
-
- DISALLOW_EVIL_CONSTRUCTORS(GraphicBufferSource);
-};
-
-} // namespace android
-
-#endif // GRAPHIC_BUFFER_SOURCE_H_
diff --git a/media/libstagefright/omx/include/media/stagefright/omx/OmxGraphicBufferSource.h b/media/libstagefright/omx/include/media/stagefright/omx/OmxGraphicBufferSource.h
new file mode 100644
index 0000000..4b0f3d2
--- /dev/null
+++ b/media/libstagefright/omx/include/media/stagefright/omx/OmxGraphicBufferSource.h
@@ -0,0 +1,84 @@
+/*
+ * 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 OMX_GRAPHIC_BUFFER_SOURCE_H_
+
+#define OMX_GRAPHIC_BUFFER_SOURCE_H_
+
+#include <media/stagefright/gbs/GraphicBufferSource.h>
+#include <media/stagefright/foundation/ABase.h>
+
+#include <android/BnGraphicBufferSource.h>
+#include <android/BnOMXBufferSource.h>
+
+#include "IOmxNodeWrapper.h"
+
+namespace android {
+
+using ::android::binder::Status;
+
+/*
+ * This class is used to feed OMX codecs from a Surface via BufferQueue or
+ * HW producer.
+ *
+ * See media/stagefright/gbs/GraphicBufferSource.h for documentation.
+ */
+class OmxGraphicBufferSource : public GraphicBufferSource {
+public:
+ OmxGraphicBufferSource() = default;
+ virtual ~OmxGraphicBufferSource() = default;
+
+ // OmxBufferSource interface
+ // ------------------------------
+
+ // This is called when OMX transitions to OMX_StateExecuting, which means
+ // we can start handing it buffers. If we already have buffers of data
+ // sitting in the BufferQueue, this will send them to the codec.
+ Status onOmxExecuting();
+
+ // This is called when OMX transitions to OMX_StateIdle, indicating that
+ // the codec is meant to return all buffers back to the client for them
+ // to be freed. Do NOT submit any more buffers to the component.
+ Status onOmxIdle();
+
+ // This is called when OMX transitions to OMX_StateLoaded, indicating that
+ // we are shutting down.
+ Status onOmxLoaded();
+
+ // Rest of the interface in GraphicBufferSource.
+
+ // IGraphicBufferSource interface
+ // ------------------------------
+
+ // Configure the buffer source to be used with an OMX node with the default
+ // data space.
+ status_t configure(
+ const sp<IOmxNodeWrapper> &omxNode,
+ int32_t dataSpace,
+ int32_t bufferCount,
+ uint32_t frameWidth,
+ uint32_t frameHeight,
+ uint32_t consumerUsage);
+
+ // Rest of the interface in GraphicBufferSource.
+
+private:
+ DISALLOW_EVIL_CONSTRUCTORS(OmxGraphicBufferSource);
+};
+
+} // namespace android
+
+#endif // OMX_GRAPHIC_BUFFER_SOURCE_H_
diff --git a/media/libstagefright/omx/tests/Android.bp b/media/libstagefright/omx/tests/Android.bp
index 999d9d4..3b521ab 100644
--- a/media/libstagefright/omx/tests/Android.bp
+++ b/media/libstagefright/omx/tests/Android.bp
@@ -34,21 +34,3 @@
compile_multilib: "32",
}
-
-cc_test {
- name: "FrameDropper_test",
-
- srcs: ["FrameDropper_test.cpp"],
-
- shared_libs: [
- "libstagefright_omx",
- "libutils",
- ],
-
- include_dirs: ["frameworks/av/media/libstagefright/omx"],
-
- cflags: [
- "-Werror",
- "-Wall",
- ],
-}
diff --git a/media/libstagefright/omx/tests/FrameDropper_test.cpp b/media/libstagefright/omx/tests/FrameDropper_test.cpp
deleted file mode 100644
index a925da6..0000000
--- a/media/libstagefright/omx/tests/FrameDropper_test.cpp
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright (C) 2015 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 "FrameDropper_test"
-#include <utils/Log.h>
-
-#include <gtest/gtest.h>
-
-#include <media/stagefright/omx/FrameDropper.h>
-#include <media/stagefright/foundation/ADebug.h>
-
-namespace android {
-
-struct TestFrame {
- int64_t timeUs;
- bool shouldDrop;
-};
-
-static const TestFrame testFrames20Fps[] = {
- {1000000, false}, {1050000, false}, {1100000, false}, {1150000, false},
- {1200000, false}, {1250000, false}, {1300000, false}, {1350000, false},
- {1400000, false}, {1450000, false}, {1500000, false}, {1550000, false},
- {1600000, false}, {1650000, false}, {1700000, false}, {1750000, false},
- {1800000, false}, {1850000, false}, {1900000, false}, {1950000, false},
-};
-
-static const TestFrame testFrames30Fps[] = {
- {1000000, false}, {1033333, false}, {1066667, false}, {1100000, false},
- {1133333, false}, {1166667, false}, {1200000, false}, {1233333, false},
- {1266667, false}, {1300000, false}, {1333333, false}, {1366667, false},
- {1400000, false}, {1433333, false}, {1466667, false}, {1500000, false},
- {1533333, false}, {1566667, false}, {1600000, false}, {1633333, false},
-};
-
-static const TestFrame testFrames40Fps[] = {
- {1000000, false}, {1025000, true}, {1050000, false}, {1075000, false},
- {1100000, false}, {1125000, true}, {1150000, false}, {1175000, false},
- {1200000, false}, {1225000, true}, {1250000, false}, {1275000, false},
- {1300000, false}, {1325000, true}, {1350000, false}, {1375000, false},
- {1400000, false}, {1425000, true}, {1450000, false}, {1475000, false},
-};
-
-static const TestFrame testFrames60Fps[] = {
- {1000000, false}, {1016667, true}, {1033333, false}, {1050000, true},
- {1066667, false}, {1083333, true}, {1100000, false}, {1116667, true},
- {1133333, false}, {1150000, true}, {1166667, false}, {1183333, true},
- {1200000, false}, {1216667, true}, {1233333, false}, {1250000, true},
- {1266667, false}, {1283333, true}, {1300000, false}, {1316667, true},
-};
-
-static const TestFrame testFramesVariableFps[] = {
- // 40fps
- {1000000, false}, {1025000, true}, {1050000, false}, {1075000, false},
- {1100000, false}, {1125000, true}, {1150000, false}, {1175000, false},
- {1200000, false}, {1225000, true}, {1250000, false}, {1275000, false},
- {1300000, false}, {1325000, true}, {1350000, false}, {1375000, false},
- {1400000, false}, {1425000, true}, {1450000, false}, {1475000, false},
- // a timestamp jump plus switch to 20fps
- {2000000, false}, {2050000, false}, {2100000, false}, {2150000, false},
- {2200000, false}, {2250000, false}, {2300000, false}, {2350000, false},
- {2400000, false}, {2450000, false}, {2500000, false}, {2550000, false},
- {2600000, false}, {2650000, false}, {2700000, false}, {2750000, false},
- {2800000, false}, {2850000, false}, {2900000, false}, {2950000, false},
- // 60fps
- {2966667, false}, {2983333, true}, {3000000, false}, {3016667, true},
- {3033333, false}, {3050000, true}, {3066667, false}, {3083333, true},
- {3100000, false}, {3116667, true}, {3133333, false}, {3150000, true},
- {3166667, false}, {3183333, true}, {3200000, false}, {3216667, true},
- {3233333, false}, {3250000, true}, {3266667, false}, {3283333, true},
-};
-
-static const int kMaxTestJitterUs = 2000;
-// return one of 1000, 0, -1000 as jitter.
-static int GetJitter(size_t i) {
- return (1 - (i % 3)) * (kMaxTestJitterUs / 2);
-}
-
-class FrameDropperTest : public ::testing::Test {
-public:
- FrameDropperTest() : mFrameDropper(new FrameDropper()) {
- EXPECT_EQ(OK, mFrameDropper->setMaxFrameRate(30.0));
- }
-
-protected:
- void RunTest(const TestFrame* frames, size_t size) {
- for (size_t i = 0; i < size; ++i) {
- int jitter = GetJitter(i);
- int64_t testTimeUs = frames[i].timeUs + jitter;
- printf("time %lld, testTime %lld, jitter %d\n",
- (long long)frames[i].timeUs, (long long)testTimeUs, jitter);
- EXPECT_EQ(frames[i].shouldDrop, mFrameDropper->shouldDrop(testTimeUs));
- }
- }
-
- sp<FrameDropper> mFrameDropper;
-};
-
-TEST_F(FrameDropperTest, TestInvalidMaxFrameRate) {
- EXPECT_NE(OK, mFrameDropper->setMaxFrameRate(-1.0));
- EXPECT_NE(OK, mFrameDropper->setMaxFrameRate(0));
-}
-
-TEST_F(FrameDropperTest, Test20Fps) {
- RunTest(testFrames20Fps, ARRAY_SIZE(testFrames20Fps));
-}
-
-TEST_F(FrameDropperTest, Test30Fps) {
- RunTest(testFrames30Fps, ARRAY_SIZE(testFrames30Fps));
-}
-
-TEST_F(FrameDropperTest, Test40Fps) {
- RunTest(testFrames40Fps, ARRAY_SIZE(testFrames40Fps));
-}
-
-TEST_F(FrameDropperTest, Test60Fps) {
- RunTest(testFrames60Fps, ARRAY_SIZE(testFrames60Fps));
-}
-
-TEST_F(FrameDropperTest, TestVariableFps) {
- RunTest(testFramesVariableFps, ARRAY_SIZE(testFramesVariableFps));
-}
-
-} // namespace android
diff --git a/media/libstagefright/rtsp/AMPEG2TSAssembler.h b/media/libstagefright/rtsp/AMPEG2TSAssembler.h
index f39c2b5..c987b5b 100644
--- a/media/libstagefright/rtsp/AMPEG2TSAssembler.h
+++ b/media/libstagefright/rtsp/AMPEG2TSAssembler.h
@@ -24,7 +24,6 @@
struct AMessage;
struct AString;
-class MetaData;
struct AMPEG2TSAssembler : public ARTPAssembler {
AMPEG2TSAssembler(
diff --git a/media/libstagefright/rtsp/rtp_test.cpp b/media/libstagefright/rtsp/rtp_test.cpp
index 98a8fb4..4590699 100644
--- a/media/libstagefright/rtsp/rtp_test.cpp
+++ b/media/libstagefright/rtsp/rtp_test.cpp
@@ -25,7 +25,6 @@
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/ALooper.h>
#include <media/stagefright/MediaBuffer.h>
-#include <media/stagefright/MetaData.h>
#include <media/stagefright/SimpleDecodingSource.h>
#include "ARTPSession.h"
diff --git a/media/libstagefright/tests/Android.bp b/media/libstagefright/tests/Android.bp
index 35119c2..e67a949 100644
--- a/media/libstagefright/tests/Android.bp
+++ b/media/libstagefright/tests/Android.bp
@@ -15,6 +15,7 @@
"libcutils",
"libgui",
"libmedia",
+ "libmediaextractor",
"libstagefright",
"libstagefright_foundation",
"libstagefright_omx",
diff --git a/media/ndk/Android.bp b/media/ndk/Android.bp
index cea2f9e..ca691f7 100644
--- a/media/ndk/Android.bp
+++ b/media/ndk/Android.bp
@@ -37,6 +37,7 @@
srcs: [
"NdkMediaCodec.cpp",
"NdkMediaCrypto.cpp",
+ "NdkMediaDataSource.cpp",
"NdkMediaExtractor.cpp",
"NdkMediaFormat.cpp",
"NdkMediaMuxer.cpp",
diff --git a/media/ndk/NdkMediaDataSource.cpp b/media/ndk/NdkMediaDataSource.cpp
new file mode 100644
index 0000000..0cae6f4
--- /dev/null
+++ b/media/ndk/NdkMediaDataSource.cpp
@@ -0,0 +1,115 @@
+/*
+ * 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "NdkMediaDataSource"
+
+#include "NdkMediaDataSourcePriv.h"
+
+#include <inttypes.h>
+#include <jni.h>
+#include <unistd.h>
+
+#include <binder/IServiceManager.h>
+#include <cutils/properties.h>
+#include <utils/Log.h>
+#include <utils/StrongPointer.h>
+#include <media/NdkMediaError.h>
+#include <media/NdkMediaDataSource.h>
+#include <media/stagefright/InterfaceUtils.h>
+
+#include "../../libstagefright/include/HTTPBase.h"
+#include "../../libstagefright/include/NuCachedSource2.h"
+
+using namespace android;
+
+struct AMediaDataSource {
+ void *userdata;
+ AMediaDataSourceReadAt readAt;
+ AMediaDataSourceGetSize getSize;
+};
+
+NdkDataSource::NdkDataSource(AMediaDataSource *dataSource)
+ : mDataSource(dataSource) {
+}
+
+status_t NdkDataSource::initCheck() const {
+ return OK;
+}
+
+ssize_t NdkDataSource::readAt(off64_t offset, void *data, size_t size) {
+ Mutex::Autolock l(mLock);
+ if (mDataSource->getSize == NULL || mDataSource->userdata == NULL) {
+ return -1;
+ }
+ return mDataSource->readAt(mDataSource->userdata, offset, data, size);
+}
+
+status_t NdkDataSource::getSize(off64_t *size) {
+ Mutex::Autolock l(mLock);
+ if (mDataSource->getSize == NULL || mDataSource->userdata == NULL) {
+ return NO_INIT;
+ }
+ if (size != NULL) {
+ *size = mDataSource->getSize(mDataSource->userdata);
+ }
+ return OK;
+}
+
+String8 NdkDataSource::toString() {
+ return String8::format("NdkDataSource(pid %d, uid %d)", getpid(), getuid());
+}
+
+String8 NdkDataSource::getMIMEType() const {
+ return String8("application/octet-stream");
+}
+
+extern "C" {
+
+EXPORT
+AMediaDataSource* AMediaDataSource_new() {
+ AMediaDataSource *mSource = new AMediaDataSource();
+ mSource->userdata = NULL;
+ mSource->readAt = NULL;
+ mSource->getSize = NULL;
+ return mSource;
+}
+
+EXPORT
+void AMediaDataSource_delete(AMediaDataSource *mSource) {
+ ALOGV("dtor");
+ if (mSource != NULL) {
+ delete mSource;
+ }
+}
+
+EXPORT
+void AMediaDataSource_setUserdata(AMediaDataSource *mSource, void *userdata) {
+ mSource->userdata = userdata;
+}
+
+EXPORT
+void AMediaDataSource_setReadAt(AMediaDataSource *mSource, AMediaDataSourceReadAt readAt) {
+ mSource->readAt = readAt;
+}
+
+EXPORT
+void AMediaDataSource_setGetSize(AMediaDataSource *mSource, AMediaDataSourceGetSize getSize) {
+ mSource->getSize = getSize;
+}
+
+} // extern "C"
+
diff --git a/media/ndk/NdkMediaDataSourcePriv.h b/media/ndk/NdkMediaDataSourcePriv.h
new file mode 100644
index 0000000..a1cb331
--- /dev/null
+++ b/media/ndk/NdkMediaDataSourcePriv.h
@@ -0,0 +1,59 @@
+/*
+ * 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.
+ */
+
+
+/*
+ * This file defines an NDK API.
+ * Do not remove methods.
+ * Do not change method signatures.
+ * Do not change the value of constants.
+ * Do not change the size of any of the classes defined in here.
+ * Do not reference types that are not part of the NDK.
+ * Do not #include files that aren't part of the NDK.
+ */
+
+#ifndef _NDK_MEDIA_DATASOURCE_PRIV_H
+#define _NDK_MEDIA_DATASOURCE_PRIV_H
+
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+#include <media/DataSource.h>
+#include <media/NdkMediaDataSource.h>
+#include <utils/Mutex.h>
+#include <utils/String8.h>
+
+using namespace android;
+
+struct NdkDataSource : public DataSource {
+
+ NdkDataSource(AMediaDataSource *);
+
+ virtual status_t initCheck() const;
+ virtual ssize_t readAt(off64_t offset, void *data, size_t size);
+ virtual status_t getSize(off64_t *);
+ virtual String8 toString();
+ virtual String8 getMIMEType() const;
+
+private:
+
+ Mutex mLock;
+ AMediaDataSource *mDataSource;
+
+};
+
+#endif // _NDK_MEDIA_DATASOURCE_PRIV_H
+
diff --git a/media/ndk/NdkMediaExtractor.cpp b/media/ndk/NdkMediaExtractor.cpp
index e677d00..5dee8b0 100644
--- a/media/ndk/NdkMediaExtractor.cpp
+++ b/media/ndk/NdkMediaExtractor.cpp
@@ -20,6 +20,7 @@
#include <media/NdkMediaError.h>
#include <media/NdkMediaExtractor.h>
+#include "NdkMediaDataSourcePriv.h"
#include "NdkMediaFormatPriv.h"
@@ -121,6 +122,11 @@
}
EXPORT
+media_status_t AMediaExtractor_setDataSourceCustom(AMediaExtractor* mData, AMediaDataSource *src) {
+ return translate_error(mData->mImpl->setDataSource(new NdkDataSource(src)));
+}
+
+EXPORT
size_t AMediaExtractor_getTrackCount(AMediaExtractor *mData) {
return mData->mImpl->countTracks();
}
diff --git a/media/ndk/NdkMediaFormat.cpp b/media/ndk/NdkMediaFormat.cpp
index a9025c0..b86876b 100644
--- a/media/ndk/NdkMediaFormat.cpp
+++ b/media/ndk/NdkMediaFormat.cpp
@@ -25,7 +25,6 @@
#include <utils/StrongPointer.h>
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/AMessage.h>
-#include <media/stagefright/MetaData.h>
#include <android_runtime/AndroidRuntime.h>
#include <android_util_Binder.h>
diff --git a/media/ndk/NdkMediaMuxer.cpp b/media/ndk/NdkMediaMuxer.cpp
index 80a4391..dffc4d7 100644
--- a/media/ndk/NdkMediaMuxer.cpp
+++ b/media/ndk/NdkMediaMuxer.cpp
@@ -27,7 +27,6 @@
#include <utils/StrongPointer.h>
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/AMessage.h>
-#include <media/stagefright/MetaData.h>
#include <media/stagefright/MediaMuxer.h>
#include <media/IMediaHTTPService.h>
#include <android_runtime/AndroidRuntime.h>
diff --git a/media/ndk/include/media/NdkMediaDataSource.h b/media/ndk/include/media/NdkMediaDataSource.h
new file mode 100644
index 0000000..752b684
--- /dev/null
+++ b/media/ndk/include/media/NdkMediaDataSource.h
@@ -0,0 +1,124 @@
+/*
+ * 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.
+ */
+
+
+/*
+ * This file defines an NDK API.
+ * Do not remove methods.
+ * Do not change method signatures.
+ * Do not change the value of constants.
+ * Do not change the size of any of the classes defined in here.
+ * Do not reference types that are not part of the NDK.
+ * Do not #include files that aren't part of the NDK.
+ */
+
+#ifndef _NDK_MEDIA_DATASOURCE_H
+#define _NDK_MEDIA_DATASOURCE_H
+
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+#include <media/NdkMediaError.h>
+
+__BEGIN_DECLS
+
+struct AMediaDataSource;
+typedef struct AMediaDataSource AMediaDataSource;
+
+#if __ANDROID_API__ >= 28
+
+/*
+ * AMediaDataSource's callbacks will be invoked on an implementation-defined thread
+ * or thread pool. No guarantees are provided about which thread(s) will be used for
+ * callbacks. However, it is guaranteed that AMediaDataSource's callbacks will only
+ * ever be invoked by a single thread at a time.
+ *
+ * There will be a thread synchronization point between each call to ensure that
+ * modifications to the state of your AMediaDataSource are visible to future
+ * calls. This means you don't need to do your own synchronization unless you're
+ * modifying the AMediaDataSource from another thread while it's being used by the
+ * framework.
+ */
+
+/**
+ * Called to request data from the given |offset|.
+ *
+ * Implementations should should write up to |size| bytes into
+ * |buffer|, and return the number of bytes written.
+ *
+ * Return 0 if size is zero (thus no bytes are read).
+ *
+ * Return -1 to indicate that end of stream is reached.
+ */
+typedef ssize_t (*AMediaDataSourceReadAt)(
+ void *userdata, off64_t offset, void * buffer, size_t size);
+
+/**
+ * Called to get the size of the data source.
+ *
+ * Return the size of data source in bytes, or -1 if the size is unknown.
+ */
+typedef ssize_t (*AMediaDataSourceGetSize)(void *userdata);
+
+/**
+ * Create new media data source. Returns NULL if memory allocation
+ * for the new data source object fails.
+ */
+AMediaDataSource* AMediaDataSource_new();
+
+/**
+ * Delete a previously created media data source.
+ */
+void AMediaDataSource_delete(AMediaDataSource*);
+
+/**
+ * Set an user provided opaque handle. This opaque handle is passed as
+ * the first argument to the data source callbacks.
+ */
+void AMediaDataSource_setUserdata(
+ AMediaDataSource*, void *userdata);
+
+/**
+ * Set a custom callback for supplying random access media data to the
+ * NDK media framework.
+ *
+ * Implement this if your app has special requirements for the way media
+ * data is obtained, or if you need a callback when data is read by the
+ * NDK media framework.
+ *
+ * Please refer to the definition of AMediaDataSourceReadAt for
+ * additional details.
+ */
+void AMediaDataSource_setReadAt(
+ AMediaDataSource*,
+ AMediaDataSourceReadAt);
+
+/**
+ * Set a custom callback for supplying the size of the data source to the
+ * NDK media framework.
+ *
+ * Please refer to the definition of AMediaDataSourceGetSize for
+ * additional details.
+ */
+void AMediaDataSource_setGetSize(
+ AMediaDataSource*,
+ AMediaDataSourceGetSize);
+
+#endif /*__ANDROID_API__ >= 28 */
+
+__END_DECLS
+
+#endif // _NDK_MEDIA_DATASOURCE_H
diff --git a/media/ndk/include/media/NdkMediaExtractor.h b/media/ndk/include/media/NdkMediaExtractor.h
index bf0e46d..820e9f5 100644
--- a/media/ndk/include/media/NdkMediaExtractor.h
+++ b/media/ndk/include/media/NdkMediaExtractor.h
@@ -32,6 +32,7 @@
#include <sys/types.h>
#include "NdkMediaCodec.h"
+#include "NdkMediaDataSource.h"
#include "NdkMediaFormat.h"
#include "NdkMediaCrypto.h"
@@ -64,6 +65,15 @@
media_status_t AMediaExtractor_setDataSource(AMediaExtractor*, const char *location);
// TODO support headers
+#if __ANDROID_API__ >= 28
+
+/**
+ * Set the custom data source implementation from which the extractor will read.
+ */
+media_status_t AMediaExtractor_setDataSourceCustom(AMediaExtractor*, AMediaDataSource *src);
+
+#endif /* __ANDROID_API__ >= 28 */
+
/**
* Return the number of tracks in the previously specified media file
*/
diff --git a/media/ndk/libmediandk.map.txt b/media/ndk/libmediandk.map.txt
index f2d97cd..613cc63 100644
--- a/media/ndk/libmediandk.map.txt
+++ b/media/ndk/libmediandk.map.txt
@@ -123,6 +123,11 @@
AMediaCrypto_isCryptoSchemeSupported;
AMediaCrypto_new;
AMediaCrypto_requiresSecureDecoderComponent;
+ AMediaDataSource_delete; # introduced=28
+ AMediaDataSource_new; # introduced=28
+ AMediaDataSource_setGetSize; # introduced=28
+ AMediaDataSource_setReadAt; # introduced=28
+ AMediaDataSource_setUserdata; # introduced=28
AMediaDrm_closeSession;
AMediaDrm_createByUUID;
AMediaDrm_decrypt;
@@ -160,6 +165,7 @@
AMediaExtractor_seekTo;
AMediaExtractor_selectTrack;
AMediaExtractor_setDataSource;
+ AMediaExtractor_setDataSourceCustom; # introduced=28
AMediaExtractor_setDataSourceFd;
AMediaExtractor_unselectTrack;
AMediaFormat_delete;
diff --git a/media/utils/Android.bp b/media/utils/Android.bp
index f2bc6d0..d6dae5b 100644
--- a/media/utils/Android.bp
+++ b/media/utils/Android.bp
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-cc_library_shared {
+cc_library {
name: "libmediautils",
srcs: [
diff --git a/packages/MediaComponents/Android.mk b/packages/MediaComponents/Android.mk
index 2fa4a82..61d73a0 100644
--- a/packages/MediaComponents/Android.mk
+++ b/packages/MediaComponents/Android.mk
@@ -32,21 +32,29 @@
LOCAL_MULTILIB := first
-# Embed native libraries in package, rather than installing to /system/lib*.
-# TODO: Find a right way to include libs in the apk. b/72066556
-LOCAL_MODULE_TAGS := samples
+LOCAL_JAVA_LIBRARIES += android-support-annotations
# To embed native libraries in package, uncomment the lines below.
-LOCAL_JNI_SHARED_LIBRARIES := \
- libaacextractor \
- libamrextractor \
- libflacextractor \
- libmidiextractor \
- libmkvextractor \
- libmp3extractor \
- libmp4extractor \
- libmpeg2extractor \
- liboggextractor \
- libwavextractor \
+#LOCAL_MODULE_TAGS := samples
+#LOCAL_JNI_SHARED_LIBRARIES := \
+# libaacextractor \
+# libamrextractor \
+# libflacextractor \
+# libmidiextractor \
+# libmkvextractor \
+# libmp3extractor \
+# libmp4extractor \
+# libmpeg2extractor \
+# liboggextractor \
+# libwavextractor \
+
+# TODO: Remove dependency with other support libraries.
+LOCAL_STATIC_ANDROID_LIBRARIES += \
+ android-support-v4 \
+ android-support-v7-appcompat \
+ android-support-v7-palette
+LOCAL_USE_AAPT2 := true
include $(BUILD_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/packages/MediaComponents/proguard.cfg b/packages/MediaComponents/proguard.cfg
index 874dbf5..43f2e63 100644
--- a/packages/MediaComponents/proguard.cfg
+++ b/packages/MediaComponents/proguard.cfg
@@ -16,5 +16,5 @@
# Keep entry point for updatable Java classes
-keep public class com.android.media.update.ApiFactory {
- public static java.lang.Object initialize(android.content.Context);
+ public static java.lang.Object initialize(android.content.res.Resources, android.content.res.Resources$Theme);
}
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_audiotrack_dark.png b/packages/MediaComponents/res/drawable-hdpi/ic_audiotrack_dark.png
new file mode 100644
index 0000000..17fd51f
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-hdpi/ic_audiotrack_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_audiotrack_light.png b/packages/MediaComponents/res/drawable-hdpi/ic_audiotrack_light.png
new file mode 100644
index 0000000..d7c8252
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-hdpi/ic_audiotrack_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_dialog_close_dark.png b/packages/MediaComponents/res/drawable-hdpi/ic_dialog_close_dark.png
new file mode 100644
index 0000000..928ddea
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-hdpi/ic_dialog_close_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_dialog_close_light.png b/packages/MediaComponents/res/drawable-hdpi/ic_dialog_close_light.png
new file mode 100644
index 0000000..1a9cd75
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-hdpi/ic_dialog_close_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_media_pause_dark.png b/packages/MediaComponents/res/drawable-hdpi/ic_media_pause_dark.png
new file mode 100644
index 0000000..7192ad4
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-hdpi/ic_media_pause_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_media_pause_light.png b/packages/MediaComponents/res/drawable-hdpi/ic_media_pause_light.png
new file mode 100644
index 0000000..bb707ea
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-hdpi/ic_media_pause_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_media_play_dark.png b/packages/MediaComponents/res/drawable-hdpi/ic_media_play_dark.png
new file mode 100644
index 0000000..0c32d00
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-hdpi/ic_media_play_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_media_play_light.png b/packages/MediaComponents/res/drawable-hdpi/ic_media_play_light.png
new file mode 100644
index 0000000..5345ee3
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-hdpi/ic_media_play_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_media_stop_dark.png b/packages/MediaComponents/res/drawable-hdpi/ic_media_stop_dark.png
new file mode 100644
index 0000000..801d341
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-hdpi/ic_media_stop_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_media_stop_light.png b/packages/MediaComponents/res/drawable-hdpi/ic_media_stop_light.png
new file mode 100644
index 0000000..9d6b65d
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-hdpi/ic_media_stop_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_media_subtitle_disabled.png b/packages/MediaComponents/res/drawable-hdpi/ic_media_subtitle_disabled.png
new file mode 100644
index 0000000..0354f61
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-hdpi/ic_media_subtitle_disabled.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_media_subtitle_enabled.png b/packages/MediaComponents/res/drawable-hdpi/ic_media_subtitle_enabled.png
new file mode 100644
index 0000000..5f8febe
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-hdpi/ic_media_subtitle_enabled.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_mr_button_disabled_dark.png b/packages/MediaComponents/res/drawable-hdpi/ic_mr_button_disabled_dark.png
new file mode 100644
index 0000000..8ad305d
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-hdpi/ic_mr_button_disabled_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_mr_button_disabled_light.png b/packages/MediaComponents/res/drawable-hdpi/ic_mr_button_disabled_light.png
new file mode 100644
index 0000000..887fde4
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-hdpi/ic_mr_button_disabled_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_mr_button_disconnected_dark.png b/packages/MediaComponents/res/drawable-hdpi/ic_mr_button_disconnected_dark.png
new file mode 100644
index 0000000..5739df7
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-hdpi/ic_mr_button_disconnected_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_mr_button_disconnected_light.png b/packages/MediaComponents/res/drawable-hdpi/ic_mr_button_disconnected_light.png
new file mode 100644
index 0000000..58c344a
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-hdpi/ic_mr_button_disconnected_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_mr_button_grey.png b/packages/MediaComponents/res/drawable-hdpi/ic_mr_button_grey.png
new file mode 100644
index 0000000..1a03420
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-hdpi/ic_mr_button_grey.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_vol_type_speaker_dark.png b/packages/MediaComponents/res/drawable-hdpi/ic_vol_type_speaker_dark.png
new file mode 100755
index 0000000..723e455
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-hdpi/ic_vol_type_speaker_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_vol_type_speaker_group_dark.png b/packages/MediaComponents/res/drawable-hdpi/ic_vol_type_speaker_group_dark.png
new file mode 100755
index 0000000..40c25a3
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-hdpi/ic_vol_type_speaker_group_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_vol_type_speaker_group_light.png b/packages/MediaComponents/res/drawable-hdpi/ic_vol_type_speaker_group_light.png
new file mode 100755
index 0000000..afdb9c1
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-hdpi/ic_vol_type_speaker_group_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_vol_type_speaker_light.png b/packages/MediaComponents/res/drawable-hdpi/ic_vol_type_speaker_light.png
new file mode 100755
index 0000000..846c109
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-hdpi/ic_vol_type_speaker_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_vol_type_tv_dark.png b/packages/MediaComponents/res/drawable-hdpi/ic_vol_type_tv_dark.png
new file mode 100755
index 0000000..33bf484
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-hdpi/ic_vol_type_tv_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_vol_type_tv_light.png b/packages/MediaComponents/res/drawable-hdpi/ic_vol_type_tv_light.png
new file mode 100755
index 0000000..c911b5c
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-hdpi/ic_vol_type_tv_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_audiotrack_dark.png b/packages/MediaComponents/res/drawable-mdpi/ic_audiotrack_dark.png
new file mode 100644
index 0000000..e94ed50
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-mdpi/ic_audiotrack_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_audiotrack_light.png b/packages/MediaComponents/res/drawable-mdpi/ic_audiotrack_light.png
new file mode 100644
index 0000000..2cf7e0c
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-mdpi/ic_audiotrack_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_dialog_close_dark.png b/packages/MediaComponents/res/drawable-mdpi/ic_dialog_close_dark.png
new file mode 100644
index 0000000..66558a8
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-mdpi/ic_dialog_close_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_dialog_close_light.png b/packages/MediaComponents/res/drawable-mdpi/ic_dialog_close_light.png
new file mode 100644
index 0000000..40a1a84
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-mdpi/ic_dialog_close_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_media_pause_dark.png b/packages/MediaComponents/res/drawable-mdpi/ic_media_pause_dark.png
new file mode 100644
index 0000000..f49aed7
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-mdpi/ic_media_pause_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_media_pause_light.png b/packages/MediaComponents/res/drawable-mdpi/ic_media_pause_light.png
new file mode 100644
index 0000000..74068ea
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-mdpi/ic_media_pause_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_media_play_dark.png b/packages/MediaComponents/res/drawable-mdpi/ic_media_play_dark.png
new file mode 100644
index 0000000..9cc777c
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-mdpi/ic_media_play_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_media_play_light.png b/packages/MediaComponents/res/drawable-mdpi/ic_media_play_light.png
new file mode 100644
index 0000000..f208795
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-mdpi/ic_media_play_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_media_stop_dark.png b/packages/MediaComponents/res/drawable-mdpi/ic_media_stop_dark.png
new file mode 100644
index 0000000..3ad2c9c
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-mdpi/ic_media_stop_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_media_stop_light.png b/packages/MediaComponents/res/drawable-mdpi/ic_media_stop_light.png
new file mode 100644
index 0000000..b002ab7
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-mdpi/ic_media_stop_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_media_subtitle_disabled.png b/packages/MediaComponents/res/drawable-mdpi/ic_media_subtitle_disabled.png
new file mode 100644
index 0000000..0354f61
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-mdpi/ic_media_subtitle_disabled.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_media_subtitle_enabled.png b/packages/MediaComponents/res/drawable-mdpi/ic_media_subtitle_enabled.png
new file mode 100644
index 0000000..5f8febe
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-mdpi/ic_media_subtitle_enabled.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_mr_button_disabled_dark.png b/packages/MediaComponents/res/drawable-mdpi/ic_mr_button_disabled_dark.png
new file mode 100644
index 0000000..4446ea4
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-mdpi/ic_mr_button_disabled_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_mr_button_disabled_light.png b/packages/MediaComponents/res/drawable-mdpi/ic_mr_button_disabled_light.png
new file mode 100644
index 0000000..4d790c6
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-mdpi/ic_mr_button_disabled_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_mr_button_disconnected_dark.png b/packages/MediaComponents/res/drawable-mdpi/ic_mr_button_disconnected_dark.png
new file mode 100644
index 0000000..c401dc0
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-mdpi/ic_mr_button_disconnected_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_mr_button_disconnected_light.png b/packages/MediaComponents/res/drawable-mdpi/ic_mr_button_disconnected_light.png
new file mode 100644
index 0000000..e24d586
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-mdpi/ic_mr_button_disconnected_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_mr_button_grey.png b/packages/MediaComponents/res/drawable-mdpi/ic_mr_button_grey.png
new file mode 100644
index 0000000..ccbb772
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-mdpi/ic_mr_button_grey.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_vol_type_speaker_dark.png b/packages/MediaComponents/res/drawable-mdpi/ic_vol_type_speaker_dark.png
new file mode 100755
index 0000000..7cc9845
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-mdpi/ic_vol_type_speaker_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_vol_type_speaker_group_dark.png b/packages/MediaComponents/res/drawable-mdpi/ic_vol_type_speaker_group_dark.png
new file mode 100755
index 0000000..22617e1
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-mdpi/ic_vol_type_speaker_group_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_vol_type_speaker_group_light.png b/packages/MediaComponents/res/drawable-mdpi/ic_vol_type_speaker_group_light.png
new file mode 100755
index 0000000..cefef3c
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-mdpi/ic_vol_type_speaker_group_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_vol_type_speaker_light.png b/packages/MediaComponents/res/drawable-mdpi/ic_vol_type_speaker_light.png
new file mode 100755
index 0000000..9a0047c
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-mdpi/ic_vol_type_speaker_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_vol_type_tv_dark.png b/packages/MediaComponents/res/drawable-mdpi/ic_vol_type_tv_dark.png
new file mode 100755
index 0000000..ca5d6a2
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-mdpi/ic_vol_type_tv_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_vol_type_tv_light.png b/packages/MediaComponents/res/drawable-mdpi/ic_vol_type_tv_light.png
new file mode 100755
index 0000000..8134310
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-mdpi/ic_vol_type_tv_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_audiotrack_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_audiotrack_dark.png
new file mode 100644
index 0000000..b5c899f
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_audiotrack_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_audiotrack_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_audiotrack_light.png
new file mode 100644
index 0000000..4778e00
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_audiotrack_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_dialog_close_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_dialog_close_dark.png
new file mode 100644
index 0000000..f992fc5
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_dialog_close_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_dialog_close_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_dialog_close_light.png
new file mode 100644
index 0000000..d3884e6
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_dialog_close_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_media_pause_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_media_pause_dark.png
new file mode 100644
index 0000000..660ac65
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_media_pause_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_media_pause_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_media_pause_light.png
new file mode 100644
index 0000000..792104f
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_media_pause_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_media_play_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_media_play_dark.png
new file mode 100644
index 0000000..be5c062
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_media_play_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_media_play_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_media_play_light.png
new file mode 100644
index 0000000..d12d495
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_media_play_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_media_stop_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_media_stop_dark.png
new file mode 100644
index 0000000..5239336
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_media_stop_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_media_stop_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_media_stop_light.png
new file mode 100644
index 0000000..5bc5a6c
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_media_stop_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_00_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_00_dark.png
new file mode 100644
index 0000000..f6dd214
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_00_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_00_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_00_light.png
new file mode 100644
index 0000000..6b7bdcd
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_00_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_01_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_01_dark.png
new file mode 100644
index 0000000..c7fe576
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_01_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_01_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_01_light.png
new file mode 100644
index 0000000..0a5d6aa
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_01_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_02_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_02_dark.png
new file mode 100644
index 0000000..0aadfa3
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_02_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_02_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_02_light.png
new file mode 100644
index 0000000..125fe0b
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_02_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_03_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_03_dark.png
new file mode 100644
index 0000000..05c48a7
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_03_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_03_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_03_light.png
new file mode 100644
index 0000000..741e911
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_03_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_04_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_04_dark.png
new file mode 100644
index 0000000..ae4218a
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_04_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_04_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_04_light.png
new file mode 100644
index 0000000..8b30fab
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_04_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_05_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_05_dark.png
new file mode 100644
index 0000000..d7aa903
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_05_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_05_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_05_light.png
new file mode 100644
index 0000000..f7e2f29
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_05_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_06_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_06_dark.png
new file mode 100644
index 0000000..e7871e2
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_06_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_06_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_06_light.png
new file mode 100644
index 0000000..8c57f63
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_06_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_07_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_07_dark.png
new file mode 100644
index 0000000..0041b01
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_07_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_07_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_07_light.png
new file mode 100644
index 0000000..6dbb694
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_07_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_08_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_08_dark.png
new file mode 100644
index 0000000..08e1013
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_08_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_08_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_08_light.png
new file mode 100644
index 0000000..5c352c3
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_08_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_09_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_09_dark.png
new file mode 100644
index 0000000..70532e9
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_09_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_09_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_09_light.png
new file mode 100644
index 0000000..9c6ba30
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_09_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_10_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_10_dark.png
new file mode 100644
index 0000000..9ba3b5f
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_10_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_10_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_10_light.png
new file mode 100644
index 0000000..bd4bb22
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_10_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_11_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_11_dark.png
new file mode 100644
index 0000000..2156127
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_11_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_11_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_11_light.png
new file mode 100644
index 0000000..b417a9f
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_11_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_12_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_12_dark.png
new file mode 100644
index 0000000..9bf633e
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_12_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_12_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_12_light.png
new file mode 100644
index 0000000..ba51811
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_12_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_13_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_13_dark.png
new file mode 100644
index 0000000..756a53c
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_13_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_13_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_13_light.png
new file mode 100644
index 0000000..4705dca
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_13_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_14_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_14_dark.png
new file mode 100644
index 0000000..50e4ea3
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_14_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_14_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_14_light.png
new file mode 100644
index 0000000..bc6724f
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_14_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_15_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_15_dark.png
new file mode 100644
index 0000000..9e3b410
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_15_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_15_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_15_light.png
new file mode 100644
index 0000000..2f18abd
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_15_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_16_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_16_dark.png
new file mode 100644
index 0000000..de81133
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_16_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_16_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_16_light.png
new file mode 100644
index 0000000..b80b191
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_16_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_17_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_17_dark.png
new file mode 100644
index 0000000..48aba3d
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_17_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_17_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_17_light.png
new file mode 100644
index 0000000..ca34d5b
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_17_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_18_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_18_dark.png
new file mode 100644
index 0000000..e9957b3
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_18_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_18_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_18_light.png
new file mode 100644
index 0000000..a5d384f
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_18_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_19_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_19_dark.png
new file mode 100644
index 0000000..ddc6297
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_19_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_19_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_19_light.png
new file mode 100644
index 0000000..28ab684
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_19_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_20_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_20_dark.png
new file mode 100644
index 0000000..51e7f75
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_20_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_20_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_20_light.png
new file mode 100644
index 0000000..4aa3ca3
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_20_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_21_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_21_dark.png
new file mode 100644
index 0000000..9caecde
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_21_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_21_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_21_light.png
new file mode 100644
index 0000000..1b8d0b6
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_21_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_22_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_22_dark.png
new file mode 100644
index 0000000..400be3c
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_22_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_22_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_22_light.png
new file mode 100644
index 0000000..c14f1bf
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_22_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_23_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_23_dark.png
new file mode 100644
index 0000000..4e18b46
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_23_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_23_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_23_light.png
new file mode 100644
index 0000000..c4c2c00
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_23_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_24_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_24_dark.png
new file mode 100644
index 0000000..98fae44
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_24_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_24_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_24_light.png
new file mode 100644
index 0000000..d64c289
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_24_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_25_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_25_dark.png
new file mode 100644
index 0000000..91f9327
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_25_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_25_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_25_light.png
new file mode 100644
index 0000000..f5e1f69
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_25_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_26_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_26_dark.png
new file mode 100644
index 0000000..3e6fafd
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_26_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_26_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_26_light.png
new file mode 100644
index 0000000..ae2bd87
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_26_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_27_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_27_dark.png
new file mode 100644
index 0000000..f73a1f8
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_27_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_27_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_27_light.png
new file mode 100644
index 0000000..78c1069
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_27_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_28_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_28_dark.png
new file mode 100644
index 0000000..562b803
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_28_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_28_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_28_light.png
new file mode 100644
index 0000000..ddfba02
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_28_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_29_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_29_dark.png
new file mode 100644
index 0000000..257f2d2
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_29_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_29_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_29_light.png
new file mode 100644
index 0000000..38f5478
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_29_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_30_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_30_dark.png
new file mode 100644
index 0000000..f995af0
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_30_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_30_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_30_light.png
new file mode 100644
index 0000000..c50b7f0
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connected_30_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_00_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_00_dark.png
new file mode 100644
index 0000000..f6dd214
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_00_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_00_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_00_light.png
new file mode 100644
index 0000000..6b7bdcd
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_00_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_01_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_01_dark.png
new file mode 100644
index 0000000..c7fe576
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_01_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_01_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_01_light.png
new file mode 100644
index 0000000..0a5d6aa
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_01_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_02_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_02_dark.png
new file mode 100644
index 0000000..0aadfa3
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_02_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_02_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_02_light.png
new file mode 100644
index 0000000..125fe0b
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_02_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_03_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_03_dark.png
new file mode 100644
index 0000000..05c48a7
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_03_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_03_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_03_light.png
new file mode 100644
index 0000000..741e911
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_03_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_04_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_04_dark.png
new file mode 100644
index 0000000..ae4218a
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_04_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_04_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_04_light.png
new file mode 100644
index 0000000..8b30fab
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_04_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_05_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_05_dark.png
new file mode 100644
index 0000000..d7aa903
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_05_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_05_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_05_light.png
new file mode 100644
index 0000000..f7e2f29
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_05_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_06_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_06_dark.png
new file mode 100644
index 0000000..e7871e2
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_06_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_06_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_06_light.png
new file mode 100644
index 0000000..8c57f63
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_06_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_07_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_07_dark.png
new file mode 100644
index 0000000..0041b01
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_07_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_07_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_07_light.png
new file mode 100644
index 0000000..6dbb694
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_07_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_08_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_08_dark.png
new file mode 100644
index 0000000..08e1013
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_08_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_08_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_08_light.png
new file mode 100644
index 0000000..5c352c3
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_08_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_09_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_09_dark.png
new file mode 100644
index 0000000..70532e9
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_09_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_09_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_09_light.png
new file mode 100644
index 0000000..9c6ba30
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_09_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_10_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_10_dark.png
new file mode 100644
index 0000000..9ba3b5f
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_10_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_10_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_10_light.png
new file mode 100644
index 0000000..bd4bb22
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_10_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_11_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_11_dark.png
new file mode 100644
index 0000000..f3570f4
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_11_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_11_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_11_light.png
new file mode 100644
index 0000000..65a403e
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_11_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_12_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_12_dark.png
new file mode 100644
index 0000000..f644bfd
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_12_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_12_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_12_light.png
new file mode 100644
index 0000000..c7d6048
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_12_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_13_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_13_dark.png
new file mode 100644
index 0000000..6e0d558
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_13_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_13_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_13_light.png
new file mode 100644
index 0000000..f3bc48d
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_13_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_14_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_14_dark.png
new file mode 100644
index 0000000..14d8f8e
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_14_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_14_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_14_light.png
new file mode 100644
index 0000000..98b90e5
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_14_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_15_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_15_dark.png
new file mode 100644
index 0000000..83234a7
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_15_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_15_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_15_light.png
new file mode 100644
index 0000000..47d452f
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_15_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_16_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_16_dark.png
new file mode 100644
index 0000000..b81cf5a
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_16_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_16_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_16_light.png
new file mode 100644
index 0000000..20d08b4
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_16_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_17_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_17_dark.png
new file mode 100644
index 0000000..6feb3f1
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_17_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_17_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_17_light.png
new file mode 100644
index 0000000..e6ae8b3
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_17_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_18_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_18_dark.png
new file mode 100644
index 0000000..0b0fc08
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_18_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_18_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_18_light.png
new file mode 100644
index 0000000..c2a16ac
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_18_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_19_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_19_dark.png
new file mode 100644
index 0000000..a3598cc
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_19_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_19_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_19_light.png
new file mode 100644
index 0000000..846d16d
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_19_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_20_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_20_dark.png
new file mode 100644
index 0000000..2070455
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_20_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_20_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_20_light.png
new file mode 100644
index 0000000..ae6db13
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_20_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_21_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_21_dark.png
new file mode 100644
index 0000000..7f3828a
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_21_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_21_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_21_light.png
new file mode 100644
index 0000000..aaccc73
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_21_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_22_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_22_dark.png
new file mode 100644
index 0000000..5c8ced9
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_22_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_22_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_22_light.png
new file mode 100644
index 0000000..ad01b9e
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_22_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_23_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_23_dark.png
new file mode 100644
index 0000000..ce31dd3
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_23_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_23_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_23_light.png
new file mode 100644
index 0000000..9ef78e4
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_23_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_24_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_24_dark.png
new file mode 100644
index 0000000..a7c2cdb
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_24_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_24_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_24_light.png
new file mode 100644
index 0000000..e7c5bea
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_24_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_25_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_25_dark.png
new file mode 100644
index 0000000..ecad0d4
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_25_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_25_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_25_light.png
new file mode 100644
index 0000000..5fa5923
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_25_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_26_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_26_dark.png
new file mode 100644
index 0000000..f687e25
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_26_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_26_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_26_light.png
new file mode 100644
index 0000000..9c06db8
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_26_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_27_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_27_dark.png
new file mode 100644
index 0000000..90225ba
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_27_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_27_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_27_light.png
new file mode 100644
index 0000000..19697de
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_27_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_28_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_28_dark.png
new file mode 100644
index 0000000..d37ec21
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_28_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_28_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_28_light.png
new file mode 100644
index 0000000..21840bf
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_28_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_29_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_29_dark.png
new file mode 100644
index 0000000..5445e3a
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_29_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_29_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_29_light.png
new file mode 100644
index 0000000..2337c65
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_29_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_30_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_30_dark.png
new file mode 100644
index 0000000..f6dd214
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_30_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_30_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_30_light.png
new file mode 100644
index 0000000..6b7bdcd
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_connecting_30_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_disabled_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_disabled_dark.png
new file mode 100644
index 0000000..c4dc132
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_disabled_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_disabled_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_disabled_light.png
new file mode 100644
index 0000000..b14617c
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_disabled_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_disconnected_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_disconnected_dark.png
new file mode 100644
index 0000000..bb30773
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_disconnected_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_disconnected_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_disconnected_light.png
new file mode 100644
index 0000000..a05d7d7
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_disconnected_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_grey.png b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_grey.png
new file mode 100644
index 0000000..2238d58
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_mr_button_grey.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_vol_type_speaker_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_vol_type_speaker_dark.png
new file mode 100755
index 0000000..e40349d
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_vol_type_speaker_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_vol_type_speaker_group_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_vol_type_speaker_group_dark.png
new file mode 100755
index 0000000..f67c463
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_vol_type_speaker_group_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_vol_type_speaker_group_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_vol_type_speaker_group_light.png
new file mode 100755
index 0000000..7fcebf5
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_vol_type_speaker_group_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_vol_type_speaker_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_vol_type_speaker_light.png
new file mode 100755
index 0000000..ea32a7a
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_vol_type_speaker_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_vol_type_tv_dark.png b/packages/MediaComponents/res/drawable-xhdpi/ic_vol_type_tv_dark.png
new file mode 100755
index 0000000..d62ca37
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_vol_type_tv_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xhdpi/ic_vol_type_tv_light.png b/packages/MediaComponents/res/drawable-xhdpi/ic_vol_type_tv_light.png
new file mode 100755
index 0000000..3131256
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xhdpi/ic_vol_type_tv_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_audiotrack_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_audiotrack_dark.png
new file mode 100644
index 0000000..f131e1b
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_audiotrack_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_audiotrack_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_audiotrack_light.png
new file mode 100644
index 0000000..e5946a2
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_audiotrack_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_dialog_close_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_dialog_close_dark.png
new file mode 100644
index 0000000..b85e87f
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_dialog_close_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_dialog_close_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_dialog_close_light.png
new file mode 100644
index 0000000..51b4401
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_dialog_close_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_media_pause_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_media_pause_dark.png
new file mode 100644
index 0000000..3ea7e03
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_media_pause_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_media_pause_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_media_pause_light.png
new file mode 100644
index 0000000..dc63538
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_media_pause_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_media_play_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_media_play_dark.png
new file mode 100644
index 0000000..2745c3a
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_media_play_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_media_play_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_media_play_light.png
new file mode 100644
index 0000000..eda3ba5
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_media_play_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_media_stop_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_media_stop_dark.png
new file mode 100644
index 0000000..035ca18
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_media_stop_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_media_stop_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_media_stop_light.png
new file mode 100644
index 0000000..eac183d
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_media_stop_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_00_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_00_dark.png
new file mode 100644
index 0000000..0db679e
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_00_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_00_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_00_light.png
new file mode 100644
index 0000000..51c6051
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_00_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_01_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_01_dark.png
new file mode 100644
index 0000000..c083914
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_01_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_01_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_01_light.png
new file mode 100644
index 0000000..c3c3caf
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_01_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_02_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_02_dark.png
new file mode 100644
index 0000000..fc444cf
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_02_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_02_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_02_light.png
new file mode 100644
index 0000000..abd6377
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_02_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_03_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_03_dark.png
new file mode 100644
index 0000000..6dbd1da
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_03_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_03_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_03_light.png
new file mode 100644
index 0000000..d2e7108
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_03_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_04_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_04_dark.png
new file mode 100644
index 0000000..d9f596b
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_04_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_04_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_04_light.png
new file mode 100644
index 0000000..4f32e1a
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_04_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_05_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_05_dark.png
new file mode 100644
index 0000000..c568e04
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_05_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_05_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_05_light.png
new file mode 100644
index 0000000..ed20dd9
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_05_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_06_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_06_dark.png
new file mode 100644
index 0000000..bbe39e7
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_06_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_06_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_06_light.png
new file mode 100644
index 0000000..1edc15f
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_06_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_07_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_07_dark.png
new file mode 100644
index 0000000..78aebaf
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_07_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_07_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_07_light.png
new file mode 100644
index 0000000..b5a6a4f
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_07_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_08_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_08_dark.png
new file mode 100644
index 0000000..44b91ce
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_08_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_08_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_08_light.png
new file mode 100644
index 0000000..85f66f9
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_08_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_09_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_09_dark.png
new file mode 100644
index 0000000..51ea34b
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_09_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_09_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_09_light.png
new file mode 100644
index 0000000..952de04
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_09_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_10_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_10_dark.png
new file mode 100644
index 0000000..8b1aa21
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_10_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_10_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_10_light.png
new file mode 100644
index 0000000..534bcc0
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_10_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_11_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_11_dark.png
new file mode 100644
index 0000000..f666b35
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_11_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_11_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_11_light.png
new file mode 100644
index 0000000..145a8fb
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_11_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_12_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_12_dark.png
new file mode 100644
index 0000000..edeb132
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_12_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_12_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_12_light.png
new file mode 100644
index 0000000..9da2b60
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_12_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_13_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_13_dark.png
new file mode 100644
index 0000000..ab80aa9
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_13_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_13_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_13_light.png
new file mode 100644
index 0000000..115efe4
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_13_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_14_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_14_dark.png
new file mode 100644
index 0000000..8c0cc31
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_14_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_14_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_14_light.png
new file mode 100644
index 0000000..e6ae6fc
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_14_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_15_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_15_dark.png
new file mode 100644
index 0000000..b8816c9
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_15_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_15_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_15_light.png
new file mode 100644
index 0000000..bd42931
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_15_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_16_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_16_dark.png
new file mode 100644
index 0000000..10d5b7f
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_16_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_16_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_16_light.png
new file mode 100644
index 0000000..303a0fe
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_16_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_17_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_17_dark.png
new file mode 100644
index 0000000..3c2a655
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_17_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_17_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_17_light.png
new file mode 100644
index 0000000..90debc2
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_17_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_18_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_18_dark.png
new file mode 100644
index 0000000..d3e78a7
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_18_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_18_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_18_light.png
new file mode 100644
index 0000000..3a3f991
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_18_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_19_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_19_dark.png
new file mode 100644
index 0000000..63fad9e
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_19_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_19_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_19_light.png
new file mode 100644
index 0000000..d6dd8d4
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_19_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_20_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_20_dark.png
new file mode 100644
index 0000000..890fd5f
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_20_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_20_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_20_light.png
new file mode 100644
index 0000000..6b0b5c1
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_20_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_21_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_21_dark.png
new file mode 100644
index 0000000..9ce1ef1
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_21_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_21_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_21_light.png
new file mode 100644
index 0000000..81710d4
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_21_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_22_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_22_dark.png
new file mode 100644
index 0000000..861c080
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_22_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_22_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_22_light.png
new file mode 100644
index 0000000..1c4aa21
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_22_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_23_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_23_dark.png
new file mode 100644
index 0000000..59a6b30
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_23_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_23_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_23_light.png
new file mode 100644
index 0000000..c6e8fe0
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_23_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_24_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_24_dark.png
new file mode 100644
index 0000000..57b840e
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_24_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_24_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_24_light.png
new file mode 100644
index 0000000..bf24050
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_24_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_25_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_25_dark.png
new file mode 100644
index 0000000..01c18c1
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_25_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_25_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_25_light.png
new file mode 100644
index 0000000..be9753e
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_25_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_26_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_26_dark.png
new file mode 100644
index 0000000..3f291b1
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_26_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_26_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_26_light.png
new file mode 100644
index 0000000..dc1c619
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_26_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_27_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_27_dark.png
new file mode 100644
index 0000000..6504a70
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_27_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_27_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_27_light.png
new file mode 100644
index 0000000..a7e0a60
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_27_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_28_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_28_dark.png
new file mode 100644
index 0000000..57b1f3e
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_28_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_28_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_28_light.png
new file mode 100644
index 0000000..5c551ec
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_28_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_29_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_29_dark.png
new file mode 100644
index 0000000..238667e
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_29_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_29_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_29_light.png
new file mode 100644
index 0000000..ffb8183
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_29_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_30_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_30_dark.png
new file mode 100644
index 0000000..4893f18
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_30_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_30_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_30_light.png
new file mode 100644
index 0000000..ac5e156
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connected_30_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_00_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_00_dark.png
new file mode 100644
index 0000000..0db679e
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_00_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_00_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_00_light.png
new file mode 100644
index 0000000..51c6051
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_00_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_01_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_01_dark.png
new file mode 100644
index 0000000..c083914
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_01_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_01_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_01_light.png
new file mode 100644
index 0000000..c3c3caf
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_01_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_02_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_02_dark.png
new file mode 100644
index 0000000..fc444cf
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_02_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_02_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_02_light.png
new file mode 100644
index 0000000..abd6377
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_02_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_03_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_03_dark.png
new file mode 100644
index 0000000..6dbd1da
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_03_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_03_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_03_light.png
new file mode 100644
index 0000000..d2e7108
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_03_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_04_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_04_dark.png
new file mode 100644
index 0000000..d9f596b
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_04_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_04_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_04_light.png
new file mode 100644
index 0000000..4f32e1a
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_04_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_05_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_05_dark.png
new file mode 100644
index 0000000..c568e04
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_05_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_05_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_05_light.png
new file mode 100644
index 0000000..ed20dd9
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_05_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_06_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_06_dark.png
new file mode 100644
index 0000000..bbe39e7
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_06_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_06_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_06_light.png
new file mode 100644
index 0000000..1edc15f
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_06_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_07_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_07_dark.png
new file mode 100644
index 0000000..78aebaf
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_07_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_07_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_07_light.png
new file mode 100644
index 0000000..b5a6a4f
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_07_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_08_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_08_dark.png
new file mode 100644
index 0000000..44b91ce
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_08_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_08_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_08_light.png
new file mode 100644
index 0000000..85f66f9
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_08_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_09_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_09_dark.png
new file mode 100644
index 0000000..51ea34b
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_09_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_09_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_09_light.png
new file mode 100644
index 0000000..952de04
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_09_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_10_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_10_dark.png
new file mode 100644
index 0000000..8b1aa21
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_10_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_10_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_10_light.png
new file mode 100644
index 0000000..534bcc0
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_10_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_11_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_11_dark.png
new file mode 100644
index 0000000..1fffa01
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_11_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_11_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_11_light.png
new file mode 100644
index 0000000..0ff7e57
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_11_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_12_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_12_dark.png
new file mode 100644
index 0000000..06ac4dc
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_12_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_12_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_12_light.png
new file mode 100644
index 0000000..42a86f5
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_12_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_13_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_13_dark.png
new file mode 100644
index 0000000..0301090
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_13_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_13_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_13_light.png
new file mode 100644
index 0000000..4396f0e
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_13_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_14_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_14_dark.png
new file mode 100644
index 0000000..e19001b
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_14_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_14_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_14_light.png
new file mode 100644
index 0000000..2271581
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_14_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_15_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_15_dark.png
new file mode 100644
index 0000000..5e96208
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_15_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_15_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_15_light.png
new file mode 100644
index 0000000..0f69500
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_15_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_16_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_16_dark.png
new file mode 100644
index 0000000..07e1bd6
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_16_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_16_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_16_light.png
new file mode 100644
index 0000000..cde8f19
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_16_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_17_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_17_dark.png
new file mode 100644
index 0000000..b632e95
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_17_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_17_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_17_light.png
new file mode 100644
index 0000000..11d5d2e
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_17_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_18_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_18_dark.png
new file mode 100644
index 0000000..660d527
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_18_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_18_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_18_light.png
new file mode 100644
index 0000000..2761ae1
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_18_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_19_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_19_dark.png
new file mode 100644
index 0000000..0aa3f84
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_19_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_19_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_19_light.png
new file mode 100644
index 0000000..27d166f
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_19_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_20_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_20_dark.png
new file mode 100644
index 0000000..ebe527e
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_20_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_20_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_20_light.png
new file mode 100644
index 0000000..aeb2a8e
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_20_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_21_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_21_dark.png
new file mode 100644
index 0000000..7337af5
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_21_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_21_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_21_light.png
new file mode 100644
index 0000000..f3f31ef
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_21_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_22_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_22_dark.png
new file mode 100644
index 0000000..20d9f57
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_22_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_22_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_22_light.png
new file mode 100644
index 0000000..bf8eb77
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_22_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_23_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_23_dark.png
new file mode 100644
index 0000000..56a0e14
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_23_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_23_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_23_light.png
new file mode 100644
index 0000000..67425e1
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_23_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_24_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_24_dark.png
new file mode 100644
index 0000000..7c76e19
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_24_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_24_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_24_light.png
new file mode 100644
index 0000000..e02f1ed
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_24_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_25_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_25_dark.png
new file mode 100644
index 0000000..f5fdcdd
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_25_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_25_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_25_light.png
new file mode 100644
index 0000000..8ce9b819
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_25_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_26_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_26_dark.png
new file mode 100644
index 0000000..a29e443
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_26_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_26_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_26_light.png
new file mode 100644
index 0000000..349ca89
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_26_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_27_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_27_dark.png
new file mode 100644
index 0000000..0fc75d5
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_27_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_27_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_27_light.png
new file mode 100644
index 0000000..5cbd27c
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_27_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_28_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_28_dark.png
new file mode 100644
index 0000000..0ebb0ac
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_28_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_28_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_28_light.png
new file mode 100644
index 0000000..5b514aa
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_28_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_29_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_29_dark.png
new file mode 100644
index 0000000..8e7fe5c
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_29_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_29_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_29_light.png
new file mode 100644
index 0000000..efb2c10
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_29_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_30_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_30_dark.png
new file mode 100644
index 0000000..0db679e
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_30_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_30_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_30_light.png
new file mode 100644
index 0000000..51c6051
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_connecting_30_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_disabled_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_disabled_dark.png
new file mode 100644
index 0000000..fdb2121
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_disabled_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_disabled_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_disabled_light.png
new file mode 100644
index 0000000..9ce7e3a
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_disabled_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_disconnected_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_disconnected_dark.png
new file mode 100644
index 0000000..e8601ce
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_disconnected_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_disconnected_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_disconnected_light.png
new file mode 100644
index 0000000..34928d7
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_disconnected_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_grey.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_grey.png
new file mode 100644
index 0000000..792fd77
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_mr_button_grey.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_vol_type_speaker_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_vol_type_speaker_dark.png
new file mode 100755
index 0000000..f171a8c
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_vol_type_speaker_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_vol_type_speaker_group_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_vol_type_speaker_group_dark.png
new file mode 100755
index 0000000..c8cb6ca
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_vol_type_speaker_group_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_vol_type_speaker_group_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_vol_type_speaker_group_light.png
new file mode 100755
index 0000000..9c8863d
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_vol_type_speaker_group_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_vol_type_speaker_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_vol_type_speaker_light.png
new file mode 100755
index 0000000..9335038
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_vol_type_speaker_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_vol_type_tv_dark.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_vol_type_tv_dark.png
new file mode 100755
index 0000000..a6a4858
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_vol_type_tv_dark.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxhdpi/ic_vol_type_tv_light.png b/packages/MediaComponents/res/drawable-xxhdpi/ic_vol_type_tv_light.png
new file mode 100755
index 0000000..4ca6787
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxhdpi/ic_vol_type_tv_light.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_00.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_00.png
new file mode 100644
index 0000000..b2305d2
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_00.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_01.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_01.png
new file mode 100644
index 0000000..59395d4
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_01.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_02.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_02.png
new file mode 100644
index 0000000..70a7282
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_02.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_03.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_03.png
new file mode 100644
index 0000000..b3f0f53
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_03.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_04.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_04.png
new file mode 100644
index 0000000..66a80d9
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_04.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_05.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_05.png
new file mode 100644
index 0000000..8ec3939
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_05.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_06.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_06.png
new file mode 100644
index 0000000..0f02536
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_06.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_07.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_07.png
new file mode 100644
index 0000000..ba228f4
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_07.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_08.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_08.png
new file mode 100644
index 0000000..304277e
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_08.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_09.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_09.png
new file mode 100644
index 0000000..f865bfb
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_09.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_10.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_10.png
new file mode 100644
index 0000000..17c5d6b
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_10.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_11.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_11.png
new file mode 100644
index 0000000..a2f4ad5
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_11.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_12.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_12.png
new file mode 100644
index 0000000..c230648
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_12.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_13.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_13.png
new file mode 100644
index 0000000..b99324e
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_13.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_14.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_14.png
new file mode 100644
index 0000000..c8618f0
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_14.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_15.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_15.png
new file mode 100644
index 0000000..4a0d770
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_collapse_15.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_00.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_00.png
new file mode 100644
index 0000000..4a0d770
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_00.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_01.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_01.png
new file mode 100644
index 0000000..4db4e50
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_01.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_02.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_02.png
new file mode 100644
index 0000000..82b5f03
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_02.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_03.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_03.png
new file mode 100644
index 0000000..b05c758
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_03.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_04.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_04.png
new file mode 100644
index 0000000..fa5c7fa
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_04.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_05.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_05.png
new file mode 100644
index 0000000..2c287e4
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_05.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_06.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_06.png
new file mode 100644
index 0000000..eb7d0cf
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_06.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_07.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_07.png
new file mode 100644
index 0000000..95fa72b
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_07.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_08.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_08.png
new file mode 100644
index 0000000..5650eea
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_08.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_09.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_09.png
new file mode 100644
index 0000000..6f44355
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_09.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_10.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_10.png
new file mode 100644
index 0000000..4e877c3
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_10.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_11.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_11.png
new file mode 100644
index 0000000..7927f0a
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_11.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_12.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_12.png
new file mode 100644
index 0000000..71b19bb
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_12.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_13.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_13.png
new file mode 100644
index 0000000..bf5921e
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_13.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_14.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_14.png
new file mode 100644
index 0000000..14b76b1
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_14.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_15.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_15.png
new file mode 100644
index 0000000..b2305d2
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_group_expand_15.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-xxxhdpi/ic_mr_button_grey.png b/packages/MediaComponents/res/drawable-xxxhdpi/ic_mr_button_grey.png
new file mode 100644
index 0000000..04a9525
--- /dev/null
+++ b/packages/MediaComponents/res/drawable-xxxhdpi/ic_mr_button_grey.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable/ic_arrow_back.xml b/packages/MediaComponents/res/drawable/ic_arrow_back.xml
new file mode 100644
index 0000000..5aba8c6
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_arrow_back.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="40dp"
+ android:height="40dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"
+ android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_aspect_ratio.xml b/packages/MediaComponents/res/drawable/ic_aspect_ratio.xml
new file mode 100644
index 0000000..c6228e6
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_aspect_ratio.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M19,12h-2v3h-3v2h5v-5zM7,9h3L10,7L5,7v5h2L7,9zM21,3L3,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2L23,5c0,-1.1 -0.9,-2 -2,-2zM21,19.01L3,19.01L3,4.99h18v14.02z"
+ android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_chevron_left.xml b/packages/MediaComponents/res/drawable/ic_chevron_left.xml
new file mode 100644
index 0000000..8336d17
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_chevron_left.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M15.41,7.41L14,6l-6,6 6,6 1.41,-1.41L10.83,12z"
+ android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_chevron_right.xml b/packages/MediaComponents/res/drawable/ic_chevron_right.xml
new file mode 100644
index 0000000..fb2ce09
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_chevron_right.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M10,6L8.59,7.41 13.17,12l-4.58,4.59L10,18l6,-6z"
+ android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_forward_30.xml b/packages/MediaComponents/res/drawable/ic_forward_30.xml
new file mode 100644
index 0000000..7efdf16
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_forward_30.xml
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="40dp"
+ android:height="40dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <group>
+ <clip-path android:pathData="M24,24H0V0h24v24z M 0,0" />
+ <path
+ android:pathData="M9.6 13.5h.4c.2 0 .4,-.1.5,-.2s.2,-.2.2,-.4v-.2s-.1,-.1,-.1,-.2,-.1,-.1,-.2,-.1h-.5s-.1.1,-.2.1,-.1.1,-.1.2v.2h-1c0,-.2 0,-.3.1,-.5s.2,-.3.3,-.4.3,-.2.4,-.2.4,-.1.5,-.1c.2 0 .4 0 .6.1s.3.1.5.2.2.2.3.4.1.3.1.5v.3s-.1.2,-.1.3,-.1.2,-.2.2,-.2.1,-.3.2c.2.1.4.2.5.4s.2.4.2.6c0 .2 0 .4,-.1.5s-.2.3,-.3.4,-.3.2,-.5.2,-.4.1,-.6.1c-.2 0,-.4 0,-.5,-.1s-.3,-.1,-.5,-.2,-.2,-.2,-.3,-.4,-.1,-.4,-.1,-.6h.8v.2s.1.1.1.2.1.1.2.1h.5s.1,-.1.2,-.1.1,-.1.1,-.2v-.5s-.1,-.1,-.1,-.2,-.1,-.1,-.2,-.1h-.6v-.7zm5.7.7c0 .3 0 .6,-.1.8l-.3.6s-.3.3,-.5.3,-.4.1,-.6.1,-.4 0,-.6,-.1,-.3,-.2,-.5,-.3,-.2,-.3,-.3,-.6,-.1,-.5,-.1,-.8v-.7c0,-.3 0,-.6.1,-.8l.3,-.6s.3,-.3.5,-.3.4,-.1.6,-.1.4 0 .6.1.3.2.5.3.2.3.3.6.1.5.1.8v.7zm-.9,-.8v-.5s-.1,-.2,-.1,-.3,-.1,-.1,-.2,-.2,-.2,-.1,-.3,-.1,-.2 0,-.3.1l-.2.2s-.1.2,-.1.3v2s.1.2.1.3.1.1.2.2.2.1.3.1.2 0 .3,-.1l.2,-.2s.1,-.2.1,-.3v-1.5zM4 13c0 4.4 3.6 8 8 8s8,-3.6 8,-8h-2c0 3.3,-2.7 6,-6 6s-6,-2.7,-6,-6 2.7,-6 6,-6v4l5,-5,-5,-5v4c-4.4 0,-8 3.6,-8 8z"
+ android:fillColor="#FFFFFF"/>
+ </group>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_fullscreen.xml b/packages/MediaComponents/res/drawable/ic_fullscreen.xml
new file mode 100644
index 0000000..4b4f6bc
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_fullscreen.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M7,14L5,14v5h5v-2L7,17v-3zM5,10h2L7,7h3L10,5L5,5v5zM17,17h-3v2h5v-5h-2v3zM14,5v2h3v3h2L19,5h-5z"
+ android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_fullscreen_exit.xml b/packages/MediaComponents/res/drawable/ic_fullscreen_exit.xml
new file mode 100644
index 0000000..bc204e2
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_fullscreen_exit.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M5,16h3v3h2v-5L5,14v2zM8,8L5,8v2h5L10,5L8,5v3zM14,19h2v-3h3v-2h-5v5zM16,8L16,5h-2v5h5L19,8h-3z"
+ android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_mute.xml b/packages/MediaComponents/res/drawable/ic_mute.xml
new file mode 100644
index 0000000..560aaec
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_mute.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M16.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v2.21l2.45,2.45c0.03,-0.2 0.05,-0.41 0.05,-0.63zM19,12c0,0.94 -0.2,1.82 -0.54,2.64l1.51,1.51C20.63,14.91 21,13.5 21,12c0,-4.28 -2.99,-7.86 -7,-8.77v2.06c2.89,0.86 5,3.54 5,6.71zM4.27,3L3,4.27 7.73,9L3,9v6h4l5,5v-6.73l4.25,4.25c-0.67,0.52 -1.42,0.93 -2.25,1.18v2.06c1.38,-0.31 2.63,-0.95 3.69,-1.81L19.73,21 21,19.73l-9,-9L4.27,3zM12,4L9.91,6.09 12,8.18L12,4z"
+ android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_pause_circle_filled.xml b/packages/MediaComponents/res/drawable/ic_pause_circle_filled.xml
new file mode 100644
index 0000000..73be228
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_pause_circle_filled.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="40dp"
+ android:height="40dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM11,16L9,16L9,8h2v8zM15,16h-2L13,8h2v8z"
+ android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_play_circle_filled.xml b/packages/MediaComponents/res/drawable/ic_play_circle_filled.xml
new file mode 100644
index 0000000..9d39def
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_play_circle_filled.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="40dp"
+ android:height="40dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM10,16.5v-9l6,4.5 -6,4.5z"
+ android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_replay.xml b/packages/MediaComponents/res/drawable/ic_replay.xml
new file mode 100644
index 0000000..2bde120
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_replay.xml
@@ -0,0 +1,4 @@
+<vector android:height="40dp" android:viewportHeight="48.0"
+ android:viewportWidth="48.0" android:width="40dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="#FFFFFF" android:pathData="M24,10V2L14,12l10,10v-8c6.63,0 12,5.37 12,12s-5.37,12 -12,12 -12,-5.37 -12,-12H8c0,8.84 7.16,16 16,16s16,-7.16 16,-16 -7.16,-16 -16,-16z"/>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_rewind_10.xml b/packages/MediaComponents/res/drawable/ic_rewind_10.xml
new file mode 100644
index 0000000..ae586b4
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_rewind_10.xml
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="40dp"
+ android:height="40dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <group>
+ <clip-path android:pathData="M0,0h24v24H0V0z M 0,0" />
+ <path
+ android:pathData="M12 5V1L7 6l5 5V7c3.3 0 6 2.7 6 6s-2.7 6,-6 6,-6,-2.7,-6,-6H4c0 4.4 3.6 8 8 8s8,-3.6 8,-8,-3.6,-8,-8,-8zm-1.1 11H10v-3.3L9 13v-.7l1.8,-.6h.1V16zm4.3,-1.8c0 .3 0 .6,-.1.8l-.3.6s-.3.3,-.5.3,-.4.1,-.6.1,-.4 0,-.6,-.1,-.3,-.2,-.5,-.3,-.2,-.3,-.3,-.6,-.1,-.5,-.1,-.8v-.7c0,-.3 0,-.6.1,-.8l.3,-.6s.3,-.3.5,-.3.4,-.1.6,-.1.4 0 .6.1c.2.1.3.2.5.3s.2.3.3.6.1.5.1.8v.7zm-.9,-.8v-.5s-.1,-.2,-.1,-.3,-.1,-.1,-.2,-.2,-.2,-.1,-.3,-.1,-.2 0,-.3.1l-.2.2s-.1.2,-.1.3v2s.1.2.1.3.1.1.2.2.2.1.3.1.2 0 .3,-.1l.2,-.2s.1,-.2.1,-.3v-1.5z"
+ android:fillColor="#FFFFFF"/>
+ </group>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_settings.xml b/packages/MediaComponents/res/drawable/ic_settings.xml
new file mode 100644
index 0000000..a59ecc1
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_settings.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M19.43,12.98c0.04,-0.32 0.07,-0.64 0.07,-0.98s-0.03,-0.66 -0.07,-0.98l2.11,-1.65c0.19,-0.15 0.24,-0.42 0.12,-0.64l-2,-3.46c-0.12,-0.22 -0.39,-0.3 -0.61,-0.22l-2.49,1c-0.52,-0.4 -1.08,-0.73 -1.69,-0.98l-0.38,-2.65C14.46,2.18 14.25,2 14,2h-4c-0.25,0 -0.46,0.18 -0.49,0.42l-0.38,2.65c-0.61,0.25 -1.17,0.59 -1.69,0.98l-2.49,-1c-0.23,-0.09 -0.49,0 -0.61,0.22l-2,3.46c-0.13,0.22 -0.07,0.49 0.12,0.64l2.11,1.65c-0.04,0.32 -0.07,0.65 -0.07,0.98s0.03,0.66 0.07,0.98l-2.11,1.65c-0.19,0.15 -0.24,0.42 -0.12,0.64l2,3.46c0.12,0.22 0.39,0.3 0.61,0.22l2.49,-1c0.52,0.4 1.08,0.73 1.69,0.98l0.38,2.65c0.03,0.24 0.24,0.42 0.49,0.42h4c0.25,0 0.46,-0.18 0.49,-0.42l0.38,-2.65c0.61,-0.25 1.17,-0.59 1.69,-0.98l2.49,1c0.23,0.09 0.49,0 0.61,-0.22l2,-3.46c0.12,-0.22 0.07,-0.49 -0.12,-0.64l-2.11,-1.65zM12,15.5c-1.93,0 -3.5,-1.57 -3.5,-3.5s1.57,-3.5 3.5,-3.5 3.5,1.57 3.5,3.5 -1.57,3.5 -3.5,3.5z"
+ android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_skip_next.xml b/packages/MediaComponents/res/drawable/ic_skip_next.xml
new file mode 100644
index 0000000..b1f2812
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_skip_next.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="40dp"
+ android:height="40dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M6,18l8.5,-6L6,6v12zM16,6v12h2V6h-2z"
+ android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_skip_previous.xml b/packages/MediaComponents/res/drawable/ic_skip_previous.xml
new file mode 100644
index 0000000..81da314
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_skip_previous.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="40dp"
+ android:height="40dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M6,6h2v12L6,18zM9.5,12l8.5,6L18,6z"
+ android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_unmute.xml b/packages/MediaComponents/res/drawable/ic_unmute.xml
new file mode 100644
index 0000000..9dfb2b9
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_unmute.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M3,9v6h4l5,5L12,4L7,9L3,9zM16.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v8.05c1.48,-0.73 2.5,-2.25 2.5,-4.02zM14,3.23v2.06c2.89,0.86 5,3.54 5,6.71s-2.11,5.85 -5,6.71v2.06c4.01,-0.91 7,-4.49 7,-8.77s-2.99,-7.86 -7,-8.77z"
+ android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/mr_button_connected_dark.xml b/packages/MediaComponents/res/drawable/mr_button_connected_dark.xml
new file mode 100644
index 0000000..110ff13
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/mr_button_connected_dark.xml
@@ -0,0 +1,51 @@
+<?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.
+-->
+
+<animation-list
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:oneshot="true">
+ <item android:drawable="@drawable/ic_mr_button_connected_00_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_01_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_02_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_03_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_04_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_05_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_06_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_07_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_08_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_09_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_10_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_11_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_12_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_13_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_14_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_15_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_16_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_17_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_18_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_19_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_20_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_21_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_22_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_23_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_24_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_25_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_26_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_27_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_28_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_29_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_30_dark" android:duration="42" />
+</animation-list>
diff --git a/packages/MediaComponents/res/drawable/mr_button_connected_light.xml b/packages/MediaComponents/res/drawable/mr_button_connected_light.xml
new file mode 100644
index 0000000..bcfc7fe
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/mr_button_connected_light.xml
@@ -0,0 +1,51 @@
+<?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.
+-->
+
+<animation-list
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:oneshot="true">
+ <item android:drawable="@drawable/ic_mr_button_connected_00_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_01_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_02_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_03_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_04_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_05_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_06_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_07_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_08_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_09_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_10_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_11_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_12_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_13_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_14_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_15_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_16_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_17_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_18_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_19_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_20_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_21_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_22_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_23_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_24_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_25_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_26_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_27_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_28_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_29_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connected_30_light" android:duration="42" />
+</animation-list>
diff --git a/packages/MediaComponents/res/drawable/mr_button_connecting_dark.xml b/packages/MediaComponents/res/drawable/mr_button_connecting_dark.xml
new file mode 100644
index 0000000..55af7b3
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/mr_button_connecting_dark.xml
@@ -0,0 +1,51 @@
+<?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.
+-->
+
+<animation-list
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:oneshot="false">
+ <item android:drawable="@drawable/ic_mr_button_connecting_00_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_01_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_02_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_03_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_04_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_05_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_06_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_07_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_08_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_09_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_10_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_11_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_12_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_13_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_14_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_15_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_16_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_17_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_18_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_19_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_20_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_21_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_22_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_23_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_24_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_25_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_26_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_27_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_28_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_29_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_30_dark" android:duration="42" />
+</animation-list>
diff --git a/packages/MediaComponents/res/drawable/mr_button_connecting_light.xml b/packages/MediaComponents/res/drawable/mr_button_connecting_light.xml
new file mode 100644
index 0000000..93b4170
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/mr_button_connecting_light.xml
@@ -0,0 +1,51 @@
+<?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.
+-->
+
+<animation-list
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:oneshot="false">
+ <item android:drawable="@drawable/ic_mr_button_connecting_00_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_01_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_02_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_03_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_04_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_05_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_06_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_07_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_08_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_09_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_10_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_11_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_12_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_13_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_14_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_15_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_16_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_17_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_18_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_19_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_20_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_21_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_22_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_23_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_24_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_25_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_26_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_27_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_28_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_29_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_mr_button_connecting_30_light" android:duration="42" />
+</animation-list>
diff --git a/packages/MediaComponents/res/drawable/mr_button_dark.xml b/packages/MediaComponents/res/drawable/mr_button_dark.xml
new file mode 100644
index 0000000..8f1dfaa
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/mr_button_dark.xml
@@ -0,0 +1,25 @@
+<?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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_checked="true" android:state_enabled="true"
+ android:drawable="@drawable/mr_button_connected_dark" />
+ <item android:state_checkable="true" android:state_enabled="true"
+ android:drawable="@drawable/mr_button_connecting_dark" />
+ <item android:state_enabled="true"
+ android:drawable="@drawable/ic_mr_button_disconnected_dark" />
+ <item android:drawable="@drawable/ic_mr_button_disabled_dark" />
+</selector>
diff --git a/packages/MediaComponents/res/drawable/mr_button_light.xml b/packages/MediaComponents/res/drawable/mr_button_light.xml
new file mode 100644
index 0000000..1d3d84e
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/mr_button_light.xml
@@ -0,0 +1,25 @@
+<?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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_checked="true" android:state_enabled="true"
+ android:drawable="@drawable/mr_button_connected_light" />
+ <item android:state_checkable="true" android:state_enabled="true"
+ android:drawable="@drawable/mr_button_connecting_light" />
+ <item android:state_enabled="true"
+ android:drawable="@drawable/ic_mr_button_disconnected_light" />
+ <item android:drawable="@drawable/ic_mr_button_disabled_light" />
+</selector>
diff --git a/packages/MediaComponents/res/drawable/mr_dialog_close_dark.xml b/packages/MediaComponents/res/drawable/mr_dialog_close_dark.xml
new file mode 100644
index 0000000..288c8c7
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/mr_dialog_close_dark.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:drawable="@drawable/ic_dialog_close_dark" />
+</selector>
diff --git a/packages/MediaComponents/res/drawable/mr_dialog_close_light.xml b/packages/MediaComponents/res/drawable/mr_dialog_close_light.xml
new file mode 100644
index 0000000..cd50e0f
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/mr_dialog_close_light.xml
@@ -0,0 +1,24 @@
+<?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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <bitmap
+ android:src="@drawable/ic_dialog_close_light"
+ android:alpha="0.87" />
+ </item>
+
+</selector>
diff --git a/packages/MediaComponents/res/drawable/mr_dialog_material_background_dark.xml b/packages/MediaComponents/res/drawable/mr_dialog_material_background_dark.xml
new file mode 100644
index 0000000..ebc7eca
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/mr_dialog_material_background_dark.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+
+<!-- This is the copy of @drawable/abc_dialog_material_background_dark except for inset
+ which includes unnecessary padding. -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <corners android:radius="2dp" />
+ <solid android:color="@color/background_floating_material_dark" />
+</shape>
\ No newline at end of file
diff --git a/packages/MediaComponents/res/drawable/mr_dialog_material_background_light.xml b/packages/MediaComponents/res/drawable/mr_dialog_material_background_light.xml
new file mode 100644
index 0000000..c1b235a
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/mr_dialog_material_background_light.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+
+<!-- This is the copy of @drawable/abc_dialog_material_background_light except for inset
+ which includes unnecessary padding. -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <corners android:radius="2dp" />
+ <solid android:color="@color/background_floating_material_light" />
+</shape>
\ No newline at end of file
diff --git a/packages/MediaComponents/res/drawable/mr_group_collapse.xml b/packages/MediaComponents/res/drawable/mr_group_collapse.xml
new file mode 100644
index 0000000..8f72bc8
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/mr_group_collapse.xml
@@ -0,0 +1,35 @@
+<?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.
+-->
+
+<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
+ android:oneshot="true">
+ <item android:drawable="@drawable/ic_group_collapse_00" android:duration="13" />
+ <item android:drawable="@drawable/ic_group_collapse_01" android:duration="13" />
+ <item android:drawable="@drawable/ic_group_collapse_02" android:duration="13" />
+ <item android:drawable="@drawable/ic_group_collapse_03" android:duration="13" />
+ <item android:drawable="@drawable/ic_group_collapse_04" android:duration="13" />
+ <item android:drawable="@drawable/ic_group_collapse_05" android:duration="13" />
+ <item android:drawable="@drawable/ic_group_collapse_06" android:duration="13" />
+ <item android:drawable="@drawable/ic_group_collapse_07" android:duration="13" />
+ <item android:drawable="@drawable/ic_group_collapse_08" android:duration="13" />
+ <item android:drawable="@drawable/ic_group_collapse_09" android:duration="13" />
+ <item android:drawable="@drawable/ic_group_collapse_10" android:duration="13" />
+ <item android:drawable="@drawable/ic_group_collapse_11" android:duration="13" />
+ <item android:drawable="@drawable/ic_group_collapse_12" android:duration="13" />
+ <item android:drawable="@drawable/ic_group_collapse_13" android:duration="13" />
+ <item android:drawable="@drawable/ic_group_collapse_14" android:duration="13" />
+ <item android:drawable="@drawable/ic_group_collapse_15" android:duration="13" />
+</animation-list>
diff --git a/packages/MediaComponents/res/drawable/mr_group_expand.xml b/packages/MediaComponents/res/drawable/mr_group_expand.xml
new file mode 100644
index 0000000..6b3fdb6
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/mr_group_expand.xml
@@ -0,0 +1,35 @@
+<?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.
+-->
+
+<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
+ android:oneshot="true">
+ <item android:drawable="@drawable/ic_group_expand_00" android:duration="13" />
+ <item android:drawable="@drawable/ic_group_expand_01" android:duration="13" />
+ <item android:drawable="@drawable/ic_group_expand_02" android:duration="13" />
+ <item android:drawable="@drawable/ic_group_expand_03" android:duration="13" />
+ <item android:drawable="@drawable/ic_group_expand_04" android:duration="13" />
+ <item android:drawable="@drawable/ic_group_expand_05" android:duration="13" />
+ <item android:drawable="@drawable/ic_group_expand_06" android:duration="13" />
+ <item android:drawable="@drawable/ic_group_expand_07" android:duration="13" />
+ <item android:drawable="@drawable/ic_group_expand_08" android:duration="13" />
+ <item android:drawable="@drawable/ic_group_expand_09" android:duration="13" />
+ <item android:drawable="@drawable/ic_group_expand_10" android:duration="13" />
+ <item android:drawable="@drawable/ic_group_expand_11" android:duration="13" />
+ <item android:drawable="@drawable/ic_group_expand_12" android:duration="13" />
+ <item android:drawable="@drawable/ic_group_expand_13" android:duration="13" />
+ <item android:drawable="@drawable/ic_group_expand_14" android:duration="13" />
+ <item android:drawable="@drawable/ic_group_expand_15" android:duration="13" />
+</animation-list>
diff --git a/packages/MediaComponents/res/drawable/mr_media_pause_dark.xml b/packages/MediaComponents/res/drawable/mr_media_pause_dark.xml
new file mode 100644
index 0000000..86218a7
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/mr_media_pause_dark.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:drawable="@drawable/ic_media_pause_dark" />
+</selector>
diff --git a/packages/MediaComponents/res/drawable/mr_media_pause_light.xml b/packages/MediaComponents/res/drawable/mr_media_pause_light.xml
new file mode 100644
index 0000000..2dd1f02
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/mr_media_pause_light.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <bitmap
+ android:src="@drawable/ic_media_pause_light"
+ android:alpha="0.87" />
+ </item>
+</selector>
diff --git a/packages/MediaComponents/res/drawable/mr_media_play_dark.xml b/packages/MediaComponents/res/drawable/mr_media_play_dark.xml
new file mode 100644
index 0000000..9d45a33
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/mr_media_play_dark.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:drawable="@drawable/ic_media_play_dark" />
+</selector>
diff --git a/packages/MediaComponents/res/drawable/mr_media_play_light.xml b/packages/MediaComponents/res/drawable/mr_media_play_light.xml
new file mode 100644
index 0000000..f1fb7a6
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/mr_media_play_light.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <bitmap
+ android:src="@drawable/ic_media_play_light"
+ android:alpha="0.87" />
+ </item>
+</selector>
diff --git a/packages/MediaComponents/res/drawable/mr_media_stop_dark.xml b/packages/MediaComponents/res/drawable/mr_media_stop_dark.xml
new file mode 100644
index 0000000..3e108a9
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/mr_media_stop_dark.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:drawable="@drawable/ic_media_stop_dark" />
+</selector>
diff --git a/packages/MediaComponents/res/drawable/mr_media_stop_light.xml b/packages/MediaComponents/res/drawable/mr_media_stop_light.xml
new file mode 100644
index 0000000..b2c6ce8
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/mr_media_stop_light.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <bitmap
+ android:src="@drawable/ic_media_stop_light"
+ android:alpha="0.87" />
+ </item>
+</selector>
diff --git a/packages/MediaComponents/res/drawable/mr_vol_type_audiotrack_dark.xml b/packages/MediaComponents/res/drawable/mr_vol_type_audiotrack_dark.xml
new file mode 100644
index 0000000..44f4fd6
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/mr_vol_type_audiotrack_dark.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:drawable="@drawable/ic_audiotrack_dark" />
+</selector>
diff --git a/packages/MediaComponents/res/drawable/mr_vol_type_audiotrack_light.xml b/packages/MediaComponents/res/drawable/mr_vol_type_audiotrack_light.xml
new file mode 100644
index 0000000..5c9dbc0
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/mr_vol_type_audiotrack_light.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <bitmap
+ android:src="@drawable/ic_audiotrack_light"
+ android:alpha="0.87" />
+ </item>
+</selector>
diff --git a/packages/MediaComponents/res/interpolator/mr_fast_out_slow_in.xml b/packages/MediaComponents/res/interpolator/mr_fast_out_slow_in.xml
new file mode 100644
index 0000000..6b6a171
--- /dev/null
+++ b/packages/MediaComponents/res/interpolator/mr_fast_out_slow_in.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
+ -->
+
+<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+ android:controlX1="0.4"
+ android:controlY1="0"
+ android:controlX2="0.2"
+ android:controlY2="1"/>
diff --git a/packages/MediaComponents/res/interpolator/mr_linear_out_slow_in.xml b/packages/MediaComponents/res/interpolator/mr_linear_out_slow_in.xml
new file mode 100644
index 0000000..20bf298
--- /dev/null
+++ b/packages/MediaComponents/res/interpolator/mr_linear_out_slow_in.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
+ -->
+
+<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+ android:controlX1="0"
+ android:controlY1="0"
+ android:controlX2="0.2"
+ android:controlY2="1"/>
diff --git a/packages/MediaComponents/res/layout/media_controller.xml b/packages/MediaComponents/res/layout/media_controller.xml
new file mode 100644
index 0000000..4d05546
--- /dev/null
+++ b/packages/MediaComponents/res/layout/media_controller.xml
@@ -0,0 +1,158 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="#55000000"
+ android:orientation="vertical"
+ android:layoutDirection="ltr">
+
+ <RelativeLayout
+ android:id="@+id/title_bar"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <RadioButton
+ android:id="@+id/back"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentStart="true"
+ android:layout_centerVertical="true"
+ android:checked="true"
+ android:visibility="gone"/>
+
+ <TextView
+ android:id="@+id/title_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_toRightOf="@id/back"
+ android:layout_centerVertical="true"
+ android:layout_marginLeft="15dp"
+ android:paddingTop="4dp"
+ android:paddingStart="4dp"
+ android:paddingEnd="4dp"
+ android:textSize="20sp"
+ android:textColor="#FFFFFFFF" />
+
+ <view class="com.android.support.mediarouter.app.MediaRouteButton" android:id="@+id/cast"
+ android:layout_alignParentEnd="true"
+ android:layout_centerVertical="true"
+ style="@style/TitleBarButton" />
+
+ </RelativeLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:gravity="center"
+ android:paddingTop="4dp"
+ android:orientation="horizontal">
+
+ <ImageButton android:id="@+id/prev" style="@style/TransportControlsButton.Previous" />
+ <ImageButton android:id="@+id/rew" style="@style/TransportControlsButton.Rew" />
+ <ImageButton android:id="@+id/pause" style="@style/TransportControlsButton.Pause" />
+ <ImageButton android:id="@+id/ffwd" style="@style/TransportControlsButton.Ffwd" />
+ <ImageButton android:id="@+id/next" style="@style/TransportControlsButton.Next" />
+
+ </LinearLayout>
+
+ <SeekBar
+ android:id="@+id/mediacontroller_progress"
+ android:layout_width="match_parent"
+ android:layout_height="32dp"
+ android:padding="0dp"
+ android:progressTint="#FFFFFFFF"
+ android:thumbTint="#FFFFFFFF"/>
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingLeft="15dp"
+ android:orientation="horizontal">
+
+ <TextView
+ android:id="@+id/time_current"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ android:layout_alignParentStart="true"
+ android:paddingEnd="4dp"
+ android:paddingStart="4dp"
+ android:textSize="14sp"
+ android:textStyle="bold"
+ android:textColor="#FFFFFF" />
+
+ <TextView
+ android:id="@+id/time"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ android:layout_toRightOf="@id/time_current"
+ android:paddingStart="4dp"
+ android:paddingEnd="4dp"
+ android:textSize="14sp"
+ android:textStyle="bold"
+ android:textColor="#BBBBBB" />
+
+ <LinearLayout
+ android:id="@+id/basic_controls"
+ android:layout_alignParentRight="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ android:orientation="horizontal" >
+
+ <ImageButton
+ android:id="@+id/subtitle"
+ android:scaleType="fitCenter"
+ style="@style/BottomBarButton.CC" />
+ <ImageButton
+ android:id="@+id/fullscreen"
+ style="@style/BottomBarButton.FullScreen"/>
+ <ImageButton
+ android:id="@+id/overflow_right"
+ style="@style/BottomBarButton.OverflowRight"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/extra_controls"
+ android:layout_alignParentRight="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ android:visibility="gone"
+ android:orientation="horizontal" >
+
+ <ImageButton
+ android:id="@+id/mute"
+ style="@style/BottomBarButton.Mute" />
+ <ImageButton
+ android:id="@+id/aspect_ratio"
+ style="@style/BottomBarButton.AspectRatio" />
+ <ImageButton
+ android:id="@+id/settings"
+ style="@style/BottomBarButton.Settings" />
+ <ImageButton
+ android:id="@+id/overflow_left"
+ style="@style/BottomBarButton.OverflowLeft"/>
+ </LinearLayout>
+
+ </RelativeLayout>
+
+</LinearLayout>
+
diff --git a/packages/MediaComponents/res/layout/mr_chooser_dialog.xml b/packages/MediaComponents/res/layout/mr_chooser_dialog.xml
new file mode 100644
index 0000000..ee89e16
--- /dev/null
+++ b/packages/MediaComponents/res/layout/mr_chooser_dialog.xml
@@ -0,0 +1,55 @@
+<?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="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <TextView android:id="@+id/mr_chooser_title"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:paddingLeft="24dp"
+ android:paddingRight="24dp"
+ android:paddingTop="24dp"
+ android:text="@string/mr_chooser_title"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:textAppearance="@style/TextAppearance.MediaRouter.Title" />
+ <ListView android:id="@+id/mr_chooser_list"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:divider="@android:color/transparent"
+ android:dividerHeight="0dp" />
+ <LinearLayout android:id="@android:id/empty"
+ android:layout_width="fill_parent"
+ android:layout_height="240dp"
+ android:orientation="vertical"
+ android:paddingTop="90dp"
+ android:paddingLeft="16dp"
+ android:paddingRight="16dp"
+ android:visibility="gone">
+ <TextView android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:text="@string/mr_chooser_searching"
+ android:textAppearance="@style/TextAppearance.MediaRouter.SecondaryText" />
+ <ProgressBar android:layout_width="150dp"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:indeterminate="true"
+ style="?android:attr/progressBarStyleHorizontal" />
+ </LinearLayout>
+</LinearLayout>
diff --git a/packages/MediaComponents/res/layout/mr_chooser_list_item.xml b/packages/MediaComponents/res/layout/mr_chooser_list_item.xml
new file mode 100644
index 0000000..958879b
--- /dev/null
+++ b/packages/MediaComponents/res/layout/mr_chooser_list_item.xml
@@ -0,0 +1,51 @@
+<?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="fill_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="56dp"
+ android:paddingLeft="24dp"
+ android:paddingRight="24dp"
+ android:orientation="horizontal"
+ android:gravity="center_vertical" >
+
+ <ImageView android:id="@+id/mr_chooser_route_icon"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_marginRight="24dp" />
+
+ <LinearLayout android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="1dp"
+ android:orientation="vertical" >
+
+ <TextView android:id="@+id/mr_chooser_route_name"
+ android:layout_width="fill_parent"
+ android:layout_height="32dp"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:textAppearance="@style/TextAppearance.MediaRouter.PrimaryText" />
+
+ <TextView android:id="@+id/mr_chooser_route_desc"
+ android:layout_width="fill_parent"
+ android:layout_height="24dp"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:textAppearance="@style/TextAppearance.MediaRouter.SecondaryText" />
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/packages/MediaComponents/res/layout/mr_controller_material_dialog_b.xml b/packages/MediaComponents/res/layout/mr_controller_material_dialog_b.xml
new file mode 100644
index 0000000..3751002
--- /dev/null
+++ b/packages/MediaComponents/res/layout/mr_controller_material_dialog_b.xml
@@ -0,0 +1,206 @@
+<?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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/mr_expandable_area"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+ <LinearLayout android:id="@+id/mr_dialog_area"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:orientation="vertical"
+ android:background="?attr/colorBackgroundFloating">
+ <LinearLayout android:id="@+id/mr_title_bar"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:paddingLeft="24dp"
+ android:paddingRight="12dp"
+ android:orientation="horizontal" >
+ <TextView android:id="@+id/mr_name"
+ android:layout_width="0dp"
+ android:layout_height="72dp"
+ android:layout_weight="1"
+ android:gravity="center_vertical"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:textAppearance="@style/TextAppearance.MediaRouter.Title" />
+ <ImageButton android:id="@+id/mr_close"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_gravity="center_vertical"
+ android:contentDescription="@string/mr_controller_close_description"
+ android:src="?attr/mediaRouteCloseDrawable"
+ android:background="?attr/selectableItemBackgroundBorderless" />
+ </LinearLayout>
+ <FrameLayout android:id="@+id/mr_custom_control"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:visibility="gone" />
+ <FrameLayout android:id="@+id/mr_default_control"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+ <ImageView android:id="@+id/mr_art"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:adjustViewBounds="true"
+ android:scaleType="fitXY"
+ android:background="?attr/colorPrimary"
+ android:layout_gravity="top"
+ android:contentDescription="@string/mr_controller_album_art"
+ android:visibility="gone" />
+ <LinearLayout android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:layout_gravity="bottom"
+ android:splitMotionEvents="false">
+ <LinearLayout android:id="@+id/mr_media_main_control"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingTop="16dp"
+ android:paddingBottom="16dp"
+ android:layout_gravity="bottom"
+ android:theme="?attr/mediaRouteControlPanelThemeOverlay">
+ <RelativeLayout
+ android:id="@+id/mr_playback_control"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:paddingLeft="24dp"
+ android:paddingRight="12dp" >
+ <ImageButton android:id="@+id/mr_control_playback_ctrl"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="12dp"
+ android:layout_alignParentRight="true"
+ android:layout_centerVertical="true"
+ android:contentDescription="@string/mr_controller_play"
+ android:background="?attr/selectableItemBackgroundBorderless"
+ android:visibility="gone" />
+ <LinearLayout android:id="@+id/mr_control_title_container"
+ android:orientation="vertical"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_toLeftOf="@id/mr_control_playback_ctrl"
+ android:layout_alignParentLeft="true"
+ android:layout_centerVertical="true">
+ <TextView android:id="@+id/mr_control_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/TextAppearance.MediaRouter.PrimaryText"
+ android:singleLine="true" />
+ <TextView android:id="@+id/mr_control_subtitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/TextAppearance.MediaRouter.SecondaryText"
+ android:singleLine="true" />
+ </LinearLayout>
+ </RelativeLayout>
+ <View android:id="@+id/mr_control_divider"
+ android:layout_width="fill_parent"
+ android:layout_height="8dp"
+ android:visibility="gone" />
+ <LinearLayout
+ android:id="@+id/mr_volume_control"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="48dp"
+ android:gravity="center_vertical"
+ android:paddingLeft="24dp"
+ android:paddingRight="12dp"
+ android:splitMotionEvents="false">
+ <ImageView
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:src="?attr/mediaRouteAudioTrackDrawable"
+ android:gravity="center"
+ android:scaleType="center"/>
+ <!-- Since dialog's top layout mr_expandable_area is clickable, it propagates pressed state
+ to its non-clickable children. Specify android:clickable="true" to prevent volume slider
+ from having false pressed state. -->
+ <com.android.support.mediarouter.app.MediaRouteVolumeSlider
+ android:id="@+id/mr_volume_slider"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:minHeight="48dp"
+ android:maxHeight="48dp"
+ android:layout_weight="1"
+ android:clickable="true"
+ android:contentDescription="@string/mr_controller_volume_slider" />
+ <com.android.support.mediarouter.app.MediaRouteExpandCollapseButton
+ android:id="@+id/mr_group_expand_collapse"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:padding="12dp"
+ android:background="?attr/selectableItemBackgroundBorderless"
+ android:visibility="gone"/>
+ </LinearLayout>
+ </LinearLayout>
+ <com.android.support.mediarouter.app.OverlayListView
+ android:id="@+id/mr_volume_group_list"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="@dimen/mr_controller_volume_group_list_padding_top"
+ android:scrollbarStyle="outsideOverlay"
+ android:clipToPadding="false"
+ android:visibility="gone"
+ android:splitMotionEvents="false"
+ android:theme="?attr/mediaRouteControlPanelThemeOverlay" />
+ </LinearLayout>
+ </FrameLayout>
+ <ScrollView
+ android:id="@+id/buttonPanel"
+ style="?attr/buttonBarStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:fillViewport="true"
+ android:scrollIndicators="top|bottom">
+ <android.support.v7.widget.ButtonBarLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="bottom"
+ android:layoutDirection="locale"
+ android:orientation="horizontal"
+ android:paddingBottom="4dp"
+ android:paddingLeft="12dp"
+ android:paddingRight="12dp"
+ android:paddingTop="4dp">
+ <Button
+ android:id="@android:id/button3"
+ style="?attr/buttonBarNeutralButtonStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ <android.support.v4.widget.Space
+ android:id="@+id/spacer"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:visibility="invisible"/>
+ <Button
+ android:id="@android:id/button2"
+ style="?android:attr/buttonBarNegativeButtonStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ <Button
+ android:id="@android:id/button1"
+ style="?attr/buttonBarPositiveButtonStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ </android.support.v7.widget.ButtonBarLayout>
+ </ScrollView>
+ </LinearLayout>
+</FrameLayout>
diff --git a/packages/MediaComponents/res/layout/mr_controller_volume_item.xml b/packages/MediaComponents/res/layout/mr_controller_volume_item.xml
new file mode 100644
index 0000000..a89058b
--- /dev/null
+++ b/packages/MediaComponents/res/layout/mr_controller_volume_item.xml
@@ -0,0 +1,52 @@
+<?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="fill_parent"
+ android:layout_height="wrap_content">
+ <LinearLayout android:id="@+id/volume_item_container"
+ android:layout_width="fill_parent"
+ android:layout_height="@dimen/mr_controller_volume_group_list_item_height"
+ android:paddingLeft="24dp"
+ android:paddingRight="60dp"
+ android:paddingBottom="8dp"
+ android:orientation="vertical" >
+ <TextView android:id="@+id/mr_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/TextAppearance.MediaRouter.SecondaryText"
+ android:singleLine="true" />
+ <LinearLayout android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:gravity="center_vertical">
+ <ImageView android:id="@+id/mr_volume_item_icon"
+ android:layout_width="@dimen/mr_controller_volume_group_list_item_icon_size"
+ android:layout_height="@dimen/mr_controller_volume_group_list_item_icon_size"
+ android:layout_marginTop="8dp"
+ android:layout_marginBottom="8dp"
+ android:scaleType="fitCenter"
+ android:src="?attr/mediaRouteAudioTrackDrawable" />
+ <android.support.v7.app.MediaRouteVolumeSlider
+ android:id="@+id/mr_volume_slider"
+ android:layout_width="fill_parent"
+ android:layout_height="40dp"
+ android:minHeight="40dp"
+ android:maxHeight="40dp"
+ android:contentDescription="@string/mr_controller_volume_slider" />
+ </LinearLayout>
+ </LinearLayout>
+</LinearLayout>
diff --git a/packages/MediaComponents/res/values-af/strings.xml b/packages/MediaComponents/res/values-af/strings.xml
new file mode 100644
index 0000000..47230ad
--- /dev/null
+++ b/packages/MediaComponents/res/values-af/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"Stelsel"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Toestelle"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Cast-knoppie"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Uitsaai-knoppie. Ontkoppel"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Uitsaai-knoppie. Koppel tans"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Uitsaai-knoppie. Gekoppel"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Saai uit na"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Vind tans toestelle"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Ontkoppel"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Hou op uitsaai"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Maak toe"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Speel"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Laat wag"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Stop"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Vou uit"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Vou in"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Albumkunswerk"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Volumeglyer"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Geen media is gekies nie"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Geen inligting beskikbaar nie"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Saai tans skerm uit"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-am/strings.xml b/packages/MediaComponents/res/values-am/strings.xml
new file mode 100644
index 0000000..39a1903
--- /dev/null
+++ b/packages/MediaComponents/res/values-am/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"ስርዓት"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"መሣሪያዎች"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"የCast አዝራር"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Cast አዝራር። ግንኙነት ተቋርጧል"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Cast አዝራር በማገናኘት ላይ"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Cast አዝራር። ተገናኝቷል"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Cast አድርግ ወደ"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"መሣሪያዎችን በማግኘት ላይ"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"ግንኙነት አቋርጥ"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Cast ማድረግ አቁም"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"ዝጋ"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"አጫውት"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"ለአፍታ አቁም"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"አቁም"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"አስፋ"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"ሰብስብ"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"የአልበም ስነ-ጥበብ"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"ተንሸራታች የድምፅ መቆጣጠሪያ"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"ምንም ማህደረመረጃ አልተመረጠም"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"ምንም መረጃ አይገኝም"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"ማያ ገጽን በመውሰድ ላይ"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-ar/strings.xml b/packages/MediaComponents/res/values-ar/strings.xml
new file mode 100644
index 0000000..f8fb97d
--- /dev/null
+++ b/packages/MediaComponents/res/values-ar/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"النظام"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"الأجهزة"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"زر الإرسال"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"زر الإرسال. تم قطع الاتصال"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"زر الإرسال. جارٍ الاتصال"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"زر الإرسال. تم الاتصال"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"إرسال إلى"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"جارٍ البحث عن أجهزة"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"قطع الاتصال"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"إيقاف الإرسال"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"إغلاق"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"تشغيل"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"إيقاف مؤقت"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"إيقاف"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"توسيع"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"تصغير"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"صورة الألبوم"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"شريط تمرير مستوى الصوت"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"لم يتم اختيار أي وسائط"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"لا تتوفر أي معلومات"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"جارٍ إرسال الشاشة"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-az/strings.xml b/packages/MediaComponents/res/values-az/strings.xml
new file mode 100644
index 0000000..a3c60ab
--- /dev/null
+++ b/packages/MediaComponents/res/values-az/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"Sistem"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Cihazlar"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Yayım düyməsi"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Yayım düyməsi. Bağlantı kəsildi"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Yayım düyməsi. Qoşulur"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Yayım düyməsi. Qoşuldu"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Bura yayımlayın"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Cihazlar axtarılır"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Bağlantını kəsin"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Yayımı dayandırın"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Qapadın"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Oynadın"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Durdurun"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Dayandırın"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Genişləndirin"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Yığcamlaşdırın"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Albom incəsənəti"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Səs hərmi diyircəyi"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Heç bir media seçilməyib"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Əlçatan məlumat yoxdur"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Ekran yayımlanır"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-b+sr+Latn/strings.xml b/packages/MediaComponents/res/values-b+sr+Latn/strings.xml
new file mode 100644
index 0000000..e25bd6e
--- /dev/null
+++ b/packages/MediaComponents/res/values-b+sr+Latn/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"Sistem"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Uređaji"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Dugme Prebaci"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Dugme Prebaci. Veza je prekinuta"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Dugme Prebaci. Povezuje se"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Dugme Prebaci. Povezan je"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Prebacuj na"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Pronalaženje uređaja"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Prekini vezu"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Zaustavi prebacivanje"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Zatvori"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Pusti"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Pauziraj"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Zaustavi"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Proširi"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Skupi"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Omot albuma"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Klizač za jačinu zvuka"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Nema izabranih medija"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Nisu dostupne nikakve informacije"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Prebacuje se ekran"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-be/strings.xml b/packages/MediaComponents/res/values-be/strings.xml
new file mode 100644
index 0000000..ac391c1
--- /dev/null
+++ b/packages/MediaComponents/res/values-be/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"Сістэма"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Прылады"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Кнопка трансляцыі"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Кнопка трансляцыі. Адключана"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Кнопка трансляцыі. Ідзе падключэнне"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Кнопка трансляцыі. Падключана"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Трансліраваць на"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Пошук прылад"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Адлучыць"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Спыніць трансляцыю"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Закрыць"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Прайграць"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Прыпыніць"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Спыніць"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Разгарнуць"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Згарнуць"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Вокладка альбома"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Паўзунок гучнасці"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Медыяфайл не выбраны"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Інфармацыя адсутнічае"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Экран трансляцыі"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-bg/strings.xml b/packages/MediaComponents/res/values-bg/strings.xml
new file mode 100644
index 0000000..76712d4
--- /dev/null
+++ b/packages/MediaComponents/res/values-bg/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"Система"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Устройства"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Бутон за предаване"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Бутон за предаване. Връзката е прекратена"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Бутон за предаване. Свързва се"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Бутон за предаване. Установена е връзка"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Предаване към"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Търсят се устройства"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Прекратяване на връзката"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Спиране на предаването"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Затваряне"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Пускане"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Поставяне на пауза"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Спиране"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Разгъване"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Свиване"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Обложка на албума"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Плъзгач за силата на звука"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Няма избрана мултимедия"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Няма налична информация"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Екранът се предава"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-bn/strings.xml b/packages/MediaComponents/res/values-bn/strings.xml
new file mode 100644
index 0000000..1bf5932
--- /dev/null
+++ b/packages/MediaComponents/res/values-bn/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"সিস্টেম"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"ডিভাইসগুলি"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"কাস্ট করার বোতাম"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"কাস্ট করার বোতাম৷ সংযোগ বিচ্ছিন্ন হয়েছে"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"কাস্ট করার বোতাম৷ সংযোগ করা হচ্ছে"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"কাস্ট করার বোতাম৷ সংযুক্ত হয়েছে"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"এতে কাস্ট করুন"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"ডিভাইসগুলিকে খোঁজা হচ্ছে"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"সংযোগ বিচ্ছিন্ন করুন"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"কাস্ট করা বন্ধ করুন"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"বন্ধ করুন"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"চালান"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"বিরাম দিন"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"থামান"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"বড় করুন"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"সঙ্কুচিত করুন"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"অ্যালবাম শৈলি"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"ভলিউম স্লাইডার"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"কোনো মিডিয়া নির্বাচন করা হয়নি"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"কোনো তথ্য উপলব্ধ নেই"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"স্ক্রীন কাস্ট করা হচ্ছে"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-bs/strings.xml b/packages/MediaComponents/res/values-bs/strings.xml
new file mode 100644
index 0000000..711c742
--- /dev/null
+++ b/packages/MediaComponents/res/values-bs/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"Sistem"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Uređaji"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Dugme za emitiranje"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Dugme za emitiranje. Veza je prekinuta"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Dugme za emitiranje. Povezivanje"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Dugme za emitiranje. Povezano"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Emitiranje na"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Traženje uređaja"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Prekini vezu"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Zaustavi prebacivanje"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Zatvori"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Reproduciraj"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Pauziraj"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Zaustavi"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Proširi"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Skupi"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Omot albuma"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Klizač za jačinu zvuka"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Nijedan medij nije odabran"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Nema dostupnih informacija"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Prebacuje se ekran"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-ca/strings.xml b/packages/MediaComponents/res/values-ca/strings.xml
new file mode 100644
index 0000000..bf85acf
--- /dev/null
+++ b/packages/MediaComponents/res/values-ca/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"Sistema"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Dispositius"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Botó d\'emetre"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Botó Emet. Desconnectat."</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Botó Emet. S\'està connectant."</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Botó Emet. Connectat."</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Emet a"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"S\'estan cercant dispositius"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Desconnecta"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Atura l\'emissió"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Tanca"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Reprodueix"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Posa en pausa"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Atura"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Desplega"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Replega"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Imatge de l\'àlbum"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Control lliscant de volum"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"No s\'ha seleccionat cap fitxer multimèdia"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"No hi ha informació disponible"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Emissió de pantalla"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-cs/strings.xml b/packages/MediaComponents/res/values-cs/strings.xml
new file mode 100644
index 0000000..09a8920
--- /dev/null
+++ b/packages/MediaComponents/res/values-cs/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"Systém"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Zařízení"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Tlačítko odesílání"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Tlačítko odesílání. Odpojeno"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Tlačítko odesílání. Připojování"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Tlačítko odesílání. Připojeno"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Odesílat do"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Hledání zařízení"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Odpojit"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Zastavit odesílání"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Zavřít"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Přehrát"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Pozastavit"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Zastavit"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Rozbalit"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Sbalit"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Obal alba"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Posuvník hlasitosti"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Nebyla vybrána žádná média"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Nejsou k dispozici žádné informace"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Odesílání obsahu obrazovky"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-da/strings.xml b/packages/MediaComponents/res/values-da/strings.xml
new file mode 100644
index 0000000..8e7a790
--- /dev/null
+++ b/packages/MediaComponents/res/values-da/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"System"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Enheder"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Cast-knap"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Cast-knap. Forbindelsen er afbrudt"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Cast-knap. Opretter forbindelse"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Cast-knap. Tilsluttet"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Cast til"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Finder enheder"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Afbryd"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Stop med at caste"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Luk"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Afspil"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Sæt på pause"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Stop"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Udvid"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Skjul"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Albumgrafik"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Lydstyrkeskyder"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Ingen medier er markeret"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Der er ingen tilgængelige oplysninger"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Skærmen castes"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-de/strings.xml b/packages/MediaComponents/res/values-de/strings.xml
new file mode 100644
index 0000000..26bf57c
--- /dev/null
+++ b/packages/MediaComponents/res/values-de/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"System"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Geräte"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Cast-Symbol"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Streaming-Schaltfläche. Nicht verbunden"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Streaming-Schaltfläche. Verbindung wird hergestellt"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Streaming-Schaltfläche. Verbunden"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Streamen auf"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Geräte werden gesucht."</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Verbindung trennen"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Streaming beenden"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Schließen"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Wiedergeben"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Pausieren"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Beenden"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Maximieren"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Minimieren"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Albumcover"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Schieberegler für die Lautstärke"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Keine Medien ausgewählt"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Keine Informationen verfügbar"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Bildschirm wird gestreamt."</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-el/strings.xml b/packages/MediaComponents/res/values-el/strings.xml
new file mode 100644
index 0000000..d82f69b
--- /dev/null
+++ b/packages/MediaComponents/res/values-el/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"Σύστημα"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Συσκευές"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Κουμπί Cast"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Κουμπί μετάδοσης. Αποσυνδέθηκε"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Κουμπί μετάδοση. Σύνδεση σε εξέλιξη"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Κουμπί μετάδοσης. Συνδέθηκε"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Μετάδοση σε"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Εύρεση συσκευών"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Αποσύνδεση"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Διακοπή μετάδοσης"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Κλείσιμο"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Αναπαραγωγή"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Παύση"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Διακοπή"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Ανάπτυξη"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Σύμπτυξη"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Εξώφυλλο άλμπουμ"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Ρυθμιστικό έντασης ήχου"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Δεν έχουν επιλεγεί μέσα"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Δεν υπάρχουν διαθέσιμες πληροφορίες"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Μετάδοση οθόνης"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-en-rAU/strings.xml b/packages/MediaComponents/res/values-en-rAU/strings.xml
new file mode 100644
index 0000000..dd3f219
--- /dev/null
+++ b/packages/MediaComponents/res/values-en-rAU/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"System"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Devices"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Cast button"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Cast button. Disconnected"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Cast button. Connecting"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Cast button. Connected"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Cast to"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Finding devices"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Disconnect"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Stop casting"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Close"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Play"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Pause"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Stop"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Expand"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Collapse"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Album art"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Volume slider"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"No media selected"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"No info available"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Casting screen"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-en-rCA/strings.xml b/packages/MediaComponents/res/values-en-rCA/strings.xml
new file mode 100644
index 0000000..dd3f219
--- /dev/null
+++ b/packages/MediaComponents/res/values-en-rCA/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"System"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Devices"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Cast button"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Cast button. Disconnected"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Cast button. Connecting"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Cast button. Connected"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Cast to"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Finding devices"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Disconnect"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Stop casting"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Close"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Play"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Pause"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Stop"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Expand"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Collapse"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Album art"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Volume slider"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"No media selected"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"No info available"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Casting screen"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-en-rGB/strings.xml b/packages/MediaComponents/res/values-en-rGB/strings.xml
new file mode 100644
index 0000000..dd3f219
--- /dev/null
+++ b/packages/MediaComponents/res/values-en-rGB/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"System"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Devices"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Cast button"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Cast button. Disconnected"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Cast button. Connecting"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Cast button. Connected"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Cast to"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Finding devices"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Disconnect"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Stop casting"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Close"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Play"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Pause"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Stop"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Expand"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Collapse"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Album art"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Volume slider"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"No media selected"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"No info available"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Casting screen"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-en-rIN/strings.xml b/packages/MediaComponents/res/values-en-rIN/strings.xml
new file mode 100644
index 0000000..dd3f219
--- /dev/null
+++ b/packages/MediaComponents/res/values-en-rIN/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"System"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Devices"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Cast button"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Cast button. Disconnected"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Cast button. Connecting"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Cast button. Connected"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Cast to"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Finding devices"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Disconnect"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Stop casting"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Close"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Play"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Pause"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Stop"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Expand"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Collapse"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Album art"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Volume slider"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"No media selected"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"No info available"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Casting screen"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-en-rXC/strings.xml b/packages/MediaComponents/res/values-en-rXC/strings.xml
new file mode 100644
index 0000000..a87007e
--- /dev/null
+++ b/packages/MediaComponents/res/values-en-rXC/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"System"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Devices"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Cast button"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Cast button. Disconnected"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Cast button. Connecting"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Cast button. Connected"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Cast to"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Finding devices"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Disconnect"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Stop casting"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Close"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Play"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Pause"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Stop"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Expand"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Collapse"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Album art"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Volume slider"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"No media selected"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"No info available"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Casting screen"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-es-rUS/strings.xml b/packages/MediaComponents/res/values-es-rUS/strings.xml
new file mode 100644
index 0000000..441ead1
--- /dev/null
+++ b/packages/MediaComponents/res/values-es-rUS/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"Sistema"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Dispositivos"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Botón para transmitir"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Botón para transmitir (desconectado)"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Botón para transmitir (conectando)"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Botón para transmitir (conectado)"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Transmitir a"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Buscando dispositivos"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Desconectar"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Detener la transmisión"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Cerrar"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Reproducir"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Pausar"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Detener"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Mostrar"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Ocultar"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Imagen del álbum"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Control deslizante del volumen"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"No se seleccionó ningún contenido multimedia"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Sin información disponible"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Transmitiendo pantalla"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-es/strings.xml b/packages/MediaComponents/res/values-es/strings.xml
new file mode 100644
index 0000000..ff43008
--- /dev/null
+++ b/packages/MediaComponents/res/values-es/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"Sistema"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Dispositivos"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Botón de enviar"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Botón de enviar. Desconectado"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Botón de enviar. Conectando"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Botón de enviar. Conectado"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Enviar a"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Buscando dispositivos"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Desconectar"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Detener envío de contenido"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Cerrar"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Reproducir"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Pausa"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Detener"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Mostrar"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Ocultar"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Portada del álbum"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Control deslizante de volumen"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"No se ha seleccionado ningún medio"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"No hay información disponible"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Enviando pantalla"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-et/strings.xml b/packages/MediaComponents/res/values-et/strings.xml
new file mode 100644
index 0000000..453235b
--- /dev/null
+++ b/packages/MediaComponents/res/values-et/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"Süsteem"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Seadmed"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Ülekandenupp"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Ülekandenupp. Ühendus on katkestatud"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Ülekandenupp. Ühendamine"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Ülekandenupp. Ühendatud"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Ülekandmine seadmesse"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Seadmete otsimine"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Katkesta ühendus"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Peata ülekandmine"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Sulgemine"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Esitamine"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Peatamine"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Peata"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Laiendamine"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Ahendamine"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Albumi kujundus"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Helitugevuse liugur"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Meediat pole valitud"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Teave puudub"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Ekraanikuva ülekandmine"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-eu/strings.xml b/packages/MediaComponents/res/values-eu/strings.xml
new file mode 100644
index 0000000..dba19e4
--- /dev/null
+++ b/packages/MediaComponents/res/values-eu/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"Sistema"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Gailuak"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Igorri botoia"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Igortzeko botoia. Deskonektatuta"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Igortzeko botoia. Konektatzen"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Igortzeko botoia. Konektatuta"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Igorri hona:"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Gailuak bilatzen"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Deskonektatu"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Utzi igortzeari"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Itxi"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Erreproduzitu"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Pausatu"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Gelditu"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Zabaldu"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Tolestu"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Albumaren azala"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Bolumenaren graduatzailea"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Ez da hautatu multimedia-edukirik"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Ez dago informaziorik"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Pantaila igortzen"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-fa/strings.xml b/packages/MediaComponents/res/values-fa/strings.xml
new file mode 100644
index 0000000..4c6c779
--- /dev/null
+++ b/packages/MediaComponents/res/values-fa/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"سیستم"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"دستگاهها"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"دکمه ارسال محتوا"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"دکمه فرستادن. ارتباط قطع شد"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"دکمه فرستادن. درحال مرتبطسازی"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"دکمه فرستادن. مرتبط شد"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"ارسال محتوا به"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"پیدا کردن دستگاهها"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"قطع ارتباط"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"توقف ارسال محتوا"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"بستن"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"پخش"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"مکث"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"توقف"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"بزرگ کردن"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"کوچک کردن"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"عکس روی جلد آلبوم"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"لغزنده میزان صدا"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"رسانه انتخاب نشده است"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"اطلاعات در دسترس نیست"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"درحال فرستادن صفحه"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-fi/strings.xml b/packages/MediaComponents/res/values-fi/strings.xml
new file mode 100644
index 0000000..d683435
--- /dev/null
+++ b/packages/MediaComponents/res/values-fi/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"Järjestelmä"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Laitteet"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Cast-painike"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Cast-painike. Yhteys katkaistu"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Cast-painike. Yhdistetään"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Cast-painike. Yhdistetty"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Suoratoiston kohde"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Etsitään laitteita"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Katkaise yhteys"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Lopeta suoratoisto"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Sulje"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Toista"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Keskeytä"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Pysäytä"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Laajenna"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Tiivistä"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Albumin kansikuva"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Äänenvoimakkuuden liukusäädin"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Ei valittua mediaa."</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Tietoja ei ole saatavilla"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Suoratoistetaan näyttöä"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-fr-rCA/strings.xml b/packages/MediaComponents/res/values-fr-rCA/strings.xml
new file mode 100644
index 0000000..c4f984b
--- /dev/null
+++ b/packages/MediaComponents/res/values-fr-rCA/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"Système"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Appareils"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Bouton Diffuser"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Bouton Diffuser. Déconnecté"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Bouton Diffuser. Connexion en cours…"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Bouton Diffuser. Connecté"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Diffuser sur"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Recherche d\'appareils"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Se déconnecter"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Arrêter la diffusion"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Fermer"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Lire"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Interrompre"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Arrêter"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Développer"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Réduire"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Image de l\'album"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Curseur de réglage du volume"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Aucun média sélectionné"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Aucune information disponible"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Diffusion de l\'écran en cours"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-fr/strings.xml b/packages/MediaComponents/res/values-fr/strings.xml
new file mode 100644
index 0000000..12c312f
--- /dev/null
+++ b/packages/MediaComponents/res/values-fr/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"Système"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Appareils"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Icône Cast"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Icône Cast. Déconnecté"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Icône Cast. Connexion…"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Icône Cast. Connecté"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Caster sur"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Recherche d\'appareils…"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Déconnecter"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Arrêter la diffusion"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Fermer"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Lecture"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Pause"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Arrêter"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Développer"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Réduire"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Image de l\'album"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Curseur de volume"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Aucun média sélectionné"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Aucune information disponible"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Diffusion de l\'écran en cours…"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-gl/strings.xml b/packages/MediaComponents/res/values-gl/strings.xml
new file mode 100644
index 0000000..1b2c354
--- /dev/null
+++ b/packages/MediaComponents/res/values-gl/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"Sistema"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Dispositivos"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Botón de emitir"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Botón de emitir. Desconectado"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Botón de emitir. Conectando"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Botón de emitir. Conectado"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Emitir a"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Buscando dispositivos"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Desconectar"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Deter emisión"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Pechar"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Reproduce"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Pausa"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Deter"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Ampliar"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Contraer"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Portada do álbum"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Control desprazable do volume"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Non se seleccionaron recursos"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Non hai información dispoñible"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Emisión de pantalla"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-gu/strings.xml b/packages/MediaComponents/res/values-gu/strings.xml
new file mode 100644
index 0000000..2cd5f3f
--- /dev/null
+++ b/packages/MediaComponents/res/values-gu/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"સિસ્ટમ"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"ઉપકરણો"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"કાસ્ટ કરો બટન"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"કાસ્ટ કરો બટન. ડિસ્કનેક્ટ કર્યું"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"કાસ્ટ કરો બટન. કનેક્ટ થઈ રહ્યું છે"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"કાસ્ટ કરો બટન. કનેક્ટ થયું"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"આના પર કાસ્ટ કરો"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"ઉપકરણો શોધી રહ્યાં છીએ"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"ડિસ્કનેક્ટ કરો"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"કાસ્ટ કરવાનું રોકો"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"બંધ કરો"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"ચલાવો"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"થોભાવો"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"રોકો"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"વિસ્તૃત કરો"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"સંકુચિત કરો"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"આલ્બમ કલા"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"વૉલ્યુમ સ્લાઇડર"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"કોઈ મીડિયા પસંદ કરેલ નથી"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"કોઈ માહિતી ઉપલબ્ધ નથી"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"સ્ક્રીનને કાસ્ટ કરી રહ્યાં છે"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-hi/strings.xml b/packages/MediaComponents/res/values-hi/strings.xml
new file mode 100644
index 0000000..9552a59
--- /dev/null
+++ b/packages/MediaComponents/res/values-hi/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"सिस्टम"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"डिवाइस"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"कास्ट करें बटन"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"कास्ट करें बटन. डिसकनेक्ट है"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"कास्ट करें बटन. कनेक्ट हो रहा है"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"कास्ट करें बटन. कनेक्ट है"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"इस पर कास्ट करें"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"डिवाइस ढूंढ रहा है"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"डिसकनेक्ट करें"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"कास्ट करना बंद करें"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"बंद करें"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"चलाएं"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"रोकें"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"बंद करें"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"विस्तार करें"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"छोटा करें"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"एल्बम आर्ट"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"वॉल्यूम स्लाइडर"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"कोई मीडिया चयनित नहीं है"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"कोई जानकारी मौजूद नहीं है"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"स्क्रीन कास्ट हो रही है"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-hr/strings.xml b/packages/MediaComponents/res/values-hr/strings.xml
new file mode 100644
index 0000000..3c43ee7
--- /dev/null
+++ b/packages/MediaComponents/res/values-hr/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"Sustav"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Uređaji"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Gumb za emitiranje"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Gumb za emitiranje. Veza prekinuta"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Gumb za emitiranje. Povezivanje"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Gumb za emitiranje. Povezan"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Emitiranje na"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Traženje uređaja"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Prekini vezu"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Zaustavi emitiranje"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Zatvaranje"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Reprodukcija"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Pauziranje"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Zaustavi"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Proširivanje"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Sažimanje"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Naslovnica albuma"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Klizač za glasnoću"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Nije odabran nijedan medij"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Informacije nisu dostupne"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Emitiranje zaslona"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-hu/strings.xml b/packages/MediaComponents/res/values-hu/strings.xml
new file mode 100644
index 0000000..a36bdfe
--- /dev/null
+++ b/packages/MediaComponents/res/values-hu/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"Rendszer"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Eszközök"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Átküldés gomb"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Átküldés gomb. Kapcsolat bontva"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Átküldés gomb. Csatlakozás"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Átküldés gomb. Csatlakoztatva"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Átküldés ide"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Eszközök keresése"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Leválasztás"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Átküldés leállítása"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Bezárás"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Lejátszás"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Szüneteltetés"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Leállítás"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Kibontás"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Összecsukás"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Lemezborító"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Hangerőszabályzó"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Nincs média kiválasztva"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Nincs információ"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Képernyőtartalom átküldése"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-hy/strings.xml b/packages/MediaComponents/res/values-hy/strings.xml
new file mode 100644
index 0000000..8ec82b7
--- /dev/null
+++ b/packages/MediaComponents/res/values-hy/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"Համակարգ"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Սարքեր"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Հեռարձակման կոճակ"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Հեռարձակման կոճակ: Սարքն անջատված է"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Հեռարձակման կոճակ: Սարքը կապակցվում է"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Հեռարձակման կոճակ: Սարքը կապակցված է"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Ընտրեք սարքը"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Սարքերի որոնում"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Անջատել"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Դադարեցնել հեռարձակումը"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Փակել"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Նվագարկել"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Դադար"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Դադարեցնել"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Ընդարձակել"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Կոծկել"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Ալբոմի շապիկ"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Ձայնի ուժգնության կարգավորիչ"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Մեդիա ֆայլեր չեն ընտրվել"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Տեղեկությունները հասանելի չեն"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Էկրանը հեռարձակվում է"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-in/strings.xml b/packages/MediaComponents/res/values-in/strings.xml
new file mode 100644
index 0000000..6b2752e
--- /dev/null
+++ b/packages/MediaComponents/res/values-in/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"Sistem"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Perangkat"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Tombol Cast"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Tombol Cast. Terputus"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Tombol Cast. Menghubungkan"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Tombol Cast. Terhubung"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Transmisikan ke"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Mencari perangkat"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Putuskan sambungan"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Hentikan cast"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Tutup"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Putar"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Jeda"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Berhenti"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Luaskan"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Ciutkan"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Sampul album"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Bilah geser volume"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Tidak ada media yang dipilih"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Tidak ada info yang tersedia"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Transmisi layar"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-is/strings.xml b/packages/MediaComponents/res/values-is/strings.xml
new file mode 100644
index 0000000..6a35ea6
--- /dev/null
+++ b/packages/MediaComponents/res/values-is/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"Kerfi"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Tæki"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Útsendingarhnappur"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Útsendingarhnappur. Aftengt"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Útsendingarhnappur. Tengist"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Útsendingarhnappur. Tengt"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Senda út í"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Leitað að tækjum"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Aftengjast"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Stöðva útsendingu"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Loka"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Spila"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Hlé"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Stöðva"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Stækka"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Minnka"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Plötuumslag"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Hljóðstyrkssleði"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Enginn miðill valinn"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Engar upplýsingar í boði"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Skjár sendur út"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-it/strings.xml b/packages/MediaComponents/res/values-it/strings.xml
new file mode 100644
index 0000000..716e3ac
--- /dev/null
+++ b/packages/MediaComponents/res/values-it/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"Sistema"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Dispositivi"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Pulsante Trasmetti"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Pulsante Trasmetti. Disconnesso"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Pulsante Trasmetti. Connessione in corso"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Pulsante Trasmetti. Connesso"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Trasmetti a"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Ricerca di dispositivi in corso"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Scollega"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Interrompi trasmissione"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Chiudi"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Riproduci"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Pausa"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Interrompi"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Espandi"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Comprimi"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Copertina"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Dispositivo di scorrimento del volume"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Nessun contenuto multimediale selezionato"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Nessuna informazione disponibile"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Trasmissione dello schermo in corso"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-iw/strings.xml b/packages/MediaComponents/res/values-iw/strings.xml
new file mode 100644
index 0000000..252b0ce
--- /dev/null
+++ b/packages/MediaComponents/res/values-iw/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"מערכת"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"מכשירים"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"לחצן הפעלת Cast"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"לחצן הפעלת Cast. מנותק"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"לחצן הפעלת Cast. מתחבר"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"לחצן הפעלת Cast. מחובר"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"העברה אל"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"מחפש מכשירים"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"נתק"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"הפסק את ההעברה"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"סגור"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"הפעל"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"השהה"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"הפסק"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"הרחב"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"כווץ"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"עטיפת אלבום"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"מחוון עוצמה"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"לא נבחרה מדיה"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"אין מידע זמין"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"העברת מסך מתבצעת"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-ja/strings.xml b/packages/MediaComponents/res/values-ja/strings.xml
new file mode 100644
index 0000000..a149727
--- /dev/null
+++ b/packages/MediaComponents/res/values-ja/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"システム"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"端末"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"キャストアイコン"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"キャスト アイコン。接続解除済み"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"キャスト アイコン。接続中"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"キャスト アイコン。接続済み"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"キャストするデバイス"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"端末を検索しています"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"接続を解除"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"キャストを停止"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"閉じる"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"再生"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"一時停止"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"停止"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"展開"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"折りたたむ"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"アルバムアート"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"音量スライダー"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"メディアが選択されていません"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"情報がありません"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"画面をキャストしています"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-ka/strings.xml b/packages/MediaComponents/res/values-ka/strings.xml
new file mode 100644
index 0000000..3da081a
--- /dev/null
+++ b/packages/MediaComponents/res/values-ka/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"სისტემა"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"მოწყობილობები"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"ტრანსლირების ღილაკი"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"ტრანსლირების ღილაკი. გათიშული"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"ტრანსლირების ღილაკი. მიმდინარეობს დაკავშირება"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"ტრანსლირების ღილაკი. დაკავშირებული"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"ტრანსლირება:"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"მოწყობილობების მოძიება..."</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"კავშირის გაწყვეტა"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"ტრანსლირების შეწყვეტა"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"დახურვა"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"დაკვრა"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"პაუზა"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"შეწყვეტა"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"გაშლა"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"ჩაკეცვა"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"ალბომის გარეკანი"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"ხმის სლაიდერი"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"მედია არჩეული არ არის"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"ინფორმაცია არ არის ხელმისაწვდომი"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"მიმდინარეობს ეკრანის გადაცემა"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-kk/strings.xml b/packages/MediaComponents/res/values-kk/strings.xml
new file mode 100644
index 0000000..94dcbb3
--- /dev/null
+++ b/packages/MediaComponents/res/values-kk/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"Жүйе"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Құрылғылар"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Трансляциялау түймесі"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"\"Трансляциялау\" түймесі. Ажыратулы"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"\"Трансляциялау\" түймесі. Қосылуда"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"\"Трансляциялау\" түймесі. Қосылды"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Келесіге трансляциялау"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Құрылғыларды табу"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Ажырату"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Трансляциялауды тоқтату"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Жабу"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Ойнату"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Кідірту"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Тоқтату"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Жаю"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Жию"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Альбом шебері"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Дыбыс деңгейінің жүгірткісі"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Ешбір тасушы таңдалмаған"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Қол жетімді ақпарат жоқ"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Экранды трансляциялау"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-km/strings.xml b/packages/MediaComponents/res/values-km/strings.xml
new file mode 100644
index 0000000..e44780e
--- /dev/null
+++ b/packages/MediaComponents/res/values-km/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"ប្រព័ន្ធ"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"ឧបករណ៍"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"ប៊ូតុងខាស"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"ខាសប៊ូតុង៖ បានកាត់ផ្តាច់"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"ខាសប៊ូតុង៖ កំពុងភ្ជាប់"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"ខាសប៊ូតុង៖ បានភ្ជាប់ហើយ"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"បញ្ជូនទៅ"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"កំពុងស្វែងរកឧបករណ៍"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"ផ្ដាច់"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"ឈប់ភ្ជាប់"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"បិទ"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"ចាក់"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"ផ្អាក"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"ឈប់"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"ពង្រីក"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"បង្រួម"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"ស្នាដៃសិល្បៈអាល់ប៊ុម"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"របារកម្រិតសំឡេង"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"គ្មានការជ្រើសមេឌៀទេ"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"មិនមានព័ត៌មានទេ"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"កំពុងខាសអេក្រង់"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-kn/strings.xml b/packages/MediaComponents/res/values-kn/strings.xml
new file mode 100644
index 0000000..4237fdd
--- /dev/null
+++ b/packages/MediaComponents/res/values-kn/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"ಸಿಸ್ಟಂ"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"ಸಾಧನಗಳು"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"ಬಿತ್ತರಿಸು ಬಟನ್"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"ಬಿತ್ತರಿಸು ಬಟನ್. ಸಂಪರ್ಕ ಕಡಿತಗೊಳಿಸಲಾಗಿದೆ"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"ಬಿತ್ತರಿಸು ಬಟನ್. ಸಂಪರ್ಕಿಸಲಾಗುತ್ತಿದೆ"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"ಬಿತ್ತರಿಸು ಬಟನ್. ಸಂಪರ್ಕಿತಗೊಂಡಿದೆ"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"ಇದಕ್ಕೆ ಬಿತ್ತರಿಸಿ"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"ಸಾಧನಗಳನ್ನು ಹುಡುಕಲಾಗುತ್ತಿದೆ"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"ಸಂಪರ್ಕ ಕಡಿತಗೊಳಿಸು"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"ಬಿತ್ತರಿಸುವಿಕೆ ನಿಲ್ಲಿಸಿ"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"ಮುಚ್ಚು"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"ಪ್ಲೇ ಮಾಡಿ"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"ವಿರಾಮ"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"ನಿಲ್ಲಿಸಿ"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"ವಿಸ್ತರಿಸು"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"ಸಂಕುಚಿಸು"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"ಆಲ್ಬಮ್ ಕಲೆ"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"ವಾಲ್ಯೂಮ್ ಸ್ಲೈಡರ್"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"ಯಾವುದೇ ಮಾಧ್ಯಮ ಆಯ್ಕೆಮಾಡಲಾಗಿಲ್ಲ"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"ಯಾವುದೇ ಮಾಹಿತಿ ಲಭ್ಯವಿಲ್ಲ"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"ಪರದೆಯನ್ನು ಬಿತ್ತರಿಸಲಾಗುತ್ತಿದೆ"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-ko/strings.xml b/packages/MediaComponents/res/values-ko/strings.xml
new file mode 100644
index 0000000..be893a9
--- /dev/null
+++ b/packages/MediaComponents/res/values-ko/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"시스템"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"기기"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"전송 버튼"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"전송 버튼. 연결 해제됨"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"전송 버튼. 연결 중"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"전송 버튼. 연결됨"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"전송할 기기"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"기기를 찾는 중"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"연결 해제"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"전송 중지"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"닫기"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"재생"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"일시중지"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"중지"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"펼치기"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"접기"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"앨범아트"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"볼륨 슬라이더"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"선택한 미디어 없음"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"정보가 없습니다."</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"화면 전송 중"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-ky/strings.xml b/packages/MediaComponents/res/values-ky/strings.xml
new file mode 100644
index 0000000..57813af
--- /dev/null
+++ b/packages/MediaComponents/res/values-ky/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"Тутум"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Түзмөктөр"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Тышкы экранга чыгаруу баскычы"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Тышкы экранга чыгаруу баскычы. Түзмөк ажырап турат."</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Тышкы экранга чыгаруу баскычы. Түзмөк туташууда"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Тышкы экранга чыгаруу баскычы. Түзмөк туташып турат"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Төмөнкүгө чыгаруу"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Түзмөктөр изделүүдө"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Ажыратуу"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Тышк экранга чыгарну токтотуу"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Жабуу"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Ойнотуу"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Тындыруу"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Токтотуу"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Жайып көрсөтүү"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Жыйыштыруу"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Альбом мукабасы"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Үндү катуулатуучу сыдырма"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Бир да медиа файл тандалган жок"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Эч маалымат жок"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Тышкы экранга чыгарылууда"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-land/dimens.xml b/packages/MediaComponents/res/values-land/dimens.xml
new file mode 100644
index 0000000..29f1e1d
--- /dev/null
+++ b/packages/MediaComponents/res/values-land/dimens.xml
@@ -0,0 +1,26 @@
+<?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>
+ <!-- MediaRouteController's volume group list -->
+ <eat-comment />
+ <!-- Maximum height of volume group list. -->
+ <dimen name="mr_controller_volume_group_list_max_height">132dp</dimen>
+ <!-- Height of volume group item. -->
+ <dimen name="mr_controller_volume_group_list_item_height">61dp</dimen>
+ <!-- Size of an item's icon. -->
+ <dimen name="mr_controller_volume_group_list_item_icon_size">18dp</dimen>
+</resources>
diff --git a/packages/MediaComponents/res/values-lo/strings.xml b/packages/MediaComponents/res/values-lo/strings.xml
new file mode 100644
index 0000000..91737db
--- /dev/null
+++ b/packages/MediaComponents/res/values-lo/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"ລະບົບ"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"ອຸປະກອນ"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"ປຸ່ມຄາສທ໌"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"ປຸ່ມສົ່ງສັນຍານ. ຕັດການເຊື່ອມຕໍ່ແລ້ວ"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"ປຸ່ມສົ່ງສັນຍານ. ກຳລັງເຊື່ອມຕໍ່"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"ປຸ່ມສົ່ງສັນຍານ. ເຊື່ອມຕໍ່ແລ້ວ"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"ສົ່ງສັນຍານຫາ"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"ກຳລັງຊອກຫາອຸປະກອນ"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"ຕັດການເຊື່ອມຕໍ່"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"ຢຸດການສົ່ງສັນຍານ"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"ປິດ"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"ຫຼິ້ນ"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"ຢຸດຊົ່ວຄາວ"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"ຢຸດ"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"ຂະຫຍາຍ"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"ຫຍໍ້ລົງ"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"ໜ້າປົກອະລະບໍ້າ"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"ຕົວປັບລະດັບສຽງ"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"ບໍ່ໄດ້ເລືອກມີເດຍໃດໄວ້"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"ບໍ່ມີຂໍ້ມູນ"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"ການສົ່ງພາບໜ້າຈໍ"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-lt/strings.xml b/packages/MediaComponents/res/values-lt/strings.xml
new file mode 100644
index 0000000..ff036d1
--- /dev/null
+++ b/packages/MediaComponents/res/values-lt/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"Sistema"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Įrenginiai"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Perdavimo mygtukas"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Perdavimo mygtukas. Atsijungta"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Perdavimo mygtukas. Prisijungiama"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Perdavimo mygtukas. Prisijungta"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Perduoti į"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Randami įrenginiai"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Atjungti"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Sustabdyti perdavimą"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Uždaryti"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Leisti"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Pristabdyti"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Sustabdyti"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Išskleisti"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Sutraukti"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Albumo viršelis"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Garsumo šliaužiklis"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Nepasirinkta jokia medija"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Informacija nepasiekiama"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Perduodamas ekranas"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-lv/strings.xml b/packages/MediaComponents/res/values-lv/strings.xml
new file mode 100644
index 0000000..454063e
--- /dev/null
+++ b/packages/MediaComponents/res/values-lv/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"Sistēma"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Ierīces"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Apraides poga"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Apraides poga. Savienojums pārtraukts"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Apraides poga. Notiek savienojuma izveide"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Apraides poga. Savienojums izveidots"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Apraidīšana uz ierīci"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Notiek ierīču meklēšana"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Atvienot"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Apturēt apraidi"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Aizvērt"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Atskaņot"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Apturēt"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Apturēt"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Izvērst"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Sakļaut"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Albuma vāciņš"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Skaļuma slīdnis"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Nav atlasīti multivides faili"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Nav pieejama informācija"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Notiek ekrāna apraide"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-mk/strings.xml b/packages/MediaComponents/res/values-mk/strings.xml
new file mode 100644
index 0000000..12dee36
--- /dev/null
+++ b/packages/MediaComponents/res/values-mk/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"Систем"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Уреди"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Копчето за Cast"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Копче за Cast. Исклучено"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Копче за Cast. Се поврзува"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Копче за Cast. Поврзано"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Емитувај на"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Се бараат уреди"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Исклучи"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Сопри го емитувањето"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Затвори"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Репродуцирај"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Паузирај"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Сопри"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Прошири"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Собери"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Корица на албум"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Лизгач за јачина на звук"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Не се избрани медиуми"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Нема достапни информации"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Екранот се емитува"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-ml/strings.xml b/packages/MediaComponents/res/values-ml/strings.xml
new file mode 100644
index 0000000..2d914b9
--- /dev/null
+++ b/packages/MediaComponents/res/values-ml/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"സിസ്റ്റം"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"ഉപകരണങ്ങൾ"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"ടാപ്പുചെയ്യുക"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"കാസ്റ്റ് ബട്ടൺ. വിച്ഛേദിച്ചു"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"കാസ്റ്റ് ബട്ടൺ. കണക്റ്റുചെയ്യുന്നു"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"കാസ്റ്റ് ബട്ടൺ. കണക്റ്റുചെയ്തു"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"ഇതിലേക്ക് കാസ്റ്റുചെയ്യുക"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"ഉപകരണങ്ങൾ കണ്ടെത്തുന്നു"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"വിച്ഛേദിക്കുക"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"കാസ്റ്റുചെയ്യൽ നിർത്തുക"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"അവസാനിപ്പിക്കുക"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"പ്ലേ ചെയ്യുക"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"തൽക്കാലം നിർത്തൂ"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"നിര്ത്തുക"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"വികസിപ്പിക്കുക"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"ചുരുക്കുക"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"ആൽബം ആർട്ട്"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"വോളിയം സ്ലൈഡർ"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"മീഡിയയൊന്നും തിരഞ്ഞെടുത്തിട്ടില്ല"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"വിവരങ്ങളൊന്നും ലഭ്യമല്ല"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"സ്ക്രീൻ കാസ്റ്റുചെയ്യുന്നു"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-mn/strings.xml b/packages/MediaComponents/res/values-mn/strings.xml
new file mode 100644
index 0000000..ef87c92
--- /dev/null
+++ b/packages/MediaComponents/res/values-mn/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"Систем"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Төхөөрөмжүүд"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Дамжуулах товчлуур"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Дамжуулах товчлуур. Салсан"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Дамжуулах товчлуур. Холбож байна"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Дамжуулах товчлуур. Холбогдсон"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Дамжуулах"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Төхөөрөмж хайж байна"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Салгах"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Дамжуулахыг зогсоох"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Хаах"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Тоглуулах"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Түр зогсоох"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Зогсоох"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Дэлгэх"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Хураах"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Цомгийн зураг"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Дууны түвшин тааруулагч"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Ямар ч медиа сонгоогүй"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Мэдээлэл байхгүй байна"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Дэлгэцийг дамжуулж байна"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-mr/strings.xml b/packages/MediaComponents/res/values-mr/strings.xml
new file mode 100644
index 0000000..2ffbebb
--- /dev/null
+++ b/packages/MediaComponents/res/values-mr/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"सिस्टम"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"डिव्हाइसेस"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"कास्ट बटण"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"कास्ट बटण. डिस्कनेक्ट केले"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"कास्ट बटण. कनेक्ट करत आहे"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"कास्ट बटण. कनेक्ट केले"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"यावर कास्ट करा"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"डिव्हाइसेस शोधत आहे"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"डिस्कनेक्ट करा"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"कास्ट करणे थांबवा"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"बंद करा"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"प्ले करा"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"विराम"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"थांबा"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"विस्तृत करा"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"संकुचित करा"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"अल्बम कला"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"व्हॉल्यूम स्लायडर"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"मीडिया निवडला नाही"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"कोणतीही माहिती उपलब्ध नाही"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"स्क्रीन कास्ट करत आहे"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-ms/strings.xml b/packages/MediaComponents/res/values-ms/strings.xml
new file mode 100644
index 0000000..085e480
--- /dev/null
+++ b/packages/MediaComponents/res/values-ms/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"Sistem"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Peranti"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Butang Hantar"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Butang hantar. Sambungan diputuskan"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Butang hantar. Menyambung"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Butang hantar. Disambungkan"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Hantar ke"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Mencari peranti"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Putuskan sambungan"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Berhenti menghantar"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Tutup"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Main"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Jeda"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Berhenti"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Kembangkan"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Runtuhkan"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Seni album"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Peluncur kelantangan"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Tiada media dipilih"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Maklumat tidak tersedia"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Menghantar skrin"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-my/strings.xml b/packages/MediaComponents/res/values-my/strings.xml
new file mode 100644
index 0000000..083d805
--- /dev/null
+++ b/packages/MediaComponents/res/values-my/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"စနစ်"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"စက်ပစ္စည်းများ"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"ကာစ်တ်လုပ်ရန် ခလုတ်"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"ကာစ်ခလုတ်။ ချိတ်ဆက်မထားပါ"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"ကာစ်ခလုတ်။ ချိတ်ဆက်နေသည်"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"ကာစ်ခလုတ်။ ချိတ်ဆက်ထားသည်"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"ကာစ်လုပ်ရန် စက်"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"စက်ပစ္စည်းများ ရှာဖွေခြင်း"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"ဆက်သွယ်မှု ဖြတ်ရန်"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"ကာစ်လုပ်ခြင်း ရပ်ရန်"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"ပိတ်ရန်"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"ဖွင့်ရန်"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"ခဏရပ်ရန်"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"ရပ်ရန်"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"ဖြန့်ချရန်၃"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"ခေါက်သိမ်းရန်..."</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"အယ်လ်ဘမ်ပုံ"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"အသံအတိုးအကျယ်ချိန်သည့် ဆလိုက်ဒါ"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"မည်သည့်မီဒီမှ မရွေးချယ်ထားပါ"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"အချက်အလက် မရရှိနိုင်ပါ"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"တည်းဖြတ်ရေး မျက်နှာပြင်"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-nb/strings.xml b/packages/MediaComponents/res/values-nb/strings.xml
new file mode 100644
index 0000000..4f764c9
--- /dev/null
+++ b/packages/MediaComponents/res/values-nb/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"System"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Enheter"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Cast-ikonet"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Cast-knappen. Frakoblet"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Cast-knappen. Kobler til"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Cast-knappen. Tilkoblet"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Cast til"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Søker etter enheter"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Koble fra"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Stopp castingen"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Lukk"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Spill av"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Sett på pause"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Stopp"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Utvid"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Skjul"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Albumgrafikk"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Glidebryter for volum"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Du har ikke valgt noen medier"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Ingen informasjon er tilgjengelig"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Caster skjermen"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-ne/strings.xml b/packages/MediaComponents/res/values-ne/strings.xml
new file mode 100644
index 0000000..d6c2e1a
--- /dev/null
+++ b/packages/MediaComponents/res/values-ne/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"प्रणाली"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"उपकरणहरू"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Cast बटन"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Cast बटन। जडान विच्छेद भयो"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Cast बटन। जडान हुँदै"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Cast बटन। जडान भयो"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"यसमा Cast गर्नुहोस्"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"यन्त्रहरू पत्ता लगाउँदै"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"विच्छेद गर्नुहोस्"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"casting रोक्नुहोस्"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"बन्द गर्नुहोस्"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"बजाउनुहोस्"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"रोक्नुहोस्"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"रोक्नुहोस्"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"विस्तार गर्नुहोस्"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"संक्षिप्त पार्नुहोस्"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"एल्बम आर्ट"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"भोल्युमको स्लाइडर"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"कुनै मिडिया चयन भएको छैन"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"जानकारी उपलब्ध छैन"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"स्क्रिन cast गर्दै"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-nl/strings.xml b/packages/MediaComponents/res/values-nl/strings.xml
new file mode 100644
index 0000000..05df62d
--- /dev/null
+++ b/packages/MediaComponents/res/values-nl/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"Systeem"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Apparaten"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Cast-icoon"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Cast-icoon. Verbinding verbroken"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Cast-icoon. Verbinding maken"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Cast-icoon. Verbonden"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Casten naar"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Apparaten zoeken"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Loskoppelen"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Casten stoppen"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Sluiten"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Afspelen"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Onderbreken"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Stoppen"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Uitvouwen"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Samenvouwen"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Albumhoes"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Volumeschuifregelaar"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Geen media geselecteerd"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Geen informatie beschikbaar"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Scherm casten"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-pa/strings.xml b/packages/MediaComponents/res/values-pa/strings.xml
new file mode 100644
index 0000000..1b5df71
--- /dev/null
+++ b/packages/MediaComponents/res/values-pa/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"ਸਿਸਟਮ"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"ਡਿਵਾਈਸਾਂ"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"ਕਾਸਟ ਬਟਨ"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"ਕਾਸਟ ਬਟਨ। ਡਿਸਕਨੈਕਟ ਕੀਤਾ ਗਿਆ"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"ਕਾਸਟ ਬਟਨ। ਕਨੈਕਟ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"ਕਾਸਟ ਬਟਨ। ਕਨੈਕਟ ਕੀਤਾ ਗਿਆ"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"ਏਥੇ ਕਾਸਟ ਕਰੋ"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"ਡੀਵਾਈਸਾਂ ਨੂੰ ਲੱਭਿਆ ਜਾ ਰਿਹਾ ਹੈ"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"ਡਿਸਕਨੈਕਟ ਕਰੋ"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"ਕਾਸਟ ਕਰਨਾ ਬੰਦ ਕਰੋ"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"ਬੰਦ ਕਰੋ"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"ਪਲੇ ਕਰੋ"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"ਰੋਕੋ"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"ਬੰਦ ਕਰੋ"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"ਵਿਸਤਾਰ ਕਰੋ"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"ਬੰਦ ਕਰੋ"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"ਐਲਬਮ ਆਰਟ"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"ਵੌਲਯੂਮ ਸਲਾਈਡਰ"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"ਕੋਈ ਵੀ ਮੀਡੀਆ ਨਹੀਂ ਚੁਣਿਆ ਗਿਆ"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"ਕੋਈ ਜਾਣਕਾਰੀ ਉਪਲਬਧ ਨਹੀਂ"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"ਸਕ੍ਰੀਨ ਜੋੜ ਰਿਹਾ ਹੈ"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-pl/strings.xml b/packages/MediaComponents/res/values-pl/strings.xml
new file mode 100644
index 0000000..c792a6d
--- /dev/null
+++ b/packages/MediaComponents/res/values-pl/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"System"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Urządzenia"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Przycisk Cast"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Przycisk Prześlij ekran. Rozłączono"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Przycisk Prześlij ekran. Łączę"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Przycisk Prześlij ekran. Połączono"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Przesyłaj na"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Znajdowanie urządzeń"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Odłącz"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Zatrzymaj przesyłanie"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Zamknij"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Odtwórz"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Wstrzymaj"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Zatrzymaj"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Rozwiń"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Zwiń"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Okładka albumu"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Suwak głośności"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Nie wybrano multimediów"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Brak informacji"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Przesyłam ekran"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-pt-rBR/strings.xml b/packages/MediaComponents/res/values-pt-rBR/strings.xml
new file mode 100644
index 0000000..43c619d
--- /dev/null
+++ b/packages/MediaComponents/res/values-pt-rBR/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"Sistema"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Dispositivos"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Botão Transmitir"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Botão \"Transmitir\". Desconectado"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Botão \"Transmitir\". Conectando"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Botão \"Transmitir\". Conectado"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Transmitir para"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Localizando dispositivos"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Desconectar"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Interromper transmissão"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Fechar"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Reproduzir"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Pausar"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Parar"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Expandir"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Recolher"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Arte do álbum"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Controle deslizante de volume"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Nenhuma mídia selecionada"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Nenhuma informação disponível"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Transmitindo a tela"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-pt-rPT/strings.xml b/packages/MediaComponents/res/values-pt-rPT/strings.xml
new file mode 100644
index 0000000..3f0a61d
--- /dev/null
+++ b/packages/MediaComponents/res/values-pt-rPT/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"Sistema"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Dispositivos"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Botão Transmitir"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Botão Transmitir. Desligado"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Botão Transmitir. A ligar..."</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Botão Transmitir. Ligado"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Transmitir para"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"A localizar dispositivos"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Desassociar"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Parar a transmissão"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Fechar"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Reproduzir"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Interromper"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Parar"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Expandir"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Reduzir"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Imagem do álbum"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Controlo de deslize do volume"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Nenhum suporte multimédia selecionado"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Nenhuma informação disponível"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"A transmitir o ecrã"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-pt/strings.xml b/packages/MediaComponents/res/values-pt/strings.xml
new file mode 100644
index 0000000..43c619d
--- /dev/null
+++ b/packages/MediaComponents/res/values-pt/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"Sistema"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Dispositivos"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Botão Transmitir"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Botão \"Transmitir\". Desconectado"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Botão \"Transmitir\". Conectando"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Botão \"Transmitir\". Conectado"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Transmitir para"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Localizando dispositivos"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Desconectar"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Interromper transmissão"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Fechar"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Reproduzir"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Pausar"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Parar"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Expandir"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Recolher"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Arte do álbum"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Controle deslizante de volume"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Nenhuma mídia selecionada"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Nenhuma informação disponível"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Transmitindo a tela"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-ro/strings.xml b/packages/MediaComponents/res/values-ro/strings.xml
new file mode 100644
index 0000000..6ebb2f6
--- /dev/null
+++ b/packages/MediaComponents/res/values-ro/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"Sistem"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Dispozitive"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Butonul de proiecție"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Butonul de proiecție. Deconectat"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Butonul de proiecție. Se conectează"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Butonul de proiecție. Conectat"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Proiectați pe"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Se caută dispozitive"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Deconectați-vă"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Nu mai proiectați"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Închideți"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Redați"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Întrerupeți"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Opriți"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Extindeți"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Restrângeți"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Grafica albumului"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Glisor pentru volum"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Niciun fișier media selectat"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Nu sunt disponibile informații"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Se proiectează ecranul"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-ru/strings.xml b/packages/MediaComponents/res/values-ru/strings.xml
new file mode 100644
index 0000000..7c462d2
--- /dev/null
+++ b/packages/MediaComponents/res/values-ru/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"Система"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Устройства"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Кнопка трансляции"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Кнопка трансляции. Устройство отключено."</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Кнопка трансляции. Устройство подключается."</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Кнопка трансляции. Устройство подключено."</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Выберите устройство"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Поиск устройств…"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Отключить"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Прекратить трансляцию"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Закрыть"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Воспроизвести"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Приостановить"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Остановить"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Развернуть"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Свернуть"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Обложка"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Регулятор громкости"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Медиафайл не выбран"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Данных нет"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Подключение к удаленному монитору"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-si/strings.xml b/packages/MediaComponents/res/values-si/strings.xml
new file mode 100644
index 0000000..a55ce50
--- /dev/null
+++ b/packages/MediaComponents/res/values-si/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"පද්ධතිය"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"උපාංග"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"විකාශ බොත්තම"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"විකාශ බොත්තම. විසන්ධි කරන ලදී"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"විකාශ බොත්තම සම්බන්ධ කරමින්"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"විකාශ බොත්තම සම්බන්ධ කරන ලදී"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"විකාශය"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"උපාංග සෙවීම"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"විසන්ධි කරන්න"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"විකාශ කිරීම නතර කරන්න"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"වසන්න"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"ධාවනය කරන්න"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"විරාම ගන්වන්න"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"නතර කරන්න"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"දිග හරින්න"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"හකුළන්න"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"ඇල්බම කලාව"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"හඬ පරිමා ස්ලයිඩරය"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"මාධ්යය තෝරා නැත"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"ලබා ගත හැකි තොරතුරු නොමැත"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"විකාශ තිරය"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-sk/strings.xml b/packages/MediaComponents/res/values-sk/strings.xml
new file mode 100644
index 0000000..a58aa11
--- /dev/null
+++ b/packages/MediaComponents/res/values-sk/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"Systém"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Zariadenia"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Tlačidlo prenosu"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Tlačidlo prenosu. Odpojené"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Tlačidlo prenosu. Pripája sa"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Tlačidlo prenosu. Pripojené"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Prenos do"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Hľadajú sa zariadenia"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Odpojiť"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Zastaviť prenášanie"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Zavrieť"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Prehrať"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Pozastaviť"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Zastaviť"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Rozbaliť"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Zbaliť"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Obrázok albumu"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Posúvač hlasitosti"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Nie sú vybrané žiadne médiá"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Nie sú k dispozícii žiadne informácie"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Prenáša sa obrazovka"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-sl/strings.xml b/packages/MediaComponents/res/values-sl/strings.xml
new file mode 100644
index 0000000..8ca4ce4
--- /dev/null
+++ b/packages/MediaComponents/res/values-sl/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"Sistem"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Naprave"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Gumb za predvajanje"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Gumb za predvajanje. Povezava je prekinjena."</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Gumb za predvajanje. Vzpostavljanje povezave."</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Gumb za predvajanje. Povezava je vzpostavljena."</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Predvajanje prek:"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Iskanje naprav"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Prekini povezavo"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Ustavi predvajanje"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Zapri"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Predvajanje"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Zaustavi"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Ustavi"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Razširi"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Strni"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Naslovnica albuma"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Drsnik za glasnost"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Ni izbrane predstavnosti"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Podatki niso na voljo"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Predvajanje zaslona"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-sq/strings.xml b/packages/MediaComponents/res/values-sq/strings.xml
new file mode 100644
index 0000000..816e110
--- /dev/null
+++ b/packages/MediaComponents/res/values-sq/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"Sistemi"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Pajisjet"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Butoni i transmetimit"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Butoni i transmetimit. Je i shkëputur"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Butoni i transmetimit. Po lidhet"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Butoni i transmetimit. Je i lidhur"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Transmeto te"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Po kërkon pajisje"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Shkëpute"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Ndalo transmetimin"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Mbyll"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Luaj"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Pauzë"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Ndalo"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Zgjeroje"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Palose"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Kopertina e albumit"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Rrëshqitësi i volumit"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Nuk u zgjodh asnjë media"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Nuk jepet asnjë informacion"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Po transmeton ekranin"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-sr/strings.xml b/packages/MediaComponents/res/values-sr/strings.xml
new file mode 100644
index 0000000..caabad5
--- /dev/null
+++ b/packages/MediaComponents/res/values-sr/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"Систем"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Уређаји"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Дугме Пребаци"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Дугме Пребаци. Веза је прекинута"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Дугме Пребаци. Повезује се"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Дугме Пребаци. Повезан је"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Пребацуј на"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Проналажење уређаја"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Прекини везу"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Заустави пребацивање"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Затвори"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Пусти"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Паузирај"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Заустави"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Прошири"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Скупи"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Омот албума"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Клизач за јачину звука"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Нема изабраних медија"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Нису доступне никакве информације"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Пребацује се екран"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-sv/strings.xml b/packages/MediaComponents/res/values-sv/strings.xml
new file mode 100644
index 0000000..ca7d3e0
--- /dev/null
+++ b/packages/MediaComponents/res/values-sv/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"System"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Enheter"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Cast-knappen"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Cast-knappen. Frånkopplad"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Cast-knappen. Ansluter"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Cast-knappen. Ansluten"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Casta till"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Letar efter enheter"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Koppla från"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Sluta casta"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Stäng"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Spela upp"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Pausa"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Avbryt"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Utöka"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Komprimera"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Skivomslag"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Volymreglage"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Inga media har valts"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Det finns ingen information"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Skärmen castas"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-sw/strings.xml b/packages/MediaComponents/res/values-sw/strings.xml
new file mode 100644
index 0000000..9562cb1
--- /dev/null
+++ b/packages/MediaComponents/res/values-sw/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"Mfumo"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Vifaa"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Kitufe cha kutuma"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Kitufe cha kutuma. Kimeondolewa"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Kitufe cha kutuma. Kinaunganisha"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Kitufe cha kutuma. Kimeunganishwa"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Tuma kwenye"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Inatafuta vifaa"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Ondoa"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Acha kutuma"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Funga"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Cheza"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Sitisha"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Simamisha"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Panua"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Kunja"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Sanaa ya albamu"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Kitelezi cha sauti"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Hakuna maudhui yaliyochaguliwa"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Hakuna maelezo yaliyopatikana"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Inatuma skrini"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-sw600dp/dimens.xml b/packages/MediaComponents/res/values-sw600dp/dimens.xml
new file mode 100644
index 0000000..4042348
--- /dev/null
+++ b/packages/MediaComponents/res/values-sw600dp/dimens.xml
@@ -0,0 +1,24 @@
+<?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>
+ <!-- The platform's desired fixed width for a dialog along the major axis
+ (the screen is in landscape). This may be either a fraction or a dimension.-->
+ <item type="dimen" name="mr_dialog_fixed_width_major">60%</item>
+ <!-- The platform's desired fixed width for a dialog along the minor axis
+ (the screen is in portrait). This may be either a fraction or a dimension.-->
+ <item type="dimen" name="mr_dialog_fixed_width_minor">90%</item>
+</resources>
diff --git a/packages/MediaComponents/res/values-sw720dp/dimens.xml b/packages/MediaComponents/res/values-sw720dp/dimens.xml
new file mode 100644
index 0000000..634ab8d
--- /dev/null
+++ b/packages/MediaComponents/res/values-sw720dp/dimens.xml
@@ -0,0 +1,24 @@
+<?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>
+ <!-- The platform's desired fixed width for a dialog along the major axis
+ (the screen is in landscape). This may be either a fraction or a dimension.-->
+ <item type="dimen" name="mr_dialog_fixed_width_major">50%</item>
+ <!-- The platform's desired fixed width for a dialog along the minor axis
+ (the screen is in portrait). This may be either a fraction or a dimension.-->
+ <item type="dimen" name="mr_dialog_fixed_width_minor">70%</item>
+</resources>
diff --git a/packages/MediaComponents/res/values-ta/strings.xml b/packages/MediaComponents/res/values-ta/strings.xml
new file mode 100644
index 0000000..e1978f3
--- /dev/null
+++ b/packages/MediaComponents/res/values-ta/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"சிஸ்டம்"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"சாதனங்கள்"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"திரையிடு பட்டன்"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"அனுப்புதல் பொத்தான். துண்டிக்கப்பட்டது"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"அனுப்புதல் பொத்தான். இணைக்கிறது"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"அனுப்புதல் பொத்தான். இணைக்கப்பட்டது"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"இதற்கு அனுப்பு"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"சாதனங்களைத் தேடுகிறது"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"தொடர்பைத் துண்டி"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"அனுப்புவதை நிறுத்து"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"மூடும்"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"இயக்கும்"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"இடைநிறுத்தும்"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"நிறுத்துவதற்கான பொத்தான்"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"விரிவாக்கு"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"சுருக்கு"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"ஆல்பம் ஆர்ட்"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"ஒலியளவு ஸ்லைடர்"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"மீடியா எதுவும் தேர்ந்தெடுக்கப்படவில்லை"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"தகவல் எதுவுமில்லை"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"திரையை அனுப்புகிறீர்கள்"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-te/strings.xml b/packages/MediaComponents/res/values-te/strings.xml
new file mode 100644
index 0000000..7d312e3
--- /dev/null
+++ b/packages/MediaComponents/res/values-te/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"సిస్టమ్"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"పరికరాలు"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"ప్రసారం చేయి బటన్"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"ప్రసార బటన్. డిస్కనెక్ట్ చేయబడింది"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"ప్రసార బటన్. కనెక్ట్ చేస్తోంది"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"ప్రసార బటన్. కనెక్ట్ చేయబడింది"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"దీనికి ప్రసారం చేయండి"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"పరికరాలను కనుగొంటోంది"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"డిస్కనెక్ట్ చేయి"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"ప్రసారాన్ని ఆపివేయి"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"మూసివేస్తుంది"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"ప్లే చేస్తుంది"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"పాజ్ చేస్తుంది"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"ఆపివేయి"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"విస్తరింపజేస్తుంది"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"కుదిస్తుంది"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"ఆల్బమ్ ఆర్ట్"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"వాల్యూమ్ స్లయిడర్"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"మీడియా ఏదీ ఎంచుకోబడలేదు"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"సమాచారం అందుబాటులో లేదు"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"స్క్రీన్ను ప్రసారం చేస్తోంది"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-th/strings.xml b/packages/MediaComponents/res/values-th/strings.xml
new file mode 100644
index 0000000..cfa8ae5
--- /dev/null
+++ b/packages/MediaComponents/res/values-th/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"ระบบ"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"อุปกรณ์"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"ปุ่ม \"แคสต์\""</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"ปุ่ม \"แคสต์\" ยกเลิกการเชื่อมต่อ"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"ปุ่ม \"แคสต์\" กำลังเชื่อมต่อ"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"ปุ่ม \"แคสต์\" เชื่อมต่อแล้ว"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"แคสต์ไปยัง"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"กำลังค้นหาอุปกรณ์"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"ยกเลิกการเชื่อมต่อ"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"หยุดแคสต์"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"ปิด"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"เล่น"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"หยุดชั่วคราว"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"หยุด"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"ขยาย"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"ยุบ"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"ปกอัลบั้ม"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"แถบเลื่อนปรับระดับเสียง"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"ไม่ได้เลือกสื่อไว้"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"ไม่มีข้อมูล"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"กำลังแคสต์หน้าจอ"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-tl/strings.xml b/packages/MediaComponents/res/values-tl/strings.xml
new file mode 100644
index 0000000..a8be3d0
--- /dev/null
+++ b/packages/MediaComponents/res/values-tl/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"System"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Mga Device"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Button na I-cast"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Button na I-cast. Nadiskonekta"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Button na I-cast. Kumokonekta"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Button na I-cast. Nakakonekta"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"I-cast sa"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Naghahanap ng mga device"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Idiskonekta"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Ihinto ang pag-cast"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Isara"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"I-play"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"I-pause"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Ihinto"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Palawakin"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"I-collapse"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Album art"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Slider ng volume"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Walang piniling media"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Walang available na impormasyon"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Kina-cast ang screen"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-tr/strings.xml b/packages/MediaComponents/res/values-tr/strings.xml
new file mode 100644
index 0000000..05f6392
--- /dev/null
+++ b/packages/MediaComponents/res/values-tr/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"Sistem"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Cihazlar"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Yayınla düğmesi"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Yayınla düğmesi. Bağlantı kesildi"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Yayınla düğmesi. Bağlanıyor"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Yayınla düğmesi. Bağlandı"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Şuraya yayınla:"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Cihazlar bulunuyor"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Bağlantıyı kes"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Yayını durdur"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Kapat"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Oynat"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Duraklat"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Durdur"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Genişlet"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Daralt"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Albüm kapağı"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Ses düzeyi kaydırma çubuğu"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Medya seçilmedi"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Bilgi yok"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Ekran yayınlanıyor"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-uk/strings.xml b/packages/MediaComponents/res/values-uk/strings.xml
new file mode 100644
index 0000000..33d365e
--- /dev/null
+++ b/packages/MediaComponents/res/values-uk/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"Система"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Пристрої"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Кнопка трансляції"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Кнопка трансляції. Від’єднано"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Кнопка трансляції. Під’єднання"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Кнопка трансляції. Під’єднано"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Транслювати на"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Пошук пристроїв"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Відключити"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Припинити трансляцію"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Закрити"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Відтворити"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Призупинити"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Припинити"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Розгорнути"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Згорнути"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Обкладинка альбому"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Повзунок гучності"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Медіа-файл не вибрано"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Немає даних"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Трансляція екрана"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-ur/strings.xml b/packages/MediaComponents/res/values-ur/strings.xml
new file mode 100644
index 0000000..632c598
--- /dev/null
+++ b/packages/MediaComponents/res/values-ur/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"سسٹم"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"آلات"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"کاسٹ کرنے کا بٹن"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"کاسٹ کرنے کا بٹن۔ غیر منسلک ہے"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"کاسٹ کرنے کا بٹن۔ منسلک ہو رہا ہے"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"کاسٹ کرنے کا بٹن۔ منسلک ہے"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"اس میں کاسٹ کریں"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"آلات تلاش ہو رہے ہیں"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"غیر منسلک کریں"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"کاسٹ کرنا بند کریں"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"بند کریں"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"چلائیں"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"موقوف کریں"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"روکیں"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"پھیلائیں"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"سکیڑیں"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"البم آرٹ"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"والیوم سلائیڈر"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"کوئی میڈیا منتخب نہیں ہے"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"کوئی معلومات دستیاب نہیں"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"سکرین کاسٹ ہو رہی ہے"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-uz/strings.xml b/packages/MediaComponents/res/values-uz/strings.xml
new file mode 100644
index 0000000..10a0817
--- /dev/null
+++ b/packages/MediaComponents/res/values-uz/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"Tizim"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Qurilmalar"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Translatsiya tugmasi"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Translatsiya tugmasi. Uzildi"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Translatsiya tugmasi. Ulanmoqda"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Translatsiya tugmasi. Ulandi"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Quyidagiga translatsiya qilish:"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Qurilmalarni topish"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Ulanishni uzish"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Translatsiyani to‘xtatish"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Yopish"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Boshlash"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"To‘xtatib turish"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"To‘xtatish"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Yoyish"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Yig‘ish"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Albom muqovasi"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Tovush balandligi slayderi"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Multimedia tanlamagan"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Hech qanday ma’lumot yo‘q"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Ekranni translatsiya qilish"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-vi/strings.xml b/packages/MediaComponents/res/values-vi/strings.xml
new file mode 100644
index 0000000..7098cca
--- /dev/null
+++ b/packages/MediaComponents/res/values-vi/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"Hệ thống"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Thiết bị"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Nút truyền"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Nút truyền. Đã ngắt kết nối"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Nút truyền. Đang kết nối"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Nút truyền. Đã kết nối"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Truyền tới"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Đang tìm thiết bị"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Ngắt kết nối"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Dừng truyền"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Đóng"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Phát"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Tạm dừng"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Dừng"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Mở rộng"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Thu gọn"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Ảnh bìa album"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Thanh trượt âm lượng"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Không có phương tiện nào được chọn"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Không có thông tin nào"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Đang truyền màn hình"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-zh-rCN/strings.xml b/packages/MediaComponents/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..1e22d01
--- /dev/null
+++ b/packages/MediaComponents/res/values-zh-rCN/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"系统"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"设备"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"投射按钮"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"投射按钮。已断开连接"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"投射按钮。正在连接"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"投射按钮。已连接"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"投射到"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"正在查找设备"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"断开连接"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"停止投射"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"关闭"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"播放"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"暂停"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"停止"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"展开"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"收起"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"专辑封面"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"音量滑块"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"未选择任何媒体"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"没有任何相关信息"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"正在投射屏幕"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-zh-rHK/strings.xml b/packages/MediaComponents/res/values-zh-rHK/strings.xml
new file mode 100644
index 0000000..156e5c2
--- /dev/null
+++ b/packages/MediaComponents/res/values-zh-rHK/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"系統"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"裝置"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"投放按鈕"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"投放按鈕。已解除連接"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"投放按鈕。正在連接"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"投放按鈕。已連接"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"投放至"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"正在尋找裝置"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"中斷連線"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"停止投放"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"關閉"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"播放"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"暫停"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"停止"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"展開"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"收合"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"專輯封面"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"音量滑桿"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"尚未選擇媒體"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"沒有詳細資料"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"正在投放螢幕"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-zh-rTW/strings.xml b/packages/MediaComponents/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..6cafde1
--- /dev/null
+++ b/packages/MediaComponents/res/values-zh-rTW/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"系統"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"裝置"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"投放按鈕"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"投放按鈕;已中斷連線"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"投放按鈕;連線中"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"投放按鈕;已連線"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"投放到"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"正在尋找裝置"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"中斷連線"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"停止投放"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"關閉"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"播放"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"暫停"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"停止"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"展開"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"收合"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"專輯封面"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"音量滑桿"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"未選取任何媒體"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"沒有可用的資訊"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"正在投放螢幕"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values-zu/strings.xml b/packages/MediaComponents/res/values-zu/strings.xml
new file mode 100644
index 0000000..e107c43
--- /dev/null
+++ b/packages/MediaComponents/res/values-zu/strings.xml
@@ -0,0 +1,40 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"Isistimu"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Amadivayisi"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Inkinobho ye-Cast"</string>
+ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Inkinobho yokusakaza. Kunqanyuliwe"</string>
+ <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Inkinobho yokusakaza. Kuyaxhunywa"</string>
+ <string name="mr_cast_button_connected" msgid="5088427771788648085">"Inkinobho yokusakaza. Kuxhunyiwe"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Sakaza ku-"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Ithola amadivayisi"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Nqamula"</string>
+ <string name="mr_controller_stop_casting" msgid="8857886794086583226">"Misa ukusakaza"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Vala"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Dlala"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Misa isikhashana"</string>
+ <string name="mr_controller_stop" msgid="735874641921425123">"Misa"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Nweba"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Goqa"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Ubuciko be-albhamu"</string>
+ <string name="mr_controller_volume_slider" msgid="2361785992211841709">"Isilayida sevolumu"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Ayikho imidiya ekhethiwe"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Alukho ulwazi olutholakalayo"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Isikrini sokusakaza"</string>
+</resources>
diff --git a/packages/MediaComponents/res/values/attrs.xml b/packages/MediaComponents/res/values/attrs.xml
new file mode 100644
index 0000000..6175b11
--- /dev/null
+++ b/packages/MediaComponents/res/values/attrs.xml
@@ -0,0 +1,45 @@
+<?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>
+ <declare-styleable name="MediaRouteButton">
+ <!-- This drawable is a state list where the "checked" state
+ indicates active media routing. Checkable indicates connecting
+ and non-checked / non-checkable indicates
+ that media is playing to the local device only. -->
+ <attr name="externalRouteEnabledDrawable" format="reference" />
+ <!-- Tint to apply to the media route button -->
+ <attr name="mediaRouteButtonTint" format="color" />
+
+ <attr name="android:minWidth" />
+ <attr name="android:minHeight" />
+ </declare-styleable>
+
+ <attr name="mediaRouteButtonStyle" format="reference" />
+ <attr name="mediaRouteCloseDrawable" format="reference" />
+ <attr name="mediaRoutePlayDrawable" format="reference" />
+ <attr name="mediaRoutePauseDrawable" format="reference" />
+ <attr name="mediaRouteStopDrawable" format="reference" />
+ <attr name="mediaRouteAudioTrackDrawable" format="reference" />
+ <attr name="mediaRouteDefaultIconDrawable" format="reference" />
+ <attr name="mediaRouteTvIconDrawable" format="reference" />
+ <attr name="mediaRouteSpeakerIconDrawable" format="reference" />
+ <attr name="mediaRouteSpeakerGroupIconDrawable" format="reference" />
+ <attr name="mediaRouteControlPanelThemeOverlay" format="reference" />
+
+ <attr name="mediaRouteTheme" format="reference" />
+ <attr name="enableControlView" format="boolean" />
+</resources>
diff --git a/packages/MediaComponents/res/values/colors.xml b/packages/MediaComponents/res/values/colors.xml
new file mode 100644
index 0000000..9e071d7
--- /dev/null
+++ b/packages/MediaComponents/res/values/colors.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <integer name="gray">0xff444444</integer>
+</resources>
\ No newline at end of file
diff --git a/packages/MediaComponents/res/values/dimens.xml b/packages/MediaComponents/res/values/dimens.xml
new file mode 100644
index 0000000..91241cd
--- /dev/null
+++ b/packages/MediaComponents/res/values/dimens.xml
@@ -0,0 +1,43 @@
+<?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>
+ <!-- Dialog size -->
+ <eat-comment />
+ <!-- The platform's desired fixed width for a dialog along the major axis
+ (the screen is in landscape). This may be either a fraction or a dimension.-->
+ <dimen name="mr_dialog_fixed_width_major">320dp</dimen>
+ <!-- The platform's desired fixed width for a dialog along the minor axis
+ (the screen is in portrait). This may be either a fraction or a dimension.-->
+ <dimen name="mr_dialog_fixed_width_minor">320dp</dimen>
+
+ <!-- MediaRouteController's volume group list -->
+ <eat-comment />
+ <!-- Maximum height of volume group list. -->
+ <dimen name="mr_controller_volume_group_list_max_height">288dp</dimen>
+ <!-- Height of volume group item. -->
+ <dimen name="mr_controller_volume_group_list_item_height">68dp</dimen>
+ <!-- Size of an item's icon. -->
+ <dimen name="mr_controller_volume_group_list_item_icon_size">24dp</dimen>
+
+ <dimen name="mr_controller_volume_group_list_padding_top">16dp</dimen>
+ <!-- Group list expand/collapse animation duration. -->
+ <integer name="mr_controller_volume_group_list_animation_duration_ms">400</integer>
+ <!-- Group list fade in animation duration. -->
+ <integer name="mr_controller_volume_group_list_fade_in_duration_ms">400</integer>
+ <!-- Group list fade out animation duration. -->
+ <integer name="mr_controller_volume_group_list_fade_out_duration_ms">200</integer>
+</resources>
diff --git a/packages/MediaComponents/res/values/strings.xml b/packages/MediaComponents/res/values/strings.xml
new file mode 100644
index 0000000..35db0e5
--- /dev/null
+++ b/packages/MediaComponents/res/values/strings.xml
@@ -0,0 +1,95 @@
+<?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>
+ <!-- Name for the default system route prior to Jellybean. [CHAR LIMIT=30] -->
+ <string name="mr_system_route_name">System</string>
+
+ <!-- Name for the user route category created when publishing routes to the system in Jellybean and above. [CHAR LIMIT=30] -->
+ <string name="mr_user_route_category_name">Devices</string>
+
+ <!-- String to be shown as a tooltip of MediaRouteButton
+ Cast is the standard android verb for sending content to a remote device. [CHAR LIMIT=50] -->
+ <string name="mr_button_content_description">Cast button</string>
+
+ <!-- Content description of a MediaRouteButton for accessibility support when no remote device is connected.
+ Cast is the standard android verb for sending content to a remote device. [CHAR LIMIT=NONE] -->
+ <string name="mr_cast_button_disconnected">Cast button. Disconnected</string>
+
+ <!-- Content description of a MediaRouteButton for accessibility support while connecting to a remote device.
+ Cast is the standard android verb for sending content to a remote device. [CHAR LIMIT=NONE] -->
+ <string name="mr_cast_button_connecting">Cast button. Connecting</string>
+
+ <!-- Content description of a MediaRouteButton for accessibility support when a remote device is connected.
+ Cast is the standard android verb for sending content to a remote device. [CHAR LIMIT=NONE] -->
+ <string name="mr_cast_button_connected">Cast button. Connected</string>
+
+ <!-- Title of the media route chooser dialog. [CHAR LIMIT=30] -->
+ <string name="mr_chooser_title">Cast to</string>
+
+ <!-- Placeholder text to show when no devices have been found. [CHAR LIMIT=50] -->
+ <string name="mr_chooser_searching">Finding devices</string>
+
+ <!-- Button to disconnect from a media route. [CHAR LIMIT=30] -->
+ <string name="mr_controller_disconnect">Disconnect</string>
+
+ <!-- Button to stop playback and disconnect from a media route. [CHAR LIMIT=30] -->
+ <string name="mr_controller_stop_casting">Stop casting</string>
+
+ <!-- Content description for accessibility (not shown on the screen): dialog close button. [CHAR LIMIT=NONE] -->
+ <string name="mr_controller_close_description">Close</string>
+
+ <!-- Content description for accessibility (not shown on the screen): media play button. [CHAR LIMIT=NONE] -->
+ <string name="mr_controller_play">Play</string>
+
+ <!-- Content description for accessibility (not shown on the screen): media pause button. [CHAR LIMIT=NONE] -->
+ <string name="mr_controller_pause">Pause</string>
+
+ <!-- Content description for accessibility (not shown on the screen): media stop button. [CHAR LIMIT=NONE] -->
+ <string name="mr_controller_stop">Stop</string>
+
+ <!-- Content description for accessibility (not shown on the screen): group expand button. Pressing button shows group members of a selected route group. [CHAR LIMIT=NONE] -->
+ <string name="mr_controller_expand_group">Expand</string>
+
+ <!-- Content description for accessibility (not shown on the screen): group collapse button. Pressing button hides group members of a selected route group. [CHAR LIMIT=NONE] -->
+ <string name="mr_controller_collapse_group">Collapse</string>
+
+ <!-- Content description for accessibility (not shown on the screen): album art button. Clicking on the album art takes user to a predefined activity per media app. [CHAR LIMIT=50] -->
+ <string name="mr_controller_album_art">Album art</string>
+
+ <!-- Content description for accessibility (not shown on the screen): volume slider. [CHAR LIMIT=NONE] -->
+ <string name="mr_controller_volume_slider">Volume slider</string>
+
+ <!-- Placeholder text to show when no media have been selected for playback. [CHAR LIMIT=50] -->
+ <string name="mr_controller_no_media_selected">No media selected</string>
+
+ <!-- Placeholder text to show when no title/description have been found for a given song/video. [CHAR LIMIT=50] -->
+ <string name="mr_controller_no_info_available">No info available</string>
+
+ <!-- Placeholder text indicating that the user is currently casting screen. [CHAR LIMIT=50] -->
+ <string name="mr_controller_casting_screen">Casting screen</string>
+
+ <string name="lockscreen_pause_button_content_description">Pause</string>
+ <string name="lockscreen_play_button_content_description">Play</string>
+ <string name="lockscreen_replay_button_content_description">Replay</string>
+
+ <!-- Text for error alert when a video container is not valid for progressive download/playback. -->
+ <string name="VideoView2_error_text_invalid_progressive_playback">This video isn\'t valid for streaming to this device.</string>
+ <!-- Text for error alert when a video cannot be played. It can be used by any app. -->
+ <string name="VideoView2_error_text_unknown">Can\'t play this video.</string>
+ <!-- Button to close error alert when a video cannot be played. -->
+ <string name="VideoView2_error_button">OK</string>
+</resources>
diff --git a/packages/MediaComponents/res/values/style.xml b/packages/MediaComponents/res/values/style.xml
new file mode 100644
index 0000000..c59380c
--- /dev/null
+++ b/packages/MediaComponents/res/values/style.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <style name="TransportControlsButton">
+ <item name="android:background">@null</item>
+ <item name="android:layout_width">70dp</item>
+ <item name="android:layout_height">40dp</item>
+ </style>
+
+ <style name="TransportControlsButton.Previous">
+ <item name="android:src">@drawable/ic_skip_previous</item>
+ </style>
+
+ <style name="TransportControlsButton.Next">
+ <item name="android:src">@drawable/ic_skip_next</item>
+ </style>
+
+ <style name="TransportControlsButton.Pause">
+ <item name="android:src">@drawable/ic_pause_circle_filled</item>
+ </style>
+
+ <style name="TransportControlsButton.Ffwd">
+ <item name="android:src">@drawable/ic_forward_30</item>
+ </style>
+
+ <style name="TransportControlsButton.Rew">
+ <item name="android:src">@drawable/ic_rewind_10</item>
+ </style>
+
+
+ <style name="TitleBarButton">
+ <item name="android:background">@null</item>
+ <item name="android:layout_width">36dp</item>
+ <item name="android:layout_height">36dp</item>
+ <item name="android:layout_margin">10dp</item>
+ </style>
+
+ <style name="BottomBarButton">
+ <item name="android:background">@null</item>
+ <item name="android:layout_width">44dp</item>
+ <item name="android:layout_height">34dp</item>
+ <item name="android:layout_marginTop">5dp</item>
+ <item name="android:layout_marginBottom">5dp</item>
+ <item name="android:paddingLeft">5dp</item>
+ <item name="android:paddingRight">5dp</item>
+ </style>
+
+ <style name="BottomBarButton.CC">
+ <item name="android:src">@drawable/ic_media_subtitle_disabled</item>
+ </style>
+
+ <style name="BottomBarButton.FullScreen">
+ <item name="android:src">@drawable/ic_fullscreen</item>
+ </style>
+
+ <style name="BottomBarButton.OverflowRight">
+ <item name="android:src">@drawable/ic_chevron_right</item>
+ </style>
+
+ <style name="BottomBarButton.OverflowLeft">
+ <item name="android:src">@drawable/ic_chevron_left</item>
+ </style>
+
+ <style name="BottomBarButton.Settings">
+ <item name="android:src">@drawable/ic_settings</item>
+ </style>
+
+ <style name="BottomBarButton.AspectRatio">
+ <item name="android:src">@drawable/ic_aspect_ratio</item>
+ </style>
+
+ <style name="BottomBarButton.Mute">
+ <item name="android:src">@drawable/ic_mute</item>
+ </style>
+
+</resources>
diff --git a/packages/MediaComponents/res/values/styles.xml b/packages/MediaComponents/res/values/styles.xml
new file mode 100644
index 0000000..bde6900
--- /dev/null
+++ b/packages/MediaComponents/res/values/styles.xml
@@ -0,0 +1,33 @@
+<?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>
+ <style name="Widget.MediaRouter.MediaRouteButton"
+ parent="Widget.AppCompat.ActionButton">
+ <item name="externalRouteEnabledDrawable">@drawable/mr_button_dark</item>
+ </style>
+
+ <style name="Widget.MediaRouter.Light.MediaRouteButton"
+ parent="Widget.AppCompat.Light.ActionButton">
+ <item name="externalRouteEnabledDrawable">@drawable/mr_button_light</item>
+ </style>
+
+ <style name="TextAppearance.MediaRouter.Title" parent="TextAppearance.AppCompat.Title" />
+
+ <style name="TextAppearance.MediaRouter.PrimaryText" parent="TextAppearance.AppCompat.Subhead" />
+
+ <style name="TextAppearance.MediaRouter.SecondaryText" parent="TextAppearance.AppCompat.Body1" />
+</resources>
diff --git a/packages/MediaComponents/res/values/symbols.xml b/packages/MediaComponents/res/values/symbols.xml
new file mode 100644
index 0000000..ee0e8c6
--- /dev/null
+++ b/packages/MediaComponents/res/values/symbols.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 2017, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** 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>
+ <!--java-symbol type="id" name="cc" />
+ <java-symbol type="id" name="ffwd" />
+ <java-symbol type="id" name="mediacontroller_progress" />
+ <java-symbol type="id" name="next" />
+ <java-symbol type="id" name="pause" />
+ <java-symbol type="id" name="prev" />
+ <java-symbol type="id" name="rew" />
+ <java-symbol type="id" name="time" />
+ <java-symbol type="id" name="time_current" /-->
+</resources>
\ No newline at end of file
diff --git a/packages/MediaComponents/res/values/themes.xml b/packages/MediaComponents/res/values/themes.xml
new file mode 100644
index 0000000..51098e9
--- /dev/null
+++ b/packages/MediaComponents/res/values/themes.xml
@@ -0,0 +1,75 @@
+<?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>
+
+ <style name="Theme.MediaRouter" parent="ThemeOverlay.AppCompat.Dark">
+ <item name="windowNoTitle">true</item>
+ <item name="mediaRouteButtonStyle">@style/Widget.MediaRouter.MediaRouteButton</item>
+
+ <item name="mediaRouteCloseDrawable">@drawable/mr_dialog_close_dark</item>
+ <item name="mediaRoutePlayDrawable">@drawable/mr_media_play_dark</item>
+ <item name="mediaRoutePauseDrawable">@drawable/mr_media_pause_dark</item>
+ <item name="mediaRouteStopDrawable">@drawable/mr_media_stop_dark</item>
+ <item name="mediaRouteAudioTrackDrawable">@drawable/mr_vol_type_audiotrack_dark</item>
+ <item name="mediaRouteDefaultIconDrawable">@drawable/ic_mr_button_disconnected_dark</item>
+ <item name="mediaRouteTvIconDrawable">@drawable/ic_vol_type_tv_dark</item>
+ <item name="mediaRouteSpeakerIconDrawable">@drawable/ic_vol_type_speaker_dark</item>
+ <item name="mediaRouteSpeakerGroupIconDrawable">@drawable/ic_vol_type_speaker_group_dark</item>
+
+ <item name="mediaRouteControlPanelThemeOverlay">@null</item>
+ </style>
+
+ <style name="Theme.MediaRouter.LightControlPanel">
+ <item name="mediaRouteControlPanelThemeOverlay">@style/ThemeOverlay.MediaRouter.Light</item>
+ </style>
+
+ <style name="Theme.MediaRouter.Light" parent="ThemeOverlay.AppCompat.Light">
+ <item name="windowNoTitle">true</item>
+ <item name="mediaRouteButtonStyle">@style/Widget.MediaRouter.Light.MediaRouteButton</item>
+
+ <item name="mediaRouteCloseDrawable">@drawable/mr_dialog_close_light</item>
+ <item name="mediaRoutePlayDrawable">@drawable/mr_media_play_light</item>
+ <item name="mediaRoutePauseDrawable">@drawable/mr_media_pause_light</item>
+ <item name="mediaRouteStopDrawable">@drawable/mr_media_stop_light</item>
+ <item name="mediaRouteAudioTrackDrawable">@drawable/mr_vol_type_audiotrack_light</item>
+ <item name="mediaRouteDefaultIconDrawable">@drawable/ic_mr_button_grey</item>
+ <item name="mediaRouteTvIconDrawable">@drawable/ic_vol_type_tv_light</item>
+ <item name="mediaRouteSpeakerIconDrawable">@drawable/ic_vol_type_speaker_light</item>
+ <item name="mediaRouteSpeakerGroupIconDrawable">@drawable/ic_vol_type_speaker_group_light</item>
+
+ <item name="mediaRouteControlPanelThemeOverlay">@null</item>
+ </style>
+
+ <style name="Theme.MediaRouter.Light.DarkControlPanel">
+ <item name="mediaRouteControlPanelThemeOverlay">@style/ThemeOverlay.MediaRouter.Dark</item>
+ </style>
+
+ <style name="ThemeOverlay.MediaRouter.Dark" parent="ThemeOverlay.AppCompat.Dark">
+ <item name="mediaRoutePlayDrawable">@drawable/mr_media_play_dark</item>
+ <item name="mediaRoutePauseDrawable">@drawable/mr_media_pause_dark</item>
+ <item name="mediaRouteStopDrawable">@drawable/mr_media_stop_dark</item>
+ <item name="mediaRouteAudioTrackDrawable">@drawable/mr_vol_type_audiotrack_dark</item>
+
+ </style>
+ <style name="ThemeOverlay.MediaRouter.Light" parent="ThemeOverlay.AppCompat.Light">
+ <item name="mediaRoutePlayDrawable">@drawable/mr_media_play_light</item>
+ <item name="mediaRoutePauseDrawable">@drawable/mr_media_pause_light</item>
+ <item name="mediaRouteStopDrawable">@drawable/mr_media_stop_light</item>
+ <item name="mediaRouteAudioTrackDrawable">@drawable/mr_vol_type_audiotrack_light</item>
+ </style>
+
+</resources>
diff --git a/packages/MediaComponents/src/com/android/media/MediaBrowser2Impl.java b/packages/MediaComponents/src/com/android/media/MediaBrowser2Impl.java
new file mode 100644
index 0000000..511eed1
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/media/MediaBrowser2Impl.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.media;
+
+import android.content.Context;
+import android.media.IMediaSession2;
+import android.media.MediaBrowser2;
+import android.media.MediaBrowser2.BrowserCallback;
+import android.media.MediaSession2.CommandButton;
+import android.media.SessionToken2;
+import android.media.update.MediaBrowser2Provider;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+
+public class MediaBrowser2Impl extends MediaController2Impl implements MediaBrowser2Provider {
+ private final String TAG = "MediaBrowser2";
+ private final boolean DEBUG = true; // TODO(jaewan): change.
+
+ private final MediaBrowser2 mInstance;
+ private final MediaBrowser2.BrowserCallback mCallback;
+
+ public MediaBrowser2Impl(Context context, MediaBrowser2 instance, SessionToken2 token,
+ Executor executor, BrowserCallback callback) {
+ super(context, instance, token, executor, callback);
+ mInstance = instance;
+ mCallback = callback;
+ }
+
+ @Override
+ public void getBrowserRoot_impl(Bundle rootHints) {
+ final IMediaSession2 binder = getSessionBinder();
+ if (binder != null) {
+ try {
+ binder.getBrowserRoot(getControllerStub(), rootHints);
+ } catch (RemoteException e) {
+ // TODO(jaewan): Handle disconnect.
+ if (DEBUG) {
+ Log.w(TAG, "Cannot connect to the service or the session is gone", e);
+ }
+ }
+ } else {
+ Log.w(TAG, "Session isn't active", new IllegalStateException());
+ }
+ }
+
+ @Override
+ public void subscribe_impl(String parentId, Bundle options) {
+ // TODO(jaewan): Implement
+ }
+
+ @Override
+ public void unsubscribe_impl(String parentId, Bundle options) {
+ // TODO(jaewan): Implement
+ }
+
+ @Override
+ public void getItem_impl(String mediaId) {
+ // TODO(jaewan): Implement
+ }
+
+ @Override
+ public void getChildren_impl(String parentId, int page, int pageSize, Bundle options) {
+ // TODO(jaewan): Implement
+ }
+
+ @Override
+ public void search_impl(String query, int page, int pageSize, Bundle extras) {
+ // TODO(jaewan): Implement
+ }
+
+ public void onGetRootResult(
+ final Bundle rootHints, final String rootMediaId, final Bundle rootExtra) {
+ getCallbackExecutor().execute(() -> {
+ mCallback.onGetRootResult(rootHints, rootMediaId, rootExtra);
+ });
+ }
+
+ public void onCustomLayoutChanged(final List<CommandButton> layout) {
+ getCallbackExecutor().execute(() -> {
+ mCallback.onCustomLayoutChanged(layout);
+ });
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/media/MediaController2Impl.java b/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
new file mode 100644
index 0000000..43f8473
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
@@ -0,0 +1,600 @@
+/*
+ * 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.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.media.IMediaSession2;
+import android.media.IMediaSession2Callback;
+import android.media.MediaController2.PlaybackInfo;
+import android.media.MediaItem2;
+import android.media.MediaSession2;
+import android.media.MediaSession2.Command;
+import android.media.MediaSession2.CommandButton;
+import android.media.MediaSession2.CommandGroup;
+import android.media.MediaController2;
+import android.media.MediaController2.ControllerCallback;
+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;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.support.annotation.GuardedBy;
+import android.util.Log;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+public class MediaController2Impl implements MediaController2Provider {
+ private static final String TAG = "MediaController2";
+ private static final boolean DEBUG = true; // TODO(jaewan): Change
+
+ private final MediaController2 mInstance;
+
+ /**
+ * Flag used by MediaController2Record to filter playback callback.
+ */
+ static final int CALLBACK_FLAG_PLAYBACK = 0x1;
+
+ static final int REQUEST_CODE_ALL = 0;
+
+ private final Object mLock = new Object();
+
+ private final Context mContext;
+ private final MediaSession2CallbackStub mSessionCallbackStub;
+ private final SessionToken2 mToken;
+ private final ControllerCallback mCallback;
+ private final Executor mCallbackExecutor;
+ private final IBinder.DeathRecipient mDeathRecipient;
+
+ @GuardedBy("mLock")
+ private final List<PlaybackListenerHolder> mPlaybackListeners = new ArrayList<>();
+ @GuardedBy("mLock")
+ private SessionServiceConnection mServiceConnection;
+ @GuardedBy("mLock")
+ private boolean mIsReleased;
+
+ // Assignment should be used with the lock hold, but should be used without a lock to prevent
+ // potential deadlock.
+ // Postfix -Binder is added to explicitly show that it's potentially remote process call.
+ // Technically -Interface is more correct, but it may misread that it's interface (vs class)
+ // so let's keep this postfix until we find better postfix.
+ @GuardedBy("mLock")
+ private volatile IMediaSession2 mSessionBinder;
+
+ // TODO(jaewan): Require session activeness changed listener, because controller can be
+ // available when the session's player is null.
+ public MediaController2Impl(Context context, MediaController2 instance, SessionToken2 token,
+ Executor executor, ControllerCallback callback) {
+ mInstance = instance;
+
+ if (context == null) {
+ throw new IllegalArgumentException("context shouldn't be null");
+ }
+ if (token == null) {
+ throw new IllegalArgumentException("token shouldn't be null");
+ }
+ if (callback == null) {
+ throw new IllegalArgumentException("callback shouldn't be null");
+ }
+ if (executor == null) {
+ throw new IllegalArgumentException("executor shouldn't be null");
+ }
+ mContext = context;
+ mSessionCallbackStub = new MediaSession2CallbackStub(this);
+ mToken = token;
+ mCallback = callback;
+ mCallbackExecutor = executor;
+ mDeathRecipient = () -> {
+ mInstance.close();
+ };
+
+ mSessionBinder = null;
+
+ if (token.getSessionBinder() == null) {
+ mServiceConnection = new SessionServiceConnection();
+ connectToService();
+ } else {
+ mServiceConnection = null;
+ connectToSession(token.getSessionBinder());
+ }
+ }
+
+ // Should be only called by constructor.
+ private void connectToService() {
+ // Service. Needs to get fresh binder whenever connection is needed.
+ final Intent intent = new Intent(MediaSessionService2.SERVICE_INTERFACE);
+ intent.setClassName(mToken.getPackageName(), mToken.getServiceName());
+
+ // Use bindService() instead of startForegroundService() to start session service for three
+ // reasons.
+ // 1. Prevent session service owner's stopSelf() from destroying service.
+ // With the startForegroundService(), service's call of stopSelf() will trigger immediate
+ // onDestroy() calls on the main thread even when onConnect() is running in another
+ // thread.
+ // 2. Minimize APIs for developers to take care about.
+ // With bindService(), developers only need to take care about Service.onBind()
+ // but Service.onStartCommand() should be also taken care about with the
+ // startForegroundService().
+ // 3. Future support for UI-less playback
+ // If a service wants to keep running, it should be either foreground service or
+ // bounded service. But there had been request for the feature for system apps
+ // and using bindService() will be better fit with it.
+ // TODO(jaewan): Use bindServiceAsUser()??
+ boolean result = mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
+ if (!result) {
+ Log.w(TAG, "bind to " + mToken + " failed");
+ } else if (DEBUG) {
+ Log.d(TAG, "bind to " + mToken + " success");
+ }
+ }
+
+ private void connectToSession(IMediaSession2 sessionBinder) {
+ try {
+ sessionBinder.connect(mContext.getPackageName(), mSessionCallbackStub);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to call connection request. Framework will retry"
+ + " automatically");
+ }
+ }
+
+ @Override
+ public void close_impl() {
+ if (DEBUG) {
+ Log.d(TAG, "relese from " + mToken);
+ }
+ final IMediaSession2 binder;
+ synchronized (mLock) {
+ if (mIsReleased) {
+ // Prevent re-enterance from the ControllerCallback.onDisconnected()
+ return;
+ }
+ mIsReleased = true;
+ if (mServiceConnection != null) {
+ mContext.unbindService(mServiceConnection);
+ mServiceConnection = null;
+ }
+ mPlaybackListeners.clear();
+ binder = mSessionBinder;
+ mSessionBinder = null;
+ mSessionCallbackStub.destroy();
+ }
+ if (binder != null) {
+ try {
+ binder.asBinder().unlinkToDeath(mDeathRecipient, 0);
+ binder.release(mSessionCallbackStub);
+ } catch (RemoteException e) {
+ // No-op.
+ }
+ }
+ mCallbackExecutor.execute(() -> {
+ mCallback.onDisconnected();
+ });
+ }
+
+ IMediaSession2 getSessionBinder() {
+ return mSessionBinder;
+ }
+
+ MediaSession2CallbackStub getControllerStub() {
+ return mSessionCallbackStub;
+ }
+
+ Executor getCallbackExecutor() {
+ return mCallbackExecutor;
+ }
+
+ @Override
+ public SessionToken2 getSessionToken_impl() {
+ return mToken;
+ }
+
+ @Override
+ public boolean isConnected_impl() {
+ final IMediaSession2 binder = mSessionBinder;
+ return binder != null;
+ }
+
+ @Override
+ public void play_impl() {
+ sendCommand(MediaSession2.COMMAND_CODE_PLAYBACK_START);
+ }
+
+ @Override
+ public void pause_impl() {
+ sendCommand(MediaSession2.COMMAND_CODE_PLAYBACK_PAUSE);
+ }
+
+ @Override
+ public void stop_impl() {
+ sendCommand(MediaSession2.COMMAND_CODE_PLAYBACK_STOP);
+ }
+
+ @Override
+ public void skipToPrevious_impl() {
+ sendCommand(MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_PREV_ITEM);
+ }
+
+ @Override
+ public void skipToNext_impl() {
+ sendCommand(MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_NEXT_ITEM);
+ }
+
+ private void sendCommand(int code) {
+ // TODO(jaewan): optimization) Cache Command objects?
+ Command command = new Command(code);
+ // TODO(jaewan): Check if the command is in the allowed group.
+
+ final IMediaSession2 binder = mSessionBinder;
+ if (binder != null) {
+ try {
+ binder.sendCommand(mSessionCallbackStub, command.toBundle(), null);
+ } 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());
+ }
+ }
+
+ //////////////////////////////////////////////////////////////////////////////////////
+ // TODO(jaewan): Implement follows
+ //////////////////////////////////////////////////////////////////////////////////////
+ @Override
+ public PendingIntent getSessionActivity_impl() {
+ // TODO(jaewan): Implement
+ return null;
+ }
+
+ @Override
+ public int getRatingType_impl() {
+ // TODO(jaewan): Implement
+ return 0;
+ }
+
+ @Override
+ public void setVolumeTo_impl(int value, int flags) {
+ // TODO(jaewan): Implement
+ }
+
+ @Override
+ public void adjustVolume_impl(int direction, int flags) {
+ // TODO(jaewan): Implement
+ }
+
+ @Override
+ public PlaybackInfo getPlaybackInfo_impl() {
+ // TODO(jaewan): Implement
+ return null;
+ }
+
+ @Override
+ public void prepareFromUri_impl(Uri uri, Bundle extras) {
+ // TODO(jaewan): Implement
+ }
+
+ @Override
+ public void prepareFromSearch_impl(String query, Bundle extras) {
+ // TODO(jaewan): Implement
+ }
+
+ @Override
+ public void prepareMediaId_impl(String mediaId, Bundle extras) {
+ // TODO(jaewan): Implement
+ }
+
+ @Override
+ public void playFromSearch_impl(String query, Bundle extras) {
+ // TODO(jaewan): Implement
+ }
+
+ @Override
+ public void playFromUri_impl(String uri, Bundle extras) {
+ // TODO(jaewan): Implement
+ }
+
+ @Override
+ public void playFromMediaId_impl(String mediaId, Bundle extras) {
+ // TODO(jaewan): Implement
+ }
+
+ @Override
+ public void setRating_impl(Rating2 rating) {
+ // TODO(jaewan): Implement
+ }
+
+ @Override
+ public void sendCustomCommand_impl(Command command, Bundle args, ResultReceiver cb) {
+ // TODO(jaewan): Implement
+ }
+
+ @Override
+ public List<MediaItem2> getPlaylist_impl() {
+ // TODO(jaewan): Implement
+ return null;
+ }
+
+ @Override
+ public void prepare_impl() {
+ // TODO(jaewan): Implement
+ }
+
+ @Override
+ public void fastForward_impl() {
+ // TODO(jaewan): Implement
+ }
+
+ @Override
+ public void rewind_impl() {
+ // TODO(jaewan): Implement
+ }
+
+ @Override
+ public void seekTo_impl(long pos) {
+ // TODO(jaewan): Implement
+ }
+
+ @Override
+ public void setCurrentPlaylistItem_impl(int index) {
+ // TODO(jaewan): Implement
+ }
+
+ @Override
+ public PlaybackState2 getPlaybackState_impl() {
+ // TODO(jaewan): Implement
+ return null;
+ }
+
+ @Override
+ public void removePlaylistItem_impl(MediaItem2 index) {
+ // TODO(jaewan): Implement
+ }
+
+ @Override
+ public void addPlaylistItem_impl(int index, MediaItem2 item) {
+ // TODO(jaewan): Implement
+ }
+
+ @Override
+ public PlaylistParams getPlaylistParam_impl() {
+ // TODO(jaewan): Implement
+ return null;
+ }
+
+ ///////////////////////////////////////////////////
+ // Protected or private methods
+ ///////////////////////////////////////////////////
+ // Should be used without a lock to prevent potential deadlock.
+ private void registerCallbackForPlaybackNotLocked() {
+ final IMediaSession2 binder = mSessionBinder;
+ if (binder != null) {
+ try {
+ binder.registerCallback(mSessionCallbackStub,
+ CALLBACK_FLAG_PLAYBACK, REQUEST_CODE_ALL);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Cannot connect to the service or the session is gone", e);
+ }
+ }
+ }
+
+ private void pushPlaybackStateChanges(final PlaybackState2 state) {
+ synchronized (mLock) {
+ for (int i = 0; i < mPlaybackListeners.size(); i++) {
+ mPlaybackListeners.get(i).postPlaybackChange(state);
+ }
+ }
+ }
+
+ // Called when the result for connecting to the session was delivered.
+ // Should be used without a lock to prevent potential deadlock.
+ private void onConnectionChangedNotLocked(IMediaSession2 sessionBinder,
+ CommandGroup commandGroup) {
+ if (DEBUG) {
+ Log.d(TAG, "onConnectionChangedNotLocked sessionBinder=" + sessionBinder
+ + ", commands=" + commandGroup);
+ }
+ boolean release = false;
+ try {
+ if (sessionBinder == null || commandGroup == null) {
+ // Connection rejected.
+ release = true;
+ return;
+ }
+ boolean registerCallbackForPlaybackNeeded;
+ synchronized (mLock) {
+ if (mIsReleased) {
+ return;
+ }
+ if (mSessionBinder != null) {
+ Log.e(TAG, "Cannot be notified about the connection result many times."
+ + " Probably a bug or malicious app.");
+ release = true;
+ return;
+ }
+ mSessionBinder = sessionBinder;
+ try {
+ // Implementation for the local binder is no-op,
+ // so can be used without worrying about deadlock.
+ mSessionBinder.asBinder().linkToDeath(mDeathRecipient, 0);
+ } catch (RemoteException e) {
+ if (DEBUG) {
+ Log.d(TAG, "Session died too early.", e);
+ }
+ release = true;
+ return;
+ }
+ registerCallbackForPlaybackNeeded = !mPlaybackListeners.isEmpty();
+ }
+ // TODO(jaewan): Keep commands to prevents illegal API calls.
+ mCallbackExecutor.execute(() -> {
+ mCallback.onConnected(commandGroup);
+ });
+ if (registerCallbackForPlaybackNeeded) {
+ registerCallbackForPlaybackNotLocked();
+ }
+ } finally {
+ if (release) {
+ // Trick to call release() without holding the lock, to prevent potential deadlock
+ // with the developer's custom lock within the ControllerCallback.onDisconnected().
+ mInstance.close();
+ }
+ }
+ }
+
+ // TODO(jaewan): Pull out this from the controller2, and rename it to the MediaController2Stub
+ // or MediaBrowser2Stub.
+ static class MediaSession2CallbackStub extends IMediaSession2Callback.Stub {
+ private final WeakReference<MediaController2Impl> mController;
+
+ private MediaSession2CallbackStub(MediaController2Impl controller) {
+ mController = new WeakReference<>(controller);
+ }
+
+ private MediaController2Impl getController() throws IllegalStateException {
+ final MediaController2Impl controller = mController.get();
+ if (controller == null) {
+ throw new IllegalStateException("Controller is released");
+ }
+ return controller;
+ }
+
+ // TODO(jaewan): Refactor code to get rid of these pattern.
+ private MediaBrowser2Impl getBrowser() throws IllegalStateException {
+ final MediaController2Impl controller = getController();
+ if (controller instanceof MediaBrowser2Impl) {
+ return (MediaBrowser2Impl) controller;
+ }
+ return null;
+ }
+
+ public void destroy() {
+ mController.clear();
+ }
+
+ @Override
+ public void onPlaybackStateChanged(Bundle state) throws RuntimeException {
+ final MediaController2Impl controller = getController();
+ controller.pushPlaybackStateChanges(PlaybackState2.fromBundle(state));
+ }
+
+ @Override
+ public void onConnectionChanged(IMediaSession2 sessionBinder, Bundle commandGroup)
+ throws RuntimeException {
+ final MediaController2Impl controller;
+ try {
+ controller = getController();
+ } catch (IllegalStateException e) {
+ Log.w(TAG, "Don't fail silently here. Highly likely a bug");
+ return;
+ }
+ controller.onConnectionChangedNotLocked(
+ sessionBinder, CommandGroup.fromBundle(commandGroup));
+ }
+
+ @Override
+ public void onGetRootResult(Bundle rootHints, String rootMediaId, Bundle rootExtra)
+ throws RuntimeException {
+ final MediaBrowser2Impl browser;
+ try {
+ browser = getBrowser();
+ } catch (IllegalStateException e) {
+ Log.w(TAG, "Don't fail silently here. Highly likely a bug");
+ return;
+ }
+ if (browser == null) {
+ // TODO(jaewan): Revisit here. Could be a bug
+ return;
+ }
+ browser.onGetRootResult(rootHints, rootMediaId, rootExtra);
+ }
+
+ @Override
+ public void onCustomLayoutChanged(List<Bundle> commandButtonlist) {
+ if (commandButtonlist == null) {
+ // Illegal call. Ignore
+ return;
+ }
+ final MediaBrowser2Impl browser;
+ try {
+ browser = getBrowser();
+ } catch (IllegalStateException e) {
+ Log.w(TAG, "Don't fail silently here. Highly likely a bug");
+ return;
+ }
+ if (browser == null) {
+ // TODO(jaewan): Revisit here. Could be a bug
+ return;
+ }
+ List<CommandButton> layout = new ArrayList<>();
+ for (int i = 0; i < commandButtonlist.size(); i++) {
+ CommandButton button = CommandButton.fromBundle(commandButtonlist.get(i));
+ if (button != null) {
+ layout.add(button);
+ }
+ }
+ browser.onCustomLayoutChanged(layout);
+ }
+ }
+
+ // This will be called on the main thread.
+ private class SessionServiceConnection implements ServiceConnection {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ // Note that it's always main-thread.
+ if (DEBUG) {
+ Log.d(TAG, "onServiceConnected " + name + " " + this);
+ }
+ // Sanity check
+ if (!mToken.getPackageName().equals(name.getPackageName())) {
+ Log.wtf(TAG, name + " was connected, but expected pkg="
+ + mToken.getPackageName() + " with id=" + mToken.getId());
+ return;
+ }
+ final IMediaSession2 sessionBinder = IMediaSession2.Stub.asInterface(service);
+ connectToSession(sessionBinder);
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ // Temporal lose of the binding because of the service crash. System will automatically
+ // rebind, so just no-op.
+ // TODO(jaewan): Really? Either disconnect cleanly or
+ if (DEBUG) {
+ Log.w(TAG, "Session service " + name + " is disconnected.");
+ }
+ }
+
+ @Override
+ public void onBindingDied(ComponentName name) {
+ // Permanent lose of the binding because of the service package update or removed.
+ // This SessionServiceRecord will be removed accordingly, but forget session binder here
+ // for sure.
+ mInstance.close();
+ }
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/media/MediaLibraryService2Impl.java b/packages/MediaComponents/src/com/android/media/MediaLibraryService2Impl.java
new file mode 100644
index 0000000..bbb3411
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/media/MediaLibraryService2Impl.java
@@ -0,0 +1,91 @@
+/*
+ * 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.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.media.MediaLibraryService2;
+import android.media.MediaLibraryService2.MediaLibrarySession;
+import android.media.MediaLibraryService2.MediaLibrarySessionCallback;
+import android.media.MediaPlayerBase;
+import android.media.MediaSession2;
+import android.media.MediaSession2.ControllerInfo;
+import android.media.MediaSession2.SessionCallback;
+import android.media.MediaSessionService2;
+import android.media.VolumeProvider;
+import android.media.update.MediaLibraryService2Provider;
+import android.os.Bundle;
+
+import java.util.concurrent.Executor;
+
+public class MediaLibraryService2Impl extends MediaSessionService2Impl implements
+ MediaLibraryService2Provider {
+ private final MediaSessionService2 mInstance;
+ private MediaLibrarySession mLibrarySession;
+
+ public MediaLibraryService2Impl(MediaLibraryService2 instance) {
+ super(instance);
+ mInstance = instance;
+ }
+
+ @Override
+ public void onCreate_impl() {
+ super.onCreate_impl();
+
+ // Effectively final
+ MediaSession2 session = getSession();
+ if (!(session instanceof MediaLibrarySession)) {
+ throw new RuntimeException("Expected MediaLibrarySession, but returned MediaSession2");
+ }
+ mLibrarySession = (MediaLibrarySession) getSession();
+ }
+
+ @Override
+ Intent createServiceIntent() {
+ Intent serviceIntent = new Intent(mInstance, mInstance.getClass());
+ serviceIntent.setAction(MediaLibraryService2.SERVICE_INTERFACE);
+ return serviceIntent;
+ }
+
+ public static class MediaLibrarySessionImpl extends MediaSession2Impl
+ implements MediaLibrarySessionProvider {
+ private final MediaLibrarySession mInstance;
+ private final MediaLibrarySessionCallback mCallback;
+
+ public MediaLibrarySessionImpl(Context context, MediaLibrarySession instance,
+ MediaPlayerBase player, String id, VolumeProvider volumeProvider, int ratingType,
+ PendingIntent sessionActivity, Executor callbackExecutor,
+ MediaLibrarySessionCallback callback) {
+ super(context, instance, player, id, volumeProvider, ratingType, sessionActivity,
+ callbackExecutor, callback);
+ mInstance = instance;
+ mCallback = callback;
+ }
+
+ @Override
+ public void notifyChildrenChanged_impl(ControllerInfo controller, String parentId,
+ Bundle options) {
+ // TODO(jaewan): Implements
+ }
+
+ @Override
+ public void notifyChildrenChanged_impl(String parentId, Bundle options) {
+ // TODO(jaewan): Implements
+ }
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java b/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
new file mode 100644
index 0000000..22a3187
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
@@ -0,0 +1,481 @@
+/*
+ * 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.Manifest.permission;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.media.AudioAttributes;
+import android.media.IMediaSession2Callback;
+import android.media.MediaItem2;
+import android.media.MediaPlayerBase;
+import android.media.MediaSession2;
+import android.media.MediaSession2.Builder;
+import android.media.MediaSession2.Command;
+import android.media.MediaSession2.CommandButton;
+import android.media.MediaSession2.CommandGroup;
+import android.media.MediaSession2.ControllerInfo;
+import android.media.MediaSession2.PlaylistParams;
+import android.media.MediaSession2.SessionCallback;
+import android.media.PlaybackState2;
+import android.media.SessionToken2;
+import android.media.VolumeProvider;
+import android.media.session.MediaSessionManager;
+import android.media.update.MediaSession2Provider;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.ResultReceiver;
+import android.util.Log;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+public class MediaSession2Impl implements MediaSession2Provider {
+ private static final String TAG = "MediaSession2";
+ private static final boolean DEBUG = true;//Log.isLoggable(TAG, Log.DEBUG);
+
+ private final MediaSession2 mInstance;
+
+ private final Context mContext;
+ private final String mId;
+ private final Handler mHandler;
+ private final Executor mCallbackExecutor;
+ private final MediaSession2Stub mSessionStub;
+ private final SessionToken2 mSessionToken;
+
+ private MediaPlayerBase mPlayer;
+
+ private final List<PlaybackListenerHolder> mListeners = new ArrayList<>();
+ private MyPlaybackListener mListener;
+ private MediaSession2 instance;
+
+ /**
+ * Can be only called by the {@link Builder#build()}.
+ *
+ * @param instance
+ * @param context
+ * @param player
+ * @param id
+ * @param callback
+ * @param volumeProvider
+ * @param ratingType
+ * @param sessionActivity
+ */
+ public MediaSession2Impl(Context context, MediaSession2 instance, MediaPlayerBase player,
+ String id, VolumeProvider volumeProvider, int ratingType, PendingIntent sessionActivity,
+ Executor callbackExecutor, SessionCallback callback) {
+ mInstance = instance;
+ // TODO(jaewan): Keep other params.
+
+ // Argument checks are done by builder already.
+ // Initialize finals first.
+ mContext = context;
+ mId = id;
+ mHandler = new Handler(Looper.myLooper());
+ mCallbackExecutor = callbackExecutor;
+ mSessionStub = new MediaSession2Stub(this, callback);
+ // Ask server to create session token for following reasons.
+ // 1. Make session ID unique per package.
+ // Server can only know if the package has another process and has another session
+ // with the same id. Let server check this.
+ // Note that 'ID is unique per package' is important for controller to distinguish
+ // a session in another package.
+ // 2. Easier to know the type of session.
+ // Session created here can be the session service token. In order distinguish,
+ // we need to iterate AndroidManifest.xml but it's already done by the server.
+ // Let server to create token with the type.
+ MediaSessionManager manager =
+ (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
+ mSessionToken = manager.createSessionToken(mContext.getPackageName(), mId, mSessionStub);
+ if (mSessionToken == null) {
+ throw new IllegalStateException("Session with the same id is already used by"
+ + " another process. Use MediaController2 instead.");
+ }
+
+ setPlayerInternal(player);
+ }
+
+ // TODO(jaewan): Add explicit release() and do not remove session object with the
+ // setPlayer(null). Token can be available when player is null, and
+ // controller can also attach to session.
+ @Override
+ public void setPlayer_impl(MediaPlayerBase player, VolumeProvider volumeProvider) throws IllegalArgumentException {
+ ensureCallingThread();
+ if (player == null) {
+ throw new IllegalArgumentException("player shouldn't be null");
+ }
+ setPlayerInternal(player);
+ }
+
+ private void setPlayerInternal(MediaPlayerBase player) {
+ if (mPlayer == player) {
+ // Player didn't changed. No-op.
+ return;
+ }
+ // TODO(jaewan): Find equivalent for the executor
+ //mHandler.removeCallbacksAndMessages(null);
+ if (mPlayer != null && mListener != null) {
+ // This might not work for a poorly implemented player.
+ mPlayer.removePlaybackListener(mListener);
+ }
+ mListener = new MyPlaybackListener(this, player);
+ player.addPlaybackListener(mCallbackExecutor, mListener);
+ notifyPlaybackStateChanged(player.getPlaybackState());
+ mPlayer = player;
+ }
+
+ @Override
+ public void close_impl() {
+ // Flush any pending messages.
+ mHandler.removeCallbacksAndMessages(null);
+ if (mSessionStub != null) {
+ if (DEBUG) {
+ Log.d(TAG, "session is now unavailable, id=" + mId);
+ }
+ // Invalidate previously published session stub.
+ mSessionStub.destroyNotLocked();
+ }
+ }
+
+ @Override
+ public MediaPlayerBase getPlayer_impl() {
+ return getPlayer();
+ }
+
+ // TODO(jaewan): Change this to @NonNull
+ @Override
+ public SessionToken2 getToken_impl() {
+ return mSessionToken;
+ }
+
+ @Override
+ public List<ControllerInfo> getConnectedControllers_impl() {
+ return mSessionStub.getControllers();
+ }
+
+ @Override
+ public void setAudioAttributes_impl(AudioAttributes attributes) {
+ // implement
+ }
+
+ @Override
+ public void setAudioFocusRequest_impl(int focusGain) {
+ // implement
+ }
+
+ @Override
+ public void play_impl() {
+ ensureCallingThread();
+ ensurePlayer();
+ mPlayer.play();
+ }
+
+ @Override
+ public void pause_impl() {
+ ensureCallingThread();
+ ensurePlayer();
+ mPlayer.pause();
+ }
+
+ @Override
+ public void stop_impl() {
+ ensureCallingThread();
+ ensurePlayer();
+ mPlayer.stop();
+ }
+
+ @Override
+ public void skipToPrevious_impl() {
+ ensureCallingThread();
+ ensurePlayer();
+ mPlayer.skipToPrevious();
+ }
+
+ @Override
+ public void skipToNext_impl() {
+ ensureCallingThread();
+ ensurePlayer();
+ mPlayer.skipToNext();
+ }
+
+ @Override
+ public void setCustomLayout_impl(ControllerInfo controller, List<CommandButton> layout) {
+ ensureCallingThread();
+ if (controller == null) {
+ throw new IllegalArgumentException("controller shouldn't be null");
+ }
+ if (layout == null) {
+ throw new IllegalArgumentException("layout shouldn't be null");
+ }
+ mSessionStub.notifyCustomLayoutNotLocked(controller, layout);
+ }
+
+ //////////////////////////////////////////////////////////////////////////////////////
+ // TODO(jaewan): Implement follows
+ //////////////////////////////////////////////////////////////////////////////////////
+ @Override
+ public void setPlayer_impl(MediaPlayerBase player) {
+ // TODO(jaewan): Implement
+ }
+
+ @Override
+ public void setAllowedCommands_impl(ControllerInfo controller, CommandGroup commands) {
+ // TODO(jaewan): Implement
+ }
+
+ @Override
+ public void notifyMetadataChanged_impl() {
+ // TODO(jaewan): Implement
+ }
+
+ @Override
+ public void sendCustomCommand_impl(ControllerInfo controller, Command command, Bundle args,
+ ResultReceiver receiver) {
+ // TODO(jaewan): Implement
+ }
+
+ @Override
+ public void sendCustomCommand_impl(Command command, Bundle args) {
+ // TODO(jaewan): Implement
+ }
+
+ @Override
+ public void setPlaylist_impl(List<MediaItem2> playlist, PlaylistParams param) {
+ // TODO(jaewan): Implement
+ }
+
+ @Override
+ public void prepare_impl() {
+ // TODO(jaewan): Implement
+ }
+
+ @Override
+ public void fastForward_impl() {
+ // TODO(jaewan): Implement
+ }
+
+ @Override
+ public void rewind_impl() {
+ // TODO(jaewan): Implement
+ }
+
+ @Override
+ public void seekTo_impl(long pos) {
+ // TODO(jaewan): Implement
+ }
+
+ @Override
+ public void setCurrentPlaylistItem_impl(int index) {
+ // TODO(jaewan): Implement
+ }
+
+ ///////////////////////////////////////////////////
+ // Protected or private methods
+ ///////////////////////////////////////////////////
+
+ // Enforces developers to call all the methods on the initially given thread
+ // because calls from the MediaController2 will be run on the thread.
+ // TODO(jaewan): Should we allow calls from the multiple thread?
+ // I prefer this way because allowing multiple thread may case tricky issue like
+ // b/63446360. If the {@link #setPlayer()} with {@code null} can be called from
+ // another thread, transport controls can be called after that.
+ // That's basically the developer's mistake, but they cannot understand what's
+ // happening behind until we tell them so.
+ // If enforcing callling thread doesn't look good, we can alternatively pick
+ // 1. Allow calls from random threads for all methods.
+ // 2. Allow calls from random threads for all methods, except for the
+ // {@link #setPlayer()}.
+ private void ensureCallingThread() {
+ // TODO(jaewan): Uncomment or remove
+ /*
+ if (mHandler.getLooper() != Looper.myLooper()) {
+ throw new IllegalStateException("Run this on the given thread");
+ }*/
+ }
+
+
+ private void ensurePlayer() {
+ // TODO(jaewan): Should we pend command instead? Follow the decision from MP2.
+ // Alternatively we can add a API like setAcceptsPendingCommands(boolean).
+ if (mPlayer == null) {
+ throw new IllegalStateException("Player isn't set");
+ }
+ }
+
+ Handler getHandler() {
+ return mHandler;
+ }
+
+ private void notifyPlaybackStateChanged(PlaybackState2 state) {
+ // Notify to listeners added directly to this session
+ for (int i = 0; i < mListeners.size(); i++) {
+ mListeners.get(i).postPlaybackChange(state);
+ }
+ // Notify to controllers as well.
+ mSessionStub.notifyPlaybackStateChangedNotLocked(state);
+ }
+
+ Context getContext() {
+ return mContext;
+ }
+
+ MediaSession2 getInstance() {
+ return mInstance;
+ }
+
+ MediaPlayerBase getPlayer() {
+ return mPlayer;
+ }
+
+ private static class MyPlaybackListener implements MediaPlayerBase.PlaybackListener {
+ private final WeakReference<MediaSession2Impl> mSession;
+ private final MediaPlayerBase mPlayer;
+
+ private MyPlaybackListener(MediaSession2Impl session, MediaPlayerBase player) {
+ mSession = new WeakReference<>(session);
+ mPlayer = player;
+ }
+
+ @Override
+ public void onPlaybackChanged(PlaybackState2 state) {
+ MediaSession2Impl session = mSession.get();
+ if (mPlayer != session.mInstance.getPlayer()) {
+ Log.w(TAG, "Unexpected playback state change notifications. Ignoring.",
+ new IllegalStateException());
+ return;
+ }
+ session.notifyPlaybackStateChanged(state);
+ }
+ }
+
+ public static class ControllerInfoImpl implements ControllerInfoProvider {
+ private final ControllerInfo mInstance;
+ private final int mUid;
+ private final String mPackageName;
+ private final boolean mIsTrusted;
+ private final IMediaSession2Callback mControllerBinder;
+
+ // Flag to indicate which callbacks should be returned for the controller binder.
+ // Either 0 or combination of {@link #CALLBACK_FLAG_PLAYBACK},
+ // {@link #CALLBACK_FLAG_SESSION_ACTIVENESS}
+ private int mFlag;
+
+ public ControllerInfoImpl(Context context, ControllerInfo instance, int uid,
+ int pid, String packageName, IMediaSession2Callback callback) {
+ mInstance = instance;
+ mUid = uid;
+ mPackageName = packageName;
+
+ // TODO(jaewan): Remove this workaround
+ if ("com.android.server.media".equals(packageName)) {
+ mIsTrusted = true;
+ } else if (context.checkPermission(permission.MEDIA_CONTENT_CONTROL, pid, uid) ==
+ PackageManager.PERMISSION_GRANTED) {
+ mIsTrusted = true;
+ } else {
+ // TODO(jaewan): Also consider enabled notification listener.
+ mIsTrusted = false;
+ // System apps may bind across the user so uid can be differ.
+ // Skip sanity check for the system app.
+ try {
+ int uidForPackage = context.getPackageManager().getPackageUid(packageName, 0);
+ if (uid != uidForPackage) {
+ throw new IllegalArgumentException("Illegal call from uid=" + uid +
+ ", pkg=" + packageName + ". Expected uid" + uidForPackage);
+ }
+ } catch (NameNotFoundException e) {
+ // Rethrow exception with different name because binder methods only accept
+ // RemoteException.
+ throw new IllegalArgumentException(e);
+ }
+ }
+ mControllerBinder = callback;
+ }
+
+ @Override
+ public String getPackageName_impl() {
+ return mPackageName;
+ }
+
+ @Override
+ public int getUid_impl() {
+ return mUid;
+ }
+
+ @Override
+ public boolean isTrusted_impl() {
+ return mIsTrusted;
+ }
+
+ @Override
+ public int hashCode_impl() {
+ return mControllerBinder.hashCode();
+ }
+
+ @Override
+ public boolean equals_impl(ControllerInfoProvider obj) {
+ return equals(obj);
+ }
+
+ @Override
+ public int hashCode() {
+ return mControllerBinder.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof ControllerInfoImpl)) {
+ return false;
+ }
+ ControllerInfoImpl other = (ControllerInfoImpl) obj;
+ return mControllerBinder.asBinder().equals(other.mControllerBinder.asBinder());
+ }
+
+ public ControllerInfo getInstance() {
+ return mInstance;
+ }
+
+ public IBinder getId() {
+ return mControllerBinder.asBinder();
+ }
+
+ public IMediaSession2Callback getControllerBinder() {
+ return mControllerBinder;
+ }
+
+ public boolean containsFlag(int flag) {
+ return (mFlag & flag) != 0;
+ }
+
+ public void addFlag(int flag) {
+ mFlag |= flag;
+ }
+
+ public void removeFlag(int flag) {
+ mFlag &= ~flag;
+ }
+
+ public static ControllerInfoImpl from(ControllerInfo controller) {
+ return (ControllerInfoImpl) controller.getProvider();
+ }
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java b/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
new file mode 100644
index 0000000..2f75dfa
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
@@ -0,0 +1,380 @@
+/*
+ * 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 static com.android.media.MediaController2Impl.CALLBACK_FLAG_PLAYBACK;
+
+import android.content.Context;
+import android.media.IMediaSession2;
+import android.media.IMediaSession2Callback;
+import android.media.MediaLibraryService2.BrowserRoot;
+import android.media.MediaLibraryService2.MediaLibrarySessionCallback;
+import android.media.MediaSession2;
+import android.media.MediaSession2.Command;
+import android.media.MediaSession2.CommandButton;
+import android.media.MediaSession2.CommandGroup;
+import android.media.MediaSession2.ControllerInfo;
+import android.media.MediaSession2.SessionCallback;
+import android.media.PlaybackState2;
+import android.media.session.PlaybackState;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.support.annotation.GuardedBy;
+import android.util.ArrayMap;
+import android.util.Log;
+import com.android.media.MediaSession2Impl.ControllerInfoImpl;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+
+public class MediaSession2Stub extends IMediaSession2.Stub {
+ private static final String TAG = "MediaSession2Stub";
+ private static final boolean DEBUG = true; // TODO(jaewan): Rename.
+
+ private final Object mLock = new Object();
+ private final CommandHandler mCommandHandler;
+ private final WeakReference<MediaSession2Impl> mSession;
+ private final Context mContext;
+ private final SessionCallback mSessionCallback;
+ private final MediaLibrarySessionCallback mLibraryCallback;
+
+ @GuardedBy("mLock")
+ private final ArrayMap<IBinder, ControllerInfo> mControllers = new ArrayMap<>();
+
+ public MediaSession2Stub(MediaSession2Impl session, SessionCallback callback) {
+ mSession = new WeakReference<>(session);
+ mContext = session.getContext();
+ // TODO(jaewan): Should be executor from the session builder
+ mCommandHandler = new CommandHandler(session.getHandler().getLooper());
+ mSessionCallback = callback;
+ mLibraryCallback = (callback instanceof MediaLibrarySessionCallback)
+ ? (MediaLibrarySessionCallback) callback : null;
+ }
+
+ public void destroyNotLocked() {
+ final List<ControllerInfo> list;
+ synchronized (mLock) {
+ mSession.clear();
+ mCommandHandler.removeCallbacksAndMessages(null);
+ list = getControllers();
+ mControllers.clear();
+ }
+ for (int i = 0; i < list.size(); i++) {
+ IMediaSession2Callback callbackBinder =
+ ((ControllerInfoImpl) list.get(i).getProvider()).getControllerBinder();
+ try {
+ // Should be used without a lock hold to prevent potential deadlock.
+ callbackBinder.onConnectionChanged(null, null);
+ } catch (RemoteException e) {
+ // Controller is gone. Should be fine because we're destroying.
+ }
+ }
+ }
+
+ private MediaSession2Impl getSession() throws IllegalStateException {
+ final MediaSession2Impl session = mSession.get();
+ if (session == null) {
+ throw new IllegalStateException("Session is died");
+ }
+ return session;
+ }
+
+ @Override
+ public void connect(String callingPackage, IMediaSession2Callback callback) {
+ if (callback == null) {
+ // Requesting connect without callback to receive result.
+ return;
+ }
+ ControllerInfo request = new ControllerInfo(mContext,
+ Binder.getCallingUid(), Binder.getCallingPid(), callingPackage, callback);
+ mCommandHandler.postConnect(request);
+ }
+
+ @Override
+ public void release(IMediaSession2Callback caller) throws RemoteException {
+ synchronized (mLock) {
+ ControllerInfo controllerInfo = mControllers.remove(caller.asBinder());
+ if (DEBUG) {
+ Log.d(TAG, "releasing " + controllerInfo);
+ }
+ }
+ }
+
+ @Override
+ public void sendCommand(IMediaSession2Callback caller, Bundle command, Bundle args)
+ throws RuntimeException {
+ ControllerInfo controller = getController(caller);
+ if (controller == null) {
+ if (DEBUG) {
+ Log.d(TAG, "Command from a controller that hasn't connected. Ignore");
+ }
+ return;
+ }
+ mCommandHandler.postCommand(controller, Command.fromBundle(command), args);
+ }
+
+ @Override
+ public void getBrowserRoot(IMediaSession2Callback caller, Bundle rootHints)
+ throws RuntimeException {
+ if (mLibraryCallback == null) {
+ if (DEBUG) {
+ Log.d(TAG, "Session cannot hand getBrowserRoot()");
+ }
+ return;
+ }
+ final ControllerInfo controller = getController(caller);
+ if (controller == null) {
+ if (DEBUG) {
+ Log.d(TAG, "getBrowerRoot from a controller that hasn't connected. Ignore");
+ }
+ return;
+ }
+ mCommandHandler.postOnGetRoot(controller, rootHints);
+ }
+
+ @Deprecated
+ @Override
+ public Bundle getPlaybackState() throws RemoteException {
+ MediaSession2Impl session = getSession();
+ // TODO(jaewan): Check if mPlayer.getPlaybackState() is safe here.
+ return session.getInstance().getPlayer().getPlaybackState().toBundle();
+ }
+
+ @Deprecated
+ @Override
+ public void registerCallback(final IMediaSession2Callback callbackBinder,
+ final int callbackFlag, final int requestCode) throws RemoteException {
+ // TODO(jaewan): Call onCommand() here. To do so, you should pend message.
+ synchronized (mLock) {
+ ControllerInfo controllerInfo = getController(callbackBinder);
+ if (controllerInfo == null) {
+ return;
+ }
+ ControllerInfoImpl.from(controllerInfo).addFlag(callbackFlag);
+ }
+ }
+
+ @Deprecated
+ @Override
+ public void unregisterCallback(IMediaSession2Callback callbackBinder, int callbackFlag)
+ throws RemoteException {
+ // TODO(jaewan): Call onCommand() here. To do so, you should pend message.
+ synchronized (mLock) {
+ ControllerInfo controllerInfo = getController(callbackBinder);
+ if (controllerInfo == null) {
+ return;
+ }
+ ControllerInfoImpl.from(controllerInfo).removeFlag(callbackFlag);
+ }
+ }
+
+ private ControllerInfo getController(IMediaSession2Callback caller) {
+ synchronized (mLock) {
+ return mControllers.get(caller.asBinder());
+ }
+ }
+
+ public List<ControllerInfo> getControllers() {
+ ArrayList<ControllerInfo> controllers = new ArrayList<>();
+ synchronized (mLock) {
+ for (int i = 0; i < mControllers.size(); i++) {
+ controllers.add(mControllers.valueAt(i));
+ }
+ }
+ return controllers;
+ }
+
+ public List<ControllerInfo> getControllersWithFlag(int flag) {
+ ArrayList<ControllerInfo> controllers = new ArrayList<>();
+ synchronized (mLock) {
+ for (int i = 0; i < mControllers.size(); i++) {
+ ControllerInfo controllerInfo = mControllers.valueAt(i);
+ if (ControllerInfoImpl.from(controllerInfo).containsFlag(flag)) {
+ controllers.add(controllerInfo);
+ }
+ }
+ }
+ return controllers;
+ }
+
+ // Should be used without a lock to prevent potential deadlock.
+ public void notifyPlaybackStateChangedNotLocked(PlaybackState2 state) {
+ final List<ControllerInfo> list = getControllersWithFlag(CALLBACK_FLAG_PLAYBACK);
+ for (int i = 0; i < list.size(); i++) {
+ IMediaSession2Callback callbackBinder =
+ ControllerInfoImpl.from(list.get(i)).getControllerBinder();
+ try {
+ callbackBinder.onPlaybackStateChanged(state.toBundle());
+ } catch (RemoteException e) {
+ Log.w(TAG, "Controller is gone", e);
+ // TODO(jaewan): What to do when the controller is gone?
+ }
+ }
+ }
+
+ public void notifyCustomLayoutNotLocked(ControllerInfo controller, List<CommandButton> layout) {
+ // TODO(jaewan): It's OK to be called while it's connecting, but not OK if the connection
+ // is rejected. Handle the case.
+ IMediaSession2Callback callbackBinder =
+ ControllerInfoImpl.from(controller).getControllerBinder();
+ try {
+ List<Bundle> layoutBundles = new ArrayList<>();
+ for (int i = 0; i < layout.size(); i++) {
+ Bundle bundle = layout.get(i).toBundle();
+ if (bundle != null) {
+ layoutBundles.add(bundle);
+ }
+ }
+ callbackBinder.onCustomLayoutChanged(layoutBundles);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Controller is gone", e);
+ // TODO(jaewan): What to do when the controller is gone?
+ }
+ }
+
+ // TODO(jaewan): Remove this. We should use Executor given by the session builder.
+ private class CommandHandler extends Handler {
+ public static final int MSG_CONNECT = 1000;
+ public static final int MSG_COMMAND = 1001;
+ public static final int MSG_ON_GET_ROOT = 2000;
+
+ public CommandHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ final MediaSession2Impl session = MediaSession2Stub.this.mSession.get();
+ if (session == null || session.getPlayer() == null) {
+ return;
+ }
+
+ switch (msg.what) {
+ case MSG_CONNECT: {
+ ControllerInfo request = (ControllerInfo) msg.obj;
+ CommandGroup allowedCommands = mSessionCallback.onConnect(request);
+ // Don't reject connection for the request from trusted app.
+ // Otherwise server will fail to retrieve session's information to dispatch
+ // media keys to.
+ boolean accept = allowedCommands != null || request.isTrusted();
+ ControllerInfoImpl impl = ControllerInfoImpl.from(request);
+ if (accept) {
+ synchronized (mLock) {
+ mControllers.put(impl.getId(), request);
+ }
+ if (allowedCommands == null) {
+ // For trusted apps, send non-null allowed commands to keep connection.
+ allowedCommands = new CommandGroup();
+ }
+ }
+ if (DEBUG) {
+ Log.d(TAG, "onConnectResult, request=" + request
+ + " accept=" + accept);
+ }
+ try {
+ impl.getControllerBinder().onConnectionChanged(
+ accept ? MediaSession2Stub.this : null,
+ allowedCommands == null ? null : allowedCommands.toBundle());
+ } catch (RemoteException e) {
+ // Controller may be died prematurely.
+ }
+ break;
+ }
+ case MSG_COMMAND: {
+ CommandParam param = (CommandParam) msg.obj;
+ Command command = param.command;
+ boolean accepted = mSessionCallback.onCommandRequest(
+ param.controller, command);
+ if (!accepted) {
+ // Don't run rejected command.
+ if (DEBUG) {
+ Log.d(TAG, "Command " + command + " from "
+ + param.controller + " was rejected by " + session);
+ }
+ return;
+ }
+
+ switch (param.command.getCommandCode()) {
+ case MediaSession2.COMMAND_CODE_PLAYBACK_START:
+ session.getInstance().play();
+ break;
+ case MediaSession2.COMMAND_CODE_PLAYBACK_PAUSE:
+ session.getInstance().pause();
+ break;
+ case MediaSession2.COMMAND_CODE_PLAYBACK_STOP:
+ session.getInstance().stop();
+ break;
+ case MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_PREV_ITEM:
+ session.getInstance().skipToPrevious();
+ break;
+ case MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_NEXT_ITEM:
+ session.getInstance().skipToNext();
+ break;
+ default:
+ // TODO(jaewan): Handle custom command.
+ }
+ break;
+ }
+ case MSG_ON_GET_ROOT: {
+ final CommandParam param = (CommandParam) msg.obj;
+ final ControllerInfoImpl controller = ControllerInfoImpl.from(param.controller);
+ BrowserRoot root = mLibraryCallback.onGetRoot(param.controller, param.args);
+ try {
+ controller.getControllerBinder().onGetRootResult(param.args,
+ root == null ? null : root.getRootId(),
+ root == null ? null : root.getExtras());
+ } catch (RemoteException e) {
+ // Controller may be died prematurely.
+ // TODO(jaewan): Handle this.
+ }
+ break;
+ }
+ }
+ }
+
+ public void postConnect(ControllerInfo request) {
+ obtainMessage(MSG_CONNECT, request).sendToTarget();
+ }
+
+ public void postCommand(ControllerInfo controller, Command command, Bundle args) {
+ CommandParam param = new CommandParam(controller, command, args);
+ obtainMessage(MSG_COMMAND, param).sendToTarget();
+ }
+
+ public void postOnGetRoot(ControllerInfo controller, Bundle rootHints) {
+ CommandParam param = new CommandParam(controller, null, rootHints);
+ obtainMessage(MSG_ON_GET_ROOT, param).sendToTarget();
+ }
+ }
+
+ private static class CommandParam {
+ public final ControllerInfo controller;
+ public final Command command;
+ public final Bundle args;
+
+ private CommandParam(ControllerInfo controller, Command command, Bundle args) {
+ this.controller = controller;
+ this.command = command;
+ this.args = args;
+ }
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/media/MediaSessionService2Impl.java b/packages/MediaComponents/src/com/android/media/MediaSessionService2Impl.java
new file mode 100644
index 0000000..9d24082
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/media/MediaSessionService2Impl.java
@@ -0,0 +1,163 @@
+/*
+ * 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 static android.content.Context.NOTIFICATION_SERVICE;
+
+import android.app.NotificationManager;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.media.MediaPlayerBase.PlaybackListener;
+import android.media.MediaSession2;
+import android.media.MediaSessionService2;
+import android.media.MediaSessionService2.MediaNotification;
+import android.media.PlaybackState2;
+import android.media.session.PlaybackState;
+import android.media.update.MediaSessionService2Provider;
+import android.os.IBinder;
+import android.os.Looper;
+import android.support.annotation.GuardedBy;
+import android.util.Log;
+
+// TODO(jaewan): Need a test for session service itself.
+public class MediaSessionService2Impl implements MediaSessionService2Provider {
+
+ private static final String TAG = "MPSessionService"; // to meet 23 char limit in Log tag
+ private static final boolean DEBUG = true; // TODO(jaewan): Change this.
+
+ private final MediaSessionService2 mInstance;
+ private final PlaybackListener mListener = new SessionServicePlaybackListener();
+
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
+ private NotificationManager mNotificationManager;
+ @GuardedBy("mLock")
+ private Intent mStartSelfIntent;
+
+ private boolean mIsRunningForeground;
+ private MediaSession2 mSession;
+
+ public MediaSessionService2Impl(MediaSessionService2 instance) {
+ if (DEBUG) {
+ Log.d(TAG, "MediaSessionService2Impl(" + instance + ")");
+ }
+ mInstance = instance;
+ }
+
+ @Override
+ public MediaSession2 getSession_impl() {
+ return getSession();
+ }
+
+ MediaSession2 getSession() {
+ synchronized (mLock) {
+ return mSession;
+ }
+ }
+
+ @Override
+ public MediaNotification onUpdateNotification_impl(PlaybackState2 state) {
+ // Provide default notification UI later.
+ return null;
+ }
+
+ @Override
+ public void onCreate_impl() {
+ mNotificationManager = (NotificationManager) mInstance.getSystemService(
+ NOTIFICATION_SERVICE);
+ mStartSelfIntent = new Intent(mInstance, mInstance.getClass());
+
+ Intent serviceIntent = createServiceIntent();
+ ResolveInfo resolveInfo = mInstance.getPackageManager()
+ .resolveService(serviceIntent, PackageManager.GET_META_DATA);
+ String id;
+ if (resolveInfo == null || resolveInfo.serviceInfo == null) {
+ throw new IllegalArgumentException("service " + mInstance + " doesn't implement"
+ + serviceIntent.getAction());
+ } else if (resolveInfo.serviceInfo.metaData == null) {
+ if (DEBUG) {
+ Log.d(TAG, "Failed to resolve ID for " + mInstance + ". Using empty id");
+ }
+ id = "";
+ } else {
+ id = resolveInfo.serviceInfo.metaData.getString(
+ MediaSessionService2.SERVICE_META_DATA, "");
+ }
+ mSession = mInstance.onCreateSession(id);
+ if (mSession == null || !id.equals(mSession.getToken().getId())) {
+ throw new RuntimeException("Expected session with id " + id + ", but got " + mSession);
+ }
+ // TODO(jaewan): Uncomment here.
+ // mSession.addPlaybackListener(mListener, mSession.getExecutor());
+ }
+
+ Intent createServiceIntent() {
+ Intent serviceIntent = new Intent(mInstance, mInstance.getClass());
+ serviceIntent.setAction(MediaSessionService2.SERVICE_INTERFACE);
+ return serviceIntent;
+ }
+
+ public IBinder onBind_impl(Intent intent) {
+ if (MediaSessionService2.SERVICE_INTERFACE.equals(intent.getAction())) {
+ return mSession.getToken().getSessionBinder().asBinder();
+ }
+ return null;
+ }
+
+ private void updateNotification(PlaybackState2 state) {
+ MediaNotification mediaNotification = mInstance.onUpdateNotification(state);
+ if (mediaNotification == null) {
+ return;
+ }
+ switch((int) state.getState()) {
+ case PlaybackState.STATE_PLAYING:
+ if (!mIsRunningForeground) {
+ mIsRunningForeground = true;
+ mInstance.startForegroundService(mStartSelfIntent);
+ mInstance.startForeground(mediaNotification.id, mediaNotification.notification);
+ return;
+ }
+ break;
+ case PlaybackState.STATE_STOPPED:
+ if (mIsRunningForeground) {
+ mIsRunningForeground = false;
+ mInstance.stopForeground(true);
+ return;
+ }
+ break;
+ }
+ mNotificationManager.notify(mediaNotification.id, mediaNotification.notification);
+ }
+
+ private class SessionServicePlaybackListener implements PlaybackListener {
+ @Override
+ public void onPlaybackChanged(PlaybackState2 state) {
+ if (state == null) {
+ Log.w(TAG, "Ignoring null playback state");
+ return;
+ }
+ MediaSession2Impl impl = (MediaSession2Impl) mSession.getProvider();
+ if (impl.getHandler().getLooper() != Looper.myLooper()) {
+ Log.w(TAG, "Ignoring " + state + ". Expected " + impl.getHandler().getLooper()
+ + " but " + Looper.myLooper());
+ return;
+ }
+ updateNotification(state);
+ }
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/media/PlaybackListenerHolder.java b/packages/MediaComponents/src/com/android/media/PlaybackListenerHolder.java
new file mode 100644
index 0000000..7b336c4
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/media/PlaybackListenerHolder.java
@@ -0,0 +1,74 @@
+/*
+ * 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.media.MediaPlayerBase.PlaybackListener;
+import android.media.PlaybackState2;
+import android.media.session.PlaybackState;
+import android.os.Handler;
+import android.os.Message;
+import android.support.annotation.NonNull;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * Holds {@link PlaybackListener} with the {@link Handler}.
+ */
+public class PlaybackListenerHolder {
+ public final Executor executor;
+ public final PlaybackListener listener;
+
+ public PlaybackListenerHolder(Executor executor, @NonNull PlaybackListener listener) {
+ this.executor = executor;
+ this.listener = listener;
+ }
+
+ public void postPlaybackChange(final PlaybackState2 state) {
+ executor.execute(() -> listener.onPlaybackChanged(state));
+ }
+
+ /**
+ * Returns {@code true} if the given list contains a {@link PlaybackListenerHolder} that holds
+ * the given listener.
+ *
+ * @param list list to check
+ * @param listener listener to check
+ * @return {@code true} if the given list contains listener. {@code false} otherwise.
+ */
+ public static <Holder extends PlaybackListenerHolder> boolean contains(
+ @NonNull List<Holder> list, PlaybackListener listener) {
+ return indexOf(list, listener) >= 0;
+ }
+
+ /**
+ * Returns the index of the {@link PlaybackListenerHolder} that contains the given listener.
+ *
+ * @param list list to check
+ * @param listener listener to check
+ * @return {@code index} of item if the given list contains listener. {@code -1} otherwise.
+ */
+ public static <Holder extends PlaybackListenerHolder> int indexOf(
+ @NonNull List<Holder> list, PlaybackListener listener) {
+ for (int i = 0; i < list.size(); i++) {
+ if (list.get(i).listener == listener) {
+ return i;
+ }
+ }
+ return -1;
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/media/update/ApiFactory.java b/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
index abff13e..8ce7bef 100644
--- a/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
+++ b/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
@@ -16,33 +16,119 @@
package com.android.media.update;
+import android.app.PendingIntent;
import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.media.MediaBrowser2;
+import android.media.MediaBrowser2.BrowserCallback;
+import android.media.MediaController2;
+import android.media.MediaController2.ControllerCallback;
+import android.media.MediaLibraryService2;
+import android.media.MediaLibraryService2.MediaLibrarySession;
+import android.media.MediaLibraryService2.MediaLibrarySessionCallback;
+import android.media.MediaPlayerBase;
+import android.media.MediaSession2;
+import android.media.MediaSession2.ControllerInfo;
+import android.media.MediaSession2.SessionCallback;
+import android.media.MediaSessionService2;
+import android.media.IMediaSession2Callback;
+import android.media.SessionToken2;
+import android.media.VolumeProvider;
+import android.media.update.MediaBrowser2Provider;
+import android.media.update.MediaControlView2Provider;
import android.media.update.MediaController2Provider;
+import android.media.update.MediaLibraryService2Provider.MediaLibrarySessionProvider;
+import android.media.update.MediaSession2Provider;
+import android.media.update.MediaSessionService2Provider;
import android.media.update.VideoView2Provider;
import android.media.update.StaticProvider;
import android.media.update.ViewProvider;
-import android.widget.MediaController2;
+import android.os.IInterface;
+import android.support.annotation.Nullable;
+import android.util.AttributeSet;
+import android.widget.MediaControlView2;
import android.widget.VideoView2;
-import com.android.widget.MediaController2Impl;
+import com.android.media.MediaBrowser2Impl;
+import com.android.media.MediaController2Impl;
+import com.android.media.MediaLibraryService2Impl;
+import com.android.media.MediaLibraryService2Impl.MediaLibrarySessionImpl;
+import com.android.media.MediaSession2Impl;
+import com.android.media.MediaSessionService2Impl;
+import com.android.widget.MediaControlView2Impl;
import com.android.widget.VideoView2Impl;
-public class ApiFactory implements StaticProvider {
+import java.util.concurrent.Executor;
- public static Object initialize(Context appContext, Context libContext)
+public class ApiFactory implements StaticProvider {
+ public static Object initialize(Resources libResources, Theme libTheme)
throws ReflectiveOperationException {
- ApiHelper.initialize(appContext, libContext);
+ ApiHelper.initialize(libResources, libTheme);
return new ApiFactory();
}
@Override
public MediaController2Provider createMediaController2(
- MediaController2 instance, ViewProvider superProvider) {
- return new MediaController2Impl(instance, superProvider);
+ Context context, MediaController2 instance, SessionToken2 token,
+ Executor executor, ControllerCallback callback) {
+ return new MediaController2Impl(context, instance, token, executor, callback);
}
@Override
- public VideoView2Provider createVideoView2(VideoView2 instance, ViewProvider superProvider) {
- return new VideoView2Impl(instance, superProvider);
+ public MediaBrowser2Provider createMediaBrowser2(Context context, MediaBrowser2 instance,
+ SessionToken2 token, Executor executor, BrowserCallback callback) {
+ return new MediaBrowser2Impl(context, instance, token, executor, callback);
+ }
+
+ @Override
+ public MediaSession2Provider createMediaSession2(Context context, MediaSession2 instance,
+ MediaPlayerBase player, String id, VolumeProvider volumeProvider,
+ int ratingType, PendingIntent sessionActivity, Executor callbackExecutor,
+ SessionCallback callback) {
+ return new MediaSession2Impl(context, instance, player, id, volumeProvider, ratingType,
+ sessionActivity, callbackExecutor, callback);
+ }
+
+ @Override
+ public MediaSession2Provider.ControllerInfoProvider createMediaSession2ControllerInfoProvider(
+ Context context, ControllerInfo instance, int uid, int pid, String packageName,
+ IInterface callback) {
+ return new MediaSession2Impl.ControllerInfoImpl(context,
+ instance, uid, pid, packageName, (IMediaSession2Callback) callback);
+ }
+
+ @Override
+ public MediaSessionService2Provider createMediaSessionService2(
+ MediaSessionService2 instance) {
+ return new MediaSessionService2Impl(instance);
+ }
+
+ @Override
+ public MediaSessionService2Provider createMediaLibraryService2(
+ MediaLibraryService2 instance) {
+ return new MediaLibraryService2Impl(instance);
+ }
+
+ @Override
+ public MediaLibrarySessionProvider createMediaLibraryService2MediaLibrarySession(
+ Context context, MediaLibrarySession instance, MediaPlayerBase player,
+ String id, VolumeProvider volumeProvider, int ratingType, PendingIntent sessionActivity,
+ Executor callbackExecutor, MediaLibrarySessionCallback callback) {
+ return new MediaLibrarySessionImpl(context, instance, player, id, volumeProvider,
+ ratingType, sessionActivity, callbackExecutor, callback);
+ }
+
+ @Override
+ public MediaControlView2Provider createMediaControlView2(
+ MediaControlView2 instance, ViewProvider superProvider) {
+ return new MediaControlView2Impl(instance, superProvider);
+ }
+
+ @Override
+ public VideoView2Provider createVideoView2(
+ VideoView2 instance, ViewProvider superProvider,
+ @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ return new VideoView2Impl(instance, superProvider, attrs, defStyleAttr, defStyleRes);
}
}
diff --git a/packages/MediaComponents/src/com/android/media/update/ApiHelper.java b/packages/MediaComponents/src/com/android/media/update/ApiHelper.java
index 550da86..b0ca1bd 100644
--- a/packages/MediaComponents/src/com/android/media/update/ApiHelper.java
+++ b/packages/MediaComponents/src/com/android/media/update/ApiHelper.java
@@ -18,34 +18,67 @@
import android.content.Context;
import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.content.res.XmlResourceParser;
+import android.util.AttributeSet;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import com.android.support.mediarouter.app.MediaRouteButton;
public class ApiHelper {
private static ApiHelper sInstance;
- private final Context mAppContext;
private final Resources mLibResources;
- private final Resources.Theme mLibTheme;
+ private final Theme mLibTheme;
public static ApiHelper getInstance() {
return sInstance;
}
- static void initialize(Context appContext, Context libContext) {
+ static void initialize(Resources libResources, Theme libTheme) {
if (sInstance == null) {
- sInstance = new ApiHelper(appContext, libContext);
+ sInstance = new ApiHelper(libResources, libTheme);
}
}
- private ApiHelper(Context appContext, Context libContext) {
- mAppContext = appContext;
- mLibResources = libContext.getResources();
- mLibTheme = libContext.getTheme();
+ private ApiHelper(Resources libResources, Theme libTheme) {
+ mLibResources = libResources;
+ mLibTheme = libTheme;
}
- public Resources getLibResources() {
- return mLibResources;
+ public static Resources getLibResources() {
+ return sInstance.mLibResources;
}
- public Resources.Theme getLibTheme() {
- return mLibTheme;
+ public static Resources.Theme getLibTheme() {
+ return sInstance.mLibTheme;
+ }
+
+ public static LayoutInflater getLayoutInflater(Context context) {
+ LayoutInflater layoutInflater = LayoutInflater.from(context).cloneInContext(
+ new ContextThemeWrapper(context, getLibTheme()));
+ layoutInflater.setFactory2(new LayoutInflater.Factory2() {
+ @Override
+ public View onCreateView(
+ View parent, String name, Context context, AttributeSet attrs) {
+ if (MediaRouteButton.class.getCanonicalName().equals(name)) {
+ return new MediaRouteButton(context, attrs);
+ }
+ return null;
+ }
+
+ @Override
+ public View onCreateView(String name, Context context, AttributeSet attrs) {
+ return onCreateView(null, name, context, attrs);
+ }
+ });
+ return layoutInflater;
+ }
+
+ public static View inflateLibLayout(Context context, int libResId) {
+ try (XmlResourceParser parser = getLibResources().getLayout(libResId)) {
+ return getLayoutInflater(context).inflate(parser, null);
+ }
}
}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/api24/media/MediaRouterApi24.java b/packages/MediaComponents/src/com/android/support/mediarouter/api24/media/MediaRouterApi24.java
new file mode 100644
index 0000000..1146af6
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/api24/media/MediaRouterApi24.java
@@ -0,0 +1,26 @@
+/*
+ * 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.support.mediarouter.media;
+
+// @@RequiresApi(24)
+final class MediaRouterApi24 {
+ public static final class RouteInfo {
+ public static int getDeviceType(Object routeObj) {
+ return ((android.media.MediaRouter.RouteInfo)routeObj).getDeviceType();
+ }
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteActionProvider.java b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteActionProvider.java
new file mode 100644
index 0000000..d3e8d47
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteActionProvider.java
@@ -0,0 +1,333 @@
+/*
+ * 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.support.mediarouter.app;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.support.v4.view.ActionProvider;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.support.mediarouter.media.MediaRouteSelector;
+import com.android.support.mediarouter.media.MediaRouter;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * The media route action provider displays a {@link MediaRouteButton media route button}
+ * in the application's {@link ActionBar} to allow the user to select routes and
+ * to control the currently selected route.
+ * <p>
+ * The application must specify the kinds of routes that the user should be allowed
+ * to select by specifying a {@link MediaRouteSelector selector} with the
+ * {@link #setRouteSelector} method.
+ * </p><p>
+ * Refer to {@link MediaRouteButton} for a description of the button that will
+ * appear in the action bar menu. Note that instead of disabling the button
+ * when no routes are available, the action provider will instead make the
+ * menu item invisible. In this way, the button will only be visible when it
+ * is possible for the user to discover and select a matching route.
+ * </p>
+ *
+ * <h3>Prerequisites</h3>
+ * <p>
+ * To use the media route action provider, the activity must be a subclass of
+ * {@link AppCompatActivity} from the <code>android.support.v7.appcompat</code>
+ * support library. Refer to support library documentation for details.
+ * </p>
+ *
+ * <h3>Example</h3>
+ * <p>
+ * </p><p>
+ * The application should define a menu resource to include the provider in the
+ * action bar options menu. Note that the support library action bar uses attributes
+ * that are defined in the application's resource namespace rather than the framework's
+ * resource namespace to configure each item.
+ * </p><pre>
+ * <menu xmlns:android="http://schemas.android.com/apk/res/android"
+ * xmlns:app="http://schemas.android.com/apk/res-auto">
+ * <item android:id="@+id/media_route_menu_item"
+ * android:title="@string/media_route_menu_title"
+ * app:showAsAction="always"
+ * app:actionProviderClass="android.support.v7.app.MediaRouteActionProvider"/>
+ * </menu>
+ * </pre><p>
+ * Then configure the menu and set the route selector for the chooser.
+ * </p><pre>
+ * public class MyActivity extends AppCompatActivity {
+ * private MediaRouter mRouter;
+ * private MediaRouter.Callback mCallback;
+ * private MediaRouteSelector mSelector;
+ *
+ * protected void onCreate(Bundle savedInstanceState) {
+ * super.onCreate(savedInstanceState);
+ *
+ * mRouter = Mediarouter.getInstance(this);
+ * mSelector = new MediaRouteSelector.Builder()
+ * .addControlCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO)
+ * .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
+ * .build();
+ * mCallback = new MyCallback();
+ * }
+ *
+ * // Add the callback on start to tell the media router what kinds of routes
+ * // the application is interested in so that it can try to discover suitable ones.
+ * public void onStart() {
+ * super.onStart();
+ *
+ * mediaRouter.addCallback(mSelector, mCallback,
+ * MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
+ *
+ * MediaRouter.RouteInfo route = mediaRouter.updateSelectedRoute(mSelector);
+ * // do something with the route...
+ * }
+ *
+ * // Remove the selector on stop to tell the media router that it no longer
+ * // needs to invest effort trying to discover routes of these kinds for now.
+ * public void onStop() {
+ * super.onStop();
+ *
+ * mediaRouter.removeCallback(mCallback);
+ * }
+ *
+ * public boolean onCreateOptionsMenu(Menu menu) {
+ * super.onCreateOptionsMenu(menu);
+ *
+ * getMenuInflater().inflate(R.menu.sample_media_router_menu, menu);
+ *
+ * MenuItem mediaRouteMenuItem = menu.findItem(R.id.media_route_menu_item);
+ * MediaRouteActionProvider mediaRouteActionProvider =
+ * (MediaRouteActionProvider)MenuItemCompat.getActionProvider(mediaRouteMenuItem);
+ * mediaRouteActionProvider.setRouteSelector(mSelector);
+ * return true;
+ * }
+ *
+ * private final class MyCallback extends MediaRouter.Callback {
+ * // Implement callback methods as needed.
+ * }
+ * }
+ * </pre>
+ *
+ * @see #setRouteSelector
+ */
+public class MediaRouteActionProvider extends ActionProvider {
+ private static final String TAG = "MediaRouteActionProvider";
+
+ private final MediaRouter mRouter;
+ private final MediaRouterCallback mCallback;
+
+ private MediaRouteSelector mSelector = MediaRouteSelector.EMPTY;
+ private MediaRouteDialogFactory mDialogFactory = MediaRouteDialogFactory.getDefault();
+ private MediaRouteButton mButton;
+
+ /**
+ * Creates the action provider.
+ *
+ * @param context The context.
+ */
+ public MediaRouteActionProvider(Context context) {
+ super(context);
+
+ mRouter = MediaRouter.getInstance(context);
+ mCallback = new MediaRouterCallback(this);
+ }
+
+ /**
+ * Gets the media route selector for filtering the routes that the user can
+ * select using the media route chooser dialog.
+ *
+ * @return The selector, never null.
+ */
+ @NonNull
+ public MediaRouteSelector getRouteSelector() {
+ return mSelector;
+ }
+
+ /**
+ * Sets the media route selector for filtering the routes that the user can
+ * select using the media route chooser dialog.
+ *
+ * @param selector The selector, must not be null.
+ */
+ public void setRouteSelector(@NonNull MediaRouteSelector selector) {
+ if (selector == null) {
+ throw new IllegalArgumentException("selector must not be null");
+ }
+
+ if (!mSelector.equals(selector)) {
+ // FIXME: We currently have no way of knowing whether the action provider
+ // is still needed by the UI. Unfortunately this means the action provider
+ // may leak callbacks until garbage collection occurs. This may result in
+ // media route providers doing more work than necessary in the short term
+ // while trying to discover routes that are no longer of interest to the
+ // application. To solve this problem, the action provider will need some
+ // indication from the framework that it is being destroyed.
+ if (!mSelector.isEmpty()) {
+ mRouter.removeCallback(mCallback);
+ }
+ if (!selector.isEmpty()) {
+ mRouter.addCallback(selector, mCallback);
+ }
+ mSelector = selector;
+ refreshRoute();
+
+ if (mButton != null) {
+ mButton.setRouteSelector(selector);
+ }
+ }
+ }
+
+ /**
+ * Gets the media route dialog factory to use when showing the route chooser
+ * or controller dialog.
+ *
+ * @return The dialog factory, never null.
+ */
+ @NonNull
+ public MediaRouteDialogFactory getDialogFactory() {
+ return mDialogFactory;
+ }
+
+ /**
+ * Sets the media route dialog factory to use when showing the route chooser
+ * or controller dialog.
+ *
+ * @param factory The dialog factory, must not be null.
+ */
+ public void setDialogFactory(@NonNull MediaRouteDialogFactory factory) {
+ if (factory == null) {
+ throw new IllegalArgumentException("factory must not be null");
+ }
+
+ if (mDialogFactory != factory) {
+ mDialogFactory = factory;
+
+ if (mButton != null) {
+ mButton.setDialogFactory(factory);
+ }
+ }
+ }
+
+ /**
+ * Gets the associated media route button, or null if it has not yet been created.
+ */
+ @Nullable
+ public MediaRouteButton getMediaRouteButton() {
+ return mButton;
+ }
+
+ /**
+ * Called when the media route button is being created.
+ * <p>
+ * Subclasses may override this method to customize the button.
+ * </p>
+ */
+ public MediaRouteButton onCreateMediaRouteButton() {
+ return new MediaRouteButton(getContext());
+ }
+
+ @Override
+ @SuppressWarnings("deprecation")
+ public View onCreateActionView() {
+ if (mButton != null) {
+ Log.e(TAG, "onCreateActionView: this ActionProvider is already associated " +
+ "with a menu item. Don't reuse MediaRouteActionProvider instances! " +
+ "Abandoning the old menu item...");
+ }
+
+ mButton = onCreateMediaRouteButton();
+ mButton.setCheatSheetEnabled(true);
+ mButton.setRouteSelector(mSelector);
+ mButton.setDialogFactory(mDialogFactory);
+ mButton.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
+ return mButton;
+ }
+
+ @Override
+ public boolean onPerformDefaultAction() {
+ if (mButton != null) {
+ return mButton.showDialog();
+ }
+ return false;
+ }
+
+ @Override
+ public boolean overridesItemVisibility() {
+ return true;
+ }
+
+ @Override
+ public boolean isVisible() {
+ return mRouter.isRouteAvailable(mSelector,
+ MediaRouter.AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE);
+ }
+
+ void refreshRoute() {
+ refreshVisibility();
+ }
+
+ private static final class MediaRouterCallback extends MediaRouter.Callback {
+ private final WeakReference<MediaRouteActionProvider> mProviderWeak;
+
+ public MediaRouterCallback(MediaRouteActionProvider provider) {
+ mProviderWeak = new WeakReference<MediaRouteActionProvider>(provider);
+ }
+
+ @Override
+ public void onRouteAdded(MediaRouter router, MediaRouter.RouteInfo info) {
+ refreshRoute(router);
+ }
+
+ @Override
+ public void onRouteRemoved(MediaRouter router, MediaRouter.RouteInfo info) {
+ refreshRoute(router);
+ }
+
+ @Override
+ public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo info) {
+ refreshRoute(router);
+ }
+
+ @Override
+ public void onProviderAdded(MediaRouter router, MediaRouter.ProviderInfo provider) {
+ refreshRoute(router);
+ }
+
+ @Override
+ public void onProviderRemoved(MediaRouter router, MediaRouter.ProviderInfo provider) {
+ refreshRoute(router);
+ }
+
+ @Override
+ public void onProviderChanged(MediaRouter router, MediaRouter.ProviderInfo provider) {
+ refreshRoute(router);
+ }
+
+ private void refreshRoute(MediaRouter router) {
+ MediaRouteActionProvider provider = mProviderWeak.get();
+ if (provider != null) {
+ provider.refreshRoute();
+ } else {
+ router.removeCallback(this);
+ }
+ }
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteButton.java b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteButton.java
new file mode 100644
index 0000000..65fc88c
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteButton.java
@@ -0,0 +1,620 @@
+/*
+ * 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.support.mediarouter.app;
+
+import android.annotation.NonNull;
+import android.app.Activity;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.drawable.AnimationDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.graphics.drawable.DrawableCompat;
+import android.support.v7.widget.TooltipCompat;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.SoundEffectConstants;
+import android.view.View;
+
+import com.android.media.update.ApiHelper;
+import com.android.media.update.R;
+import com.android.support.mediarouter.media.MediaRouteSelector;
+import com.android.support.mediarouter.media.MediaRouter;
+
+/**
+ * The media route button allows the user to select routes and to control the
+ * currently selected route.
+ * <p>
+ * The application must specify the kinds of routes that the user should be allowed
+ * to select by specifying a {@link MediaRouteSelector selector} with the
+ * {@link #setRouteSelector} method.
+ * </p><p>
+ * When the default route is selected or when the currently selected route does not
+ * match the {@link #getRouteSelector() selector}, the button will appear in
+ * an inactive state indicating that the application is not connected to a
+ * route of the kind that it wants to use. Clicking on the button opens
+ * a {@link MediaRouteChooserDialog} to allow the user to select a route.
+ * If no non-default routes match the selector and it is not possible for an active
+ * scan to discover any matching routes, then the button is disabled and cannot
+ * be clicked.
+ * </p><p>
+ * When a non-default route is selected that matches the selector, the button will
+ * appear in an active state indicating that the application is connected
+ * to a route of the kind that it wants to use. The button may also appear
+ * in an intermediary connecting state if the route is in the process of connecting
+ * to the destination but has not yet completed doing so. In either case, clicking
+ * on the button opens a {@link MediaRouteControllerDialog} to allow the user
+ * to control or disconnect from the current route.
+ * </p>
+ *
+ * <h3>Prerequisites</h3>
+ * <p>
+ * To use the media route button, the activity must be a subclass of
+ * {@link FragmentActivity} from the <code>android.support.v4</code>
+ * support library. Refer to support library documentation for details.
+ * </p>
+ *
+ * @see MediaRouteActionProvider
+ * @see #setRouteSelector
+ */
+public class MediaRouteButton extends View {
+ private static final String TAG = "MediaRouteButton";
+
+ private static final String CHOOSER_FRAGMENT_TAG =
+ "android.support.v7.mediarouter:MediaRouteChooserDialogFragment";
+ private static final String CONTROLLER_FRAGMENT_TAG =
+ "android.support.v7.mediarouter:MediaRouteControllerDialogFragment";
+
+ private final MediaRouter mRouter;
+ private final MediaRouterCallback mCallback;
+
+ private MediaRouteSelector mSelector = MediaRouteSelector.EMPTY;
+ private MediaRouteDialogFactory mDialogFactory = MediaRouteDialogFactory.getDefault();
+
+ private boolean mAttachedToWindow;
+
+ private static final SparseArray<Drawable.ConstantState> sRemoteIndicatorCache =
+ new SparseArray<>(2);
+ private RemoteIndicatorLoader mRemoteIndicatorLoader;
+ private Drawable mRemoteIndicator;
+ private boolean mRemoteActive;
+ private boolean mIsConnecting;
+
+ private ColorStateList mButtonTint;
+ private int mMinWidth;
+ private int mMinHeight;
+
+ // The checked state is used when connected to a remote route.
+ private static final int[] CHECKED_STATE_SET = {
+ android.R.attr.state_checked
+ };
+
+ // The checkable state is used while connecting to a remote route.
+ private static final int[] CHECKABLE_STATE_SET = {
+ android.R.attr.state_checkable
+ };
+
+ public MediaRouteButton(Context context) {
+ this(context, null);
+ }
+
+ public MediaRouteButton(Context context, AttributeSet attrs) {
+ this(context, attrs, R.attr.mediaRouteButtonStyle);
+ }
+
+ public MediaRouteButton(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(MediaRouterThemeHelper.createThemedButtonContext(context), attrs, defStyleAttr);
+ context = getContext();
+
+ mRouter = MediaRouter.getInstance(context);
+ mCallback = new MediaRouterCallback();
+
+ Resources.Theme theme = ApiHelper.getLibResources().newTheme();
+ theme.applyStyle(MediaRouterThemeHelper.getRouterThemeId(context), true);
+ TypedArray a = theme.obtainStyledAttributes(attrs,
+ R.styleable.MediaRouteButton, defStyleAttr, 0);
+
+ mButtonTint = a.getColorStateList(R.styleable.MediaRouteButton_mediaRouteButtonTint);
+ mMinWidth = a.getDimensionPixelSize(
+ R.styleable.MediaRouteButton_android_minWidth, 0);
+ mMinHeight = a.getDimensionPixelSize(
+ R.styleable.MediaRouteButton_android_minHeight, 0);
+ int remoteIndicatorResId = a.getResourceId(
+ R.styleable.MediaRouteButton_externalRouteEnabledDrawable, 0);
+ a.recycle();
+
+ if (remoteIndicatorResId != 0) {
+ Drawable.ConstantState remoteIndicatorState =
+ sRemoteIndicatorCache.get(remoteIndicatorResId);
+ if (remoteIndicatorState != null) {
+ setRemoteIndicatorDrawable(remoteIndicatorState.newDrawable());
+ } else {
+ mRemoteIndicatorLoader = new RemoteIndicatorLoader(remoteIndicatorResId);
+ mRemoteIndicatorLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ }
+ }
+
+ updateContentDescription();
+ setClickable(true);
+ }
+
+ /**
+ * Gets the media route selector for filtering the routes that the user can
+ * select using the media route chooser dialog.
+ *
+ * @return The selector, never null.
+ */
+ @NonNull
+ public MediaRouteSelector getRouteSelector() {
+ return mSelector;
+ }
+
+ /**
+ * Sets the media route selector for filtering the routes that the user can
+ * select using the media route chooser dialog.
+ *
+ * @param selector The selector, must not be null.
+ */
+ public void setRouteSelector(MediaRouteSelector selector) {
+ if (selector == null) {
+ throw new IllegalArgumentException("selector must not be null");
+ }
+
+ if (!mSelector.equals(selector)) {
+ if (mAttachedToWindow) {
+ if (!mSelector.isEmpty()) {
+ mRouter.removeCallback(mCallback);
+ }
+ if (!selector.isEmpty()) {
+ mRouter.addCallback(selector, mCallback);
+ }
+ }
+ mSelector = selector;
+ refreshRoute();
+ }
+ }
+
+ /**
+ * Gets the media route dialog factory to use when showing the route chooser
+ * or controller dialog.
+ *
+ * @return The dialog factory, never null.
+ */
+ @NonNull
+ public MediaRouteDialogFactory getDialogFactory() {
+ return mDialogFactory;
+ }
+
+ /**
+ * Sets the media route dialog factory to use when showing the route chooser
+ * or controller dialog.
+ *
+ * @param factory The dialog factory, must not be null.
+ */
+ public void setDialogFactory(@NonNull MediaRouteDialogFactory factory) {
+ if (factory == null) {
+ throw new IllegalArgumentException("factory must not be null");
+ }
+
+ mDialogFactory = factory;
+ }
+
+ /**
+ * Show the route chooser or controller dialog.
+ * <p>
+ * If the default route is selected or if the currently selected route does
+ * not match the {@link #getRouteSelector selector}, then shows the route chooser dialog.
+ * Otherwise, shows the route controller dialog to offer the user
+ * a choice to disconnect from the route or perform other control actions
+ * such as setting the route's volume.
+ * </p><p>
+ * The application can customize the dialogs by calling {@link #setDialogFactory}
+ * to provide a customized dialog factory.
+ * </p>
+ *
+ * @return True if the dialog was actually shown.
+ *
+ * @throws IllegalStateException if the activity is not a subclass of
+ * {@link FragmentActivity}.
+ */
+ public boolean showDialog() {
+ if (!mAttachedToWindow) {
+ return false;
+ }
+
+ final FragmentManager fm = getFragmentManager();
+ if (fm == null) {
+ throw new IllegalStateException("The activity must be a subclass of FragmentActivity");
+ }
+
+ MediaRouter.RouteInfo route = mRouter.getSelectedRoute();
+ if (route.isDefaultOrBluetooth() || !route.matchesSelector(mSelector)) {
+ if (fm.findFragmentByTag(CHOOSER_FRAGMENT_TAG) != null) {
+ Log.w(TAG, "showDialog(): Route chooser dialog already showing!");
+ return false;
+ }
+ MediaRouteChooserDialogFragment f =
+ mDialogFactory.onCreateChooserDialogFragment();
+ f.setRouteSelector(mSelector);
+ f.show(fm, CHOOSER_FRAGMENT_TAG);
+ } else {
+ if (fm.findFragmentByTag(CONTROLLER_FRAGMENT_TAG) != null) {
+ Log.w(TAG, "showDialog(): Route controller dialog already showing!");
+ return false;
+ }
+ MediaRouteControllerDialogFragment f =
+ mDialogFactory.onCreateControllerDialogFragment();
+ f.show(fm, CONTROLLER_FRAGMENT_TAG);
+ }
+ return true;
+ }
+
+ private FragmentManager getFragmentManager() {
+ Activity activity = getActivity();
+ if (activity instanceof FragmentActivity) {
+ return ((FragmentActivity)activity).getSupportFragmentManager();
+ }
+ return null;
+ }
+
+ private Activity getActivity() {
+ // Gross way of unwrapping the Activity so we can get the FragmentManager
+ Context context = getContext();
+ while (context instanceof ContextWrapper) {
+ if (context instanceof Activity) {
+ return (Activity)context;
+ }
+ context = ((ContextWrapper)context).getBaseContext();
+ }
+ return null;
+ }
+
+ /**
+ * Sets whether to enable showing a toast with the content descriptor of the
+ * button when the button is long pressed.
+ */
+ void setCheatSheetEnabled(boolean enable) {
+ TooltipCompat.setTooltipText(this, enable
+ ? ApiHelper.getLibResources().getString(R.string.mr_button_content_description)
+ : null);
+ }
+
+ @Override
+ public boolean performClick() {
+ // Send the appropriate accessibility events and call listeners
+ boolean handled = super.performClick();
+ if (!handled) {
+ playSoundEffect(SoundEffectConstants.CLICK);
+ }
+ return showDialog() || handled;
+ }
+
+ @Override
+ protected int[] onCreateDrawableState(int extraSpace) {
+ final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
+
+ // Technically we should be handling this more completely, but these
+ // are implementation details here. Checkable is used to express the connecting
+ // drawable state and it's mutually exclusive with check for the purposes
+ // of state selection here.
+ if (mIsConnecting) {
+ mergeDrawableStates(drawableState, CHECKABLE_STATE_SET);
+ } else if (mRemoteActive) {
+ mergeDrawableStates(drawableState, CHECKED_STATE_SET);
+ }
+ return drawableState;
+ }
+
+ @Override
+ protected void drawableStateChanged() {
+ super.drawableStateChanged();
+
+ if (mRemoteIndicator != null) {
+ int[] myDrawableState = getDrawableState();
+ mRemoteIndicator.setState(myDrawableState);
+ invalidate();
+ }
+ }
+
+ /**
+ * Sets a drawable to use as the remote route indicator.
+ */
+ public void setRemoteIndicatorDrawable(Drawable d) {
+ if (mRemoteIndicatorLoader != null) {
+ mRemoteIndicatorLoader.cancel(false);
+ }
+
+ if (mRemoteIndicator != null) {
+ mRemoteIndicator.setCallback(null);
+ unscheduleDrawable(mRemoteIndicator);
+ }
+ if (d != null) {
+ if (mButtonTint != null) {
+ d = DrawableCompat.wrap(d.mutate());
+ DrawableCompat.setTintList(d, mButtonTint);
+ }
+ d.setCallback(this);
+ d.setState(getDrawableState());
+ d.setVisible(getVisibility() == VISIBLE, false);
+ }
+ mRemoteIndicator = d;
+
+ refreshDrawableState();
+ if (mAttachedToWindow && mRemoteIndicator != null
+ && mRemoteIndicator.getCurrent() instanceof AnimationDrawable) {
+ AnimationDrawable curDrawable = (AnimationDrawable) mRemoteIndicator.getCurrent();
+ if (mIsConnecting) {
+ if (!curDrawable.isRunning()) {
+ curDrawable.start();
+ }
+ } else if (mRemoteActive) {
+ if (curDrawable.isRunning()) {
+ curDrawable.stop();
+ }
+ curDrawable.selectDrawable(curDrawable.getNumberOfFrames() - 1);
+ }
+ }
+ }
+
+ @Override
+ protected boolean verifyDrawable(Drawable who) {
+ return super.verifyDrawable(who) || who == mRemoteIndicator;
+ }
+
+ @Override
+ public void jumpDrawablesToCurrentState() {
+ // We can't call super to handle the background so we do it ourselves.
+ //super.jumpDrawablesToCurrentState();
+ if (getBackground() != null) {
+ DrawableCompat.jumpToCurrentState(getBackground());
+ }
+
+ // Handle our own remote indicator.
+ if (mRemoteIndicator != null) {
+ DrawableCompat.jumpToCurrentState(mRemoteIndicator);
+ }
+ }
+
+ @Override
+ public void setVisibility(int visibility) {
+ super.setVisibility(visibility);
+
+ if (mRemoteIndicator != null) {
+ mRemoteIndicator.setVisible(getVisibility() == VISIBLE, false);
+ }
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ mAttachedToWindow = true;
+ if (!mSelector.isEmpty()) {
+ mRouter.addCallback(mSelector, mCallback);
+ }
+ refreshRoute();
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ mAttachedToWindow = false;
+ if (!mSelector.isEmpty()) {
+ mRouter.removeCallback(mCallback);
+ }
+
+ super.onDetachedFromWindow();
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+ final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+ final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+ final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+
+ final int width = Math.max(mMinWidth, mRemoteIndicator != null ?
+ mRemoteIndicator.getIntrinsicWidth() + getPaddingLeft() + getPaddingRight() : 0);
+ final int height = Math.max(mMinHeight, mRemoteIndicator != null ?
+ mRemoteIndicator.getIntrinsicHeight() + getPaddingTop() + getPaddingBottom() : 0);
+
+ int measuredWidth;
+ switch (widthMode) {
+ case MeasureSpec.EXACTLY:
+ measuredWidth = widthSize;
+ break;
+ case MeasureSpec.AT_MOST:
+ measuredWidth = Math.min(widthSize, width);
+ break;
+ default:
+ case MeasureSpec.UNSPECIFIED:
+ measuredWidth = width;
+ break;
+ }
+
+ int measuredHeight;
+ switch (heightMode) {
+ case MeasureSpec.EXACTLY:
+ measuredHeight = heightSize;
+ break;
+ case MeasureSpec.AT_MOST:
+ measuredHeight = Math.min(heightSize, height);
+ break;
+ default:
+ case MeasureSpec.UNSPECIFIED:
+ measuredHeight = height;
+ break;
+ }
+
+ setMeasuredDimension(measuredWidth, measuredHeight);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ if (mRemoteIndicator != null) {
+ final int left = getPaddingLeft();
+ final int right = getWidth() - getPaddingRight();
+ final int top = getPaddingTop();
+ final int bottom = getHeight() - getPaddingBottom();
+
+ final int drawWidth = mRemoteIndicator.getIntrinsicWidth();
+ final int drawHeight = mRemoteIndicator.getIntrinsicHeight();
+ final int drawLeft = left + (right - left - drawWidth) / 2;
+ final int drawTop = top + (bottom - top - drawHeight) / 2;
+
+ mRemoteIndicator.setBounds(drawLeft, drawTop,
+ drawLeft + drawWidth, drawTop + drawHeight);
+ mRemoteIndicator.draw(canvas);
+ }
+ }
+
+ void refreshRoute() {
+ final MediaRouter.RouteInfo route = mRouter.getSelectedRoute();
+ final boolean isRemote = !route.isDefaultOrBluetooth() && route.matchesSelector(mSelector);
+ final boolean isConnecting = isRemote && route.isConnecting();
+ boolean needsRefresh = false;
+ if (mRemoteActive != isRemote) {
+ mRemoteActive = isRemote;
+ needsRefresh = true;
+ }
+ if (mIsConnecting != isConnecting) {
+ mIsConnecting = isConnecting;
+ needsRefresh = true;
+ }
+
+ if (needsRefresh) {
+ updateContentDescription();
+ refreshDrawableState();
+ }
+ if (mAttachedToWindow) {
+ setEnabled(mRouter.isRouteAvailable(mSelector,
+ MediaRouter.AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE));
+ }
+ if (mRemoteIndicator != null
+ && mRemoteIndicator.getCurrent() instanceof AnimationDrawable) {
+ AnimationDrawable curDrawable = (AnimationDrawable) mRemoteIndicator.getCurrent();
+ if (mAttachedToWindow) {
+ if ((needsRefresh || isConnecting) && !curDrawable.isRunning()) {
+ curDrawable.start();
+ }
+ } else if (isRemote && !isConnecting) {
+ // When the route is already connected before the view is attached, show the last
+ // frame of the connected animation immediately.
+ if (curDrawable.isRunning()) {
+ curDrawable.stop();
+ }
+ curDrawable.selectDrawable(curDrawable.getNumberOfFrames() - 1);
+ }
+ }
+ }
+
+ private void updateContentDescription() {
+ int resId;
+ if (mIsConnecting) {
+ resId = R.string.mr_cast_button_connecting;
+ } else if (mRemoteActive) {
+ resId = R.string.mr_cast_button_connected;
+ } else {
+ resId = R.string.mr_cast_button_disconnected;
+ }
+ setContentDescription(ApiHelper.getLibResources().getString(resId));
+ }
+
+ private final class MediaRouterCallback extends MediaRouter.Callback {
+ MediaRouterCallback() {
+ }
+
+ @Override
+ public void onRouteAdded(MediaRouter router, MediaRouter.RouteInfo info) {
+ refreshRoute();
+ }
+
+ @Override
+ public void onRouteRemoved(MediaRouter router, MediaRouter.RouteInfo info) {
+ refreshRoute();
+ }
+
+ @Override
+ public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo info) {
+ refreshRoute();
+ }
+
+ @Override
+ public void onRouteSelected(MediaRouter router, MediaRouter.RouteInfo info) {
+ refreshRoute();
+ }
+
+ @Override
+ public void onRouteUnselected(MediaRouter router, MediaRouter.RouteInfo info) {
+ refreshRoute();
+ }
+
+ @Override
+ public void onProviderAdded(MediaRouter router, MediaRouter.ProviderInfo provider) {
+ refreshRoute();
+ }
+
+ @Override
+ public void onProviderRemoved(MediaRouter router, MediaRouter.ProviderInfo provider) {
+ refreshRoute();
+ }
+
+ @Override
+ public void onProviderChanged(MediaRouter router, MediaRouter.ProviderInfo provider) {
+ refreshRoute();
+ }
+ }
+
+ private final class RemoteIndicatorLoader extends AsyncTask<Void, Void, Drawable> {
+ private final int mResId;
+
+ RemoteIndicatorLoader(int resId) {
+ mResId = resId;
+ }
+
+ @Override
+ protected Drawable doInBackground(Void... params) {
+ return ApiHelper.getLibResources().getDrawable(mResId);
+ }
+
+ @Override
+ protected void onPostExecute(Drawable remoteIndicator) {
+ cacheAndReset(remoteIndicator);
+ setRemoteIndicatorDrawable(remoteIndicator);
+ }
+
+ @Override
+ protected void onCancelled(Drawable remoteIndicator) {
+ cacheAndReset(remoteIndicator);
+ }
+
+ private void cacheAndReset(Drawable remoteIndicator) {
+ if (remoteIndicator != null) {
+ sRemoteIndicatorCache.put(mResId, remoteIndicator.getConstantState());
+ }
+ mRemoteIndicatorLoader = null;
+ }
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteChooserDialog.java b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteChooserDialog.java
new file mode 100644
index 0000000..cc7c3d5
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteChooserDialog.java
@@ -0,0 +1,392 @@
+/*
+ * 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.support.mediarouter.app;
+
+import static com.android.support.mediarouter.media.MediaRouter.RouteInfo.CONNECTION_STATE_CONNECTED;
+import static com.android.support.mediarouter.media.MediaRouter.RouteInfo.CONNECTION_STATE_CONNECTING;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemClock;
+import android.support.v7.app.AppCompatDialog;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import com.android.media.update.R;
+import com.android.support.mediarouter.media.MediaRouteSelector;
+import com.android.support.mediarouter.media.MediaRouter;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * This class implements the route chooser dialog for {@link MediaRouter}.
+ * <p>
+ * This dialog allows the user to choose a route that matches a given selector.
+ * </p>
+ *
+ * @see MediaRouteButton
+ * @see MediaRouteActionProvider
+ */
+public class MediaRouteChooserDialog extends AppCompatDialog {
+ static final String TAG = "MediaRouteChooserDialog";
+
+ // Do not update the route list immediately to avoid unnatural dialog change.
+ private static final long UPDATE_ROUTES_DELAY_MS = 300L;
+ static final int MSG_UPDATE_ROUTES = 1;
+
+ private final MediaRouter mRouter;
+ private final MediaRouterCallback mCallback;
+
+ private TextView mTitleView;
+ private MediaRouteSelector mSelector = MediaRouteSelector.EMPTY;
+ private ArrayList<MediaRouter.RouteInfo> mRoutes;
+ private RouteAdapter mAdapter;
+ private ListView mListView;
+ private boolean mAttachedToWindow;
+ private long mLastUpdateTime;
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case MSG_UPDATE_ROUTES:
+ updateRoutes((List<MediaRouter.RouteInfo>) message.obj);
+ break;
+ }
+ }
+ };
+
+ public MediaRouteChooserDialog(Context context) {
+ this(context, 0);
+ }
+
+ public MediaRouteChooserDialog(Context context, int theme) {
+ super(context = MediaRouterThemeHelper.createThemedDialogContext(context, theme, false),
+ MediaRouterThemeHelper.createThemedDialogStyle(context));
+ context = getContext();
+
+ mRouter = MediaRouter.getInstance(context);
+ mCallback = new MediaRouterCallback();
+ }
+
+ /**
+ * Gets the media route selector for filtering the routes that the user can select.
+ *
+ * @return The selector, never null.
+ */
+ @NonNull
+ public MediaRouteSelector getRouteSelector() {
+ return mSelector;
+ }
+
+ /**
+ * Sets the media route selector for filtering the routes that the user can select.
+ *
+ * @param selector The selector, must not be null.
+ */
+ public void setRouteSelector(@NonNull MediaRouteSelector selector) {
+ if (selector == null) {
+ throw new IllegalArgumentException("selector must not be null");
+ }
+
+ if (!mSelector.equals(selector)) {
+ mSelector = selector;
+
+ if (mAttachedToWindow) {
+ mRouter.removeCallback(mCallback);
+ mRouter.addCallback(selector, mCallback,
+ MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
+ }
+
+ refreshRoutes();
+ }
+ }
+
+ /**
+ * Called to filter the set of routes that should be included in the list.
+ * <p>
+ * The default implementation iterates over all routes in the provided list and
+ * removes those for which {@link #onFilterRoute} returns false.
+ * </p>
+ *
+ * @param routes The list of routes to filter in-place, never null.
+ */
+ public void onFilterRoutes(@NonNull List<MediaRouter.RouteInfo> routes) {
+ for (int i = routes.size(); i-- > 0; ) {
+ if (!onFilterRoute(routes.get(i))) {
+ routes.remove(i);
+ }
+ }
+ }
+
+ /**
+ * Returns true if the route should be included in the list.
+ * <p>
+ * The default implementation returns true for enabled non-default routes that
+ * match the selector. Subclasses can override this method to filter routes
+ * differently.
+ * </p>
+ *
+ * @param route The route to consider, never null.
+ * @return True if the route should be included in the chooser dialog.
+ */
+ public boolean onFilterRoute(@NonNull MediaRouter.RouteInfo route) {
+ return !route.isDefaultOrBluetooth() && route.isEnabled()
+ && route.matchesSelector(mSelector);
+ }
+
+ @Override
+ public void setTitle(CharSequence title) {
+ mTitleView.setText(title);
+ }
+
+ @Override
+ public void setTitle(int titleId) {
+ mTitleView.setText(titleId);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.mr_chooser_dialog);
+
+ mRoutes = new ArrayList<>();
+ mAdapter = new RouteAdapter(getContext(), mRoutes);
+ mListView = (ListView)findViewById(R.id.mr_chooser_list);
+ mListView.setAdapter(mAdapter);
+ mListView.setOnItemClickListener(mAdapter);
+ mListView.setEmptyView(findViewById(android.R.id.empty));
+ mTitleView = findViewById(R.id.mr_chooser_title);
+
+ updateLayout();
+ }
+
+ /**
+ * Sets the width of the dialog. Also called when configuration changes.
+ */
+ void updateLayout() {
+ getWindow().setLayout(MediaRouteDialogHelper.getDialogWidth(getContext()),
+ ViewGroup.LayoutParams.WRAP_CONTENT);
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ mAttachedToWindow = true;
+ mRouter.addCallback(mSelector, mCallback, MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
+ refreshRoutes();
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ mAttachedToWindow = false;
+ mRouter.removeCallback(mCallback);
+ mHandler.removeMessages(MSG_UPDATE_ROUTES);
+
+ super.onDetachedFromWindow();
+ }
+
+ /**
+ * Refreshes the list of routes that are shown in the chooser dialog.
+ */
+ public void refreshRoutes() {
+ if (mAttachedToWindow) {
+ ArrayList<MediaRouter.RouteInfo> routes = new ArrayList<>(mRouter.getRoutes());
+ onFilterRoutes(routes);
+ Collections.sort(routes, RouteComparator.sInstance);
+ if (SystemClock.uptimeMillis() - mLastUpdateTime >= UPDATE_ROUTES_DELAY_MS) {
+ updateRoutes(routes);
+ } else {
+ mHandler.removeMessages(MSG_UPDATE_ROUTES);
+ mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_UPDATE_ROUTES, routes),
+ mLastUpdateTime + UPDATE_ROUTES_DELAY_MS);
+ }
+ }
+ }
+
+ void updateRoutes(List<MediaRouter.RouteInfo> routes) {
+ mLastUpdateTime = SystemClock.uptimeMillis();
+ mRoutes.clear();
+ mRoutes.addAll(routes);
+ mAdapter.notifyDataSetChanged();
+ }
+
+ private final class RouteAdapter extends ArrayAdapter<MediaRouter.RouteInfo>
+ implements ListView.OnItemClickListener {
+ private final LayoutInflater mInflater;
+ private final Drawable mDefaultIcon;
+ private final Drawable mTvIcon;
+ private final Drawable mSpeakerIcon;
+ private final Drawable mSpeakerGroupIcon;
+
+ public RouteAdapter(Context context, List<MediaRouter.RouteInfo> routes) {
+ super(context, 0, routes);
+ mInflater = LayoutInflater.from(context);
+ TypedArray styledAttributes = getContext().obtainStyledAttributes(new int[] {
+ R.attr.mediaRouteDefaultIconDrawable,
+ R.attr.mediaRouteTvIconDrawable,
+ R.attr.mediaRouteSpeakerIconDrawable,
+ R.attr.mediaRouteSpeakerGroupIconDrawable});
+ mDefaultIcon = styledAttributes.getDrawable(0);
+ mTvIcon = styledAttributes.getDrawable(1);
+ mSpeakerIcon = styledAttributes.getDrawable(2);
+ mSpeakerGroupIcon = styledAttributes.getDrawable(3);
+ styledAttributes.recycle();
+ }
+
+ @Override
+ public boolean areAllItemsEnabled() {
+ return false;
+ }
+
+ @Override
+ public boolean isEnabled(int position) {
+ return getItem(position).isEnabled();
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View view = convertView;
+ if (view == null) {
+ view = mInflater.inflate(R.layout.mr_chooser_list_item, parent, false);
+ }
+
+ MediaRouter.RouteInfo route = getItem(position);
+ TextView text1 = (TextView) view.findViewById(R.id.mr_chooser_route_name);
+ TextView text2 = (TextView) view.findViewById(R.id.mr_chooser_route_desc);
+ text1.setText(route.getName());
+ String description = route.getDescription();
+ boolean isConnectedOrConnecting =
+ route.getConnectionState() == CONNECTION_STATE_CONNECTED
+ || route.getConnectionState() == CONNECTION_STATE_CONNECTING;
+ if (isConnectedOrConnecting && !TextUtils.isEmpty(description)) {
+ text1.setGravity(Gravity.BOTTOM);
+ text2.setVisibility(View.VISIBLE);
+ text2.setText(description);
+ } else {
+ text1.setGravity(Gravity.CENTER_VERTICAL);
+ text2.setVisibility(View.GONE);
+ text2.setText("");
+ }
+ view.setEnabled(route.isEnabled());
+
+ ImageView iconView = (ImageView) view.findViewById(R.id.mr_chooser_route_icon);
+ if (iconView != null) {
+ iconView.setImageDrawable(getIconDrawable(route));
+ }
+ return view;
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ MediaRouter.RouteInfo route = getItem(position);
+ if (route.isEnabled()) {
+ route.select();
+ dismiss();
+ }
+ }
+
+ private Drawable getIconDrawable(MediaRouter.RouteInfo route) {
+ Uri iconUri = route.getIconUri();
+ if (iconUri != null) {
+ try {
+ InputStream is = getContext().getContentResolver().openInputStream(iconUri);
+ Drawable drawable = Drawable.createFromStream(is, null);
+ if (drawable != null) {
+ return drawable;
+ }
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to load " + iconUri, e);
+ // Falls back.
+ }
+ }
+ return getDefaultIconDrawable(route);
+ }
+
+ private Drawable getDefaultIconDrawable(MediaRouter.RouteInfo route) {
+ // If the type of the receiver device is specified, use it.
+ switch (route.getDeviceType()) {
+ case MediaRouter.RouteInfo.DEVICE_TYPE_TV:
+ return mTvIcon;
+ case MediaRouter.RouteInfo.DEVICE_TYPE_SPEAKER:
+ return mSpeakerIcon;
+ }
+
+ // Otherwise, make the best guess based on other route information.
+ if (route instanceof MediaRouter.RouteGroup) {
+ // Only speakers can be grouped for now.
+ return mSpeakerGroupIcon;
+ }
+ return mDefaultIcon;
+ }
+ }
+
+ private final class MediaRouterCallback extends MediaRouter.Callback {
+ MediaRouterCallback() {
+ }
+
+ @Override
+ public void onRouteAdded(MediaRouter router, MediaRouter.RouteInfo info) {
+ refreshRoutes();
+ }
+
+ @Override
+ public void onRouteRemoved(MediaRouter router, MediaRouter.RouteInfo info) {
+ refreshRoutes();
+ }
+
+ @Override
+ public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo info) {
+ refreshRoutes();
+ }
+
+ @Override
+ public void onRouteSelected(MediaRouter router, MediaRouter.RouteInfo route) {
+ dismiss();
+ }
+ }
+
+ static final class RouteComparator implements Comparator<MediaRouter.RouteInfo> {
+ public static final RouteComparator sInstance = new RouteComparator();
+
+ @Override
+ public int compare(MediaRouter.RouteInfo lhs, MediaRouter.RouteInfo rhs) {
+ return lhs.getName().compareToIgnoreCase(rhs.getName());
+ }
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteChooserDialogFragment.java b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteChooserDialogFragment.java
new file mode 100644
index 0000000..2f85fb3
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteChooserDialogFragment.java
@@ -0,0 +1,126 @@
+/*
+ * 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.support.mediarouter.app;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.support.v4.app.DialogFragment;
+
+import com.android.support.mediarouter.media.MediaRouteSelector;
+
+/**
+ * Media route chooser dialog fragment.
+ * <p>
+ * Creates a {@link MediaRouteChooserDialog}. The application may subclass
+ * this dialog fragment to customize the media route chooser dialog.
+ * </p>
+ */
+public class MediaRouteChooserDialogFragment extends DialogFragment {
+ private final String ARGUMENT_SELECTOR = "selector";
+
+ private MediaRouteChooserDialog mDialog;
+ private MediaRouteSelector mSelector;
+
+ /**
+ * Creates a media route chooser dialog fragment.
+ * <p>
+ * All subclasses of this class must also possess a default constructor.
+ * </p>
+ */
+ public MediaRouteChooserDialogFragment() {
+ setCancelable(true);
+ }
+
+ /**
+ * Gets the media route selector for filtering the routes that the user can select.
+ *
+ * @return The selector, never null.
+ */
+ public MediaRouteSelector getRouteSelector() {
+ ensureRouteSelector();
+ return mSelector;
+ }
+
+ private void ensureRouteSelector() {
+ if (mSelector == null) {
+ Bundle args = getArguments();
+ if (args != null) {
+ mSelector = MediaRouteSelector.fromBundle(args.getBundle(ARGUMENT_SELECTOR));
+ }
+ if (mSelector == null) {
+ mSelector = MediaRouteSelector.EMPTY;
+ }
+ }
+ }
+
+ /**
+ * Sets the media route selector for filtering the routes that the user can select.
+ * This method must be called before the fragment is added.
+ *
+ * @param selector The selector to set.
+ */
+ public void setRouteSelector(MediaRouteSelector selector) {
+ if (selector == null) {
+ throw new IllegalArgumentException("selector must not be null");
+ }
+
+ ensureRouteSelector();
+ if (!mSelector.equals(selector)) {
+ mSelector = selector;
+
+ Bundle args = getArguments();
+ if (args == null) {
+ args = new Bundle();
+ }
+ args.putBundle(ARGUMENT_SELECTOR, selector.asBundle());
+ setArguments(args);
+
+ MediaRouteChooserDialog dialog = (MediaRouteChooserDialog)getDialog();
+ if (dialog != null) {
+ dialog.setRouteSelector(selector);
+ }
+ }
+ }
+
+ /**
+ * Called when the chooser dialog is being created.
+ * <p>
+ * Subclasses may override this method to customize the dialog.
+ * </p>
+ */
+ public MediaRouteChooserDialog onCreateChooserDialog(
+ Context context, Bundle savedInstanceState) {
+ return new MediaRouteChooserDialog(context);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ mDialog = onCreateChooserDialog(getContext(), savedInstanceState);
+ mDialog.setRouteSelector(getRouteSelector());
+ return mDialog;
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ if (mDialog != null) {
+ mDialog.updateLayout();
+ }
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteControllerDialog.java b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteControllerDialog.java
new file mode 100644
index 0000000..942797b
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteControllerDialog.java
@@ -0,0 +1,1481 @@
+/*
+ * 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.support.mediarouter.app;
+
+import static android.support.v4.media.session.PlaybackStateCompat.ACTION_PAUSE;
+import static android.support.v4.media.session.PlaybackStateCompat.ACTION_PLAY;
+import static android.support.v4.media.session.PlaybackStateCompat.ACTION_PLAY_PAUSE;
+import static android.support.v4.media.session.PlaybackStateCompat.ACTION_STOP;
+
+import android.app.PendingIntent;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.support.v4.media.MediaDescriptionCompat;
+import android.support.v4.media.MediaMetadataCompat;
+import android.support.v4.media.session.MediaControllerCompat;
+import android.support.v4.media.session.MediaSessionCompat;
+import android.support.v4.media.session.PlaybackStateCompat;
+import android.support.v4.util.ObjectsCompat;
+import android.support.v4.view.accessibility.AccessibilityEventCompat;
+import android.support.v7.app.AlertDialog;
+import android.support.v7.graphics.Palette;
+
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.MeasureSpec;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+import android.view.animation.Transformation;
+import android.view.animation.TranslateAnimation;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.FrameLayout;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+import com.android.media.update.R;
+import com.android.support.mediarouter.media.MediaRouteSelector;
+import com.android.support.mediarouter.media.MediaRouter;
+import com.android.support.mediarouter.app.OverlayListView.OverlayObject;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * This class implements the route controller dialog for {@link MediaRouter}.
+ * <p>
+ * This dialog allows the user to control or disconnect from the currently selected route.
+ * </p>
+ *
+ * @see MediaRouteButton
+ * @see MediaRouteActionProvider
+ */
+public class MediaRouteControllerDialog extends AlertDialog {
+ // Tags should be less than 24 characters long (see docs for android.util.Log.isLoggable())
+ static final String TAG = "MediaRouteCtrlDialog";
+ static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ // Time to wait before updating the volume when the user lets go of the seek bar
+ // to allow the route provider time to propagate the change and publish a new
+ // route descriptor.
+ static final int VOLUME_UPDATE_DELAY_MILLIS = 500;
+ static final int CONNECTION_TIMEOUT_MILLIS = (int) TimeUnit.SECONDS.toMillis(30L);
+
+ private static final int BUTTON_NEUTRAL_RES_ID = android.R.id.button3;
+ static final int BUTTON_DISCONNECT_RES_ID = android.R.id.button2;
+ static final int BUTTON_STOP_RES_ID = android.R.id.button1;
+
+ final MediaRouter mRouter;
+ private final MediaRouterCallback mCallback;
+ final MediaRouter.RouteInfo mRoute;
+
+ Context mContext;
+ private boolean mCreated;
+ private boolean mAttachedToWindow;
+
+ private int mDialogContentWidth;
+
+ private View mCustomControlView;
+
+ private Button mDisconnectButton;
+ private Button mStopCastingButton;
+ private ImageButton mPlaybackControlButton;
+ private ImageButton mCloseButton;
+ private MediaRouteExpandCollapseButton mGroupExpandCollapseButton;
+
+ private FrameLayout mExpandableAreaLayout;
+ private LinearLayout mDialogAreaLayout;
+ FrameLayout mDefaultControlLayout;
+ private FrameLayout mCustomControlLayout;
+ private ImageView mArtView;
+ private TextView mTitleView;
+ private TextView mSubtitleView;
+ private TextView mRouteNameTextView;
+
+ private boolean mVolumeControlEnabled = true;
+ // Layout for media controllers including play/pause button and the main volume slider.
+ private LinearLayout mMediaMainControlLayout;
+ private RelativeLayout mPlaybackControlLayout;
+ private LinearLayout mVolumeControlLayout;
+ private View mDividerView;
+
+ OverlayListView mVolumeGroupList;
+ VolumeGroupAdapter mVolumeGroupAdapter;
+ private List<MediaRouter.RouteInfo> mGroupMemberRoutes;
+ Set<MediaRouter.RouteInfo> mGroupMemberRoutesAdded;
+ private Set<MediaRouter.RouteInfo> mGroupMemberRoutesRemoved;
+ Set<MediaRouter.RouteInfo> mGroupMemberRoutesAnimatingWithBitmap;
+ SeekBar mVolumeSlider;
+ VolumeChangeListener mVolumeChangeListener;
+ MediaRouter.RouteInfo mRouteInVolumeSliderTouched;
+ private int mVolumeGroupListItemIconSize;
+ private int mVolumeGroupListItemHeight;
+ private int mVolumeGroupListMaxHeight;
+ private final int mVolumeGroupListPaddingTop;
+ Map<MediaRouter.RouteInfo, SeekBar> mVolumeSliderMap;
+
+ MediaControllerCompat mMediaController;
+ MediaControllerCallback mControllerCallback;
+ PlaybackStateCompat mState;
+ MediaDescriptionCompat mDescription;
+
+ FetchArtTask mFetchArtTask;
+ Bitmap mArtIconBitmap;
+ Uri mArtIconUri;
+ boolean mArtIconIsLoaded;
+ Bitmap mArtIconLoadedBitmap;
+ int mArtIconBackgroundColor;
+
+ boolean mHasPendingUpdate;
+ boolean mPendingUpdateAnimationNeeded;
+
+ boolean mIsGroupExpanded;
+ boolean mIsGroupListAnimating;
+ boolean mIsGroupListAnimationPending;
+ int mGroupListAnimationDurationMs;
+ private int mGroupListFadeInDurationMs;
+ private int mGroupListFadeOutDurationMs;
+
+ private Interpolator mInterpolator;
+ private Interpolator mLinearOutSlowInInterpolator;
+ private Interpolator mFastOutSlowInInterpolator;
+ private Interpolator mAccelerateDecelerateInterpolator;
+
+ final AccessibilityManager mAccessibilityManager;
+
+ Runnable mGroupListFadeInAnimation = new Runnable() {
+ @Override
+ public void run() {
+ startGroupListFadeInAnimation();
+ }
+ };
+
+ public MediaRouteControllerDialog(Context context) {
+ this(context, 0);
+ }
+
+ public MediaRouteControllerDialog(Context context, int theme) {
+ super(context = MediaRouterThemeHelper.createThemedDialogContext(context, theme, true),
+ MediaRouterThemeHelper.createThemedDialogStyle(context));
+ mContext = getContext();
+
+ mControllerCallback = new MediaControllerCallback();
+ mRouter = MediaRouter.getInstance(mContext);
+ mCallback = new MediaRouterCallback();
+ mRoute = mRouter.getSelectedRoute();
+ setMediaSession(mRouter.getMediaSessionToken());
+ mVolumeGroupListPaddingTop = mContext.getResources().getDimensionPixelSize(
+ R.dimen.mr_controller_volume_group_list_padding_top);
+ mAccessibilityManager =
+ (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
+ if (android.os.Build.VERSION.SDK_INT >= 21) {
+ mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
+ R.interpolator.mr_linear_out_slow_in);
+ mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
+ R.interpolator.mr_fast_out_slow_in);
+ }
+ mAccelerateDecelerateInterpolator = new AccelerateDecelerateInterpolator();
+ }
+
+ /**
+ * Gets the route that this dialog is controlling.
+ */
+ public MediaRouter.RouteInfo getRoute() {
+ return mRoute;
+ }
+
+ private MediaRouter.RouteGroup getGroup() {
+ if (mRoute instanceof MediaRouter.RouteGroup) {
+ return (MediaRouter.RouteGroup) mRoute;
+ }
+ return null;
+ }
+
+ /**
+ * Provides the subclass an opportunity to create a view that will replace the default media
+ * controls for the currently playing content.
+ *
+ * @param savedInstanceState The dialog's saved instance state.
+ * @return The media control view, or null if none.
+ */
+ public View onCreateMediaControlView(Bundle savedInstanceState) {
+ return null;
+ }
+
+ /**
+ * Gets the media control view that was created by {@link #onCreateMediaControlView(Bundle)}.
+ *
+ * @return The media control view, or null if none.
+ */
+ public View getMediaControlView() {
+ return mCustomControlView;
+ }
+
+ /**
+ * Sets whether to enable the volume slider and volume control using the volume keys
+ * when the route supports it.
+ * <p>
+ * The default value is true.
+ * </p>
+ */
+ public void setVolumeControlEnabled(boolean enable) {
+ if (mVolumeControlEnabled != enable) {
+ mVolumeControlEnabled = enable;
+ if (mCreated) {
+ update(false);
+ }
+ }
+ }
+
+ /**
+ * Returns whether to enable the volume slider and volume control using the volume keys
+ * when the route supports it.
+ */
+ public boolean isVolumeControlEnabled() {
+ return mVolumeControlEnabled;
+ }
+
+ /**
+ * Set the session to use for metadata and transport controls. The dialog
+ * will listen to changes on this session and update the UI automatically in
+ * response to changes.
+ *
+ * @param sessionToken The token for the session to use.
+ */
+ private void setMediaSession(MediaSessionCompat.Token sessionToken) {
+ if (mMediaController != null) {
+ mMediaController.unregisterCallback(mControllerCallback);
+ mMediaController = null;
+ }
+ if (sessionToken == null) {
+ return;
+ }
+ if (!mAttachedToWindow) {
+ return;
+ }
+ try {
+ mMediaController = new MediaControllerCompat(mContext, sessionToken);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error creating media controller in setMediaSession.", e);
+ }
+ if (mMediaController != null) {
+ mMediaController.registerCallback(mControllerCallback);
+ }
+ MediaMetadataCompat metadata = mMediaController == null ? null
+ : mMediaController.getMetadata();
+ mDescription = metadata == null ? null : metadata.getDescription();
+ mState = mMediaController == null ? null : mMediaController.getPlaybackState();
+ updateArtIconIfNeeded();
+ update(false);
+ }
+
+ /**
+ * Gets the session to use for metadata and transport controls.
+ *
+ * @return The token for the session to use or null if none.
+ */
+ public MediaSessionCompat.Token getMediaSession() {
+ return mMediaController == null ? null : mMediaController.getSessionToken();
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ getWindow().setBackgroundDrawableResource(android.R.color.transparent);
+ setContentView(R.layout.mr_controller_material_dialog_b);
+
+ // Remove the neutral button.
+ findViewById(BUTTON_NEUTRAL_RES_ID).setVisibility(View.GONE);
+
+ ClickListener listener = new ClickListener();
+
+ mExpandableAreaLayout = findViewById(R.id.mr_expandable_area);
+ mExpandableAreaLayout.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ dismiss();
+ }
+ });
+ mDialogAreaLayout = findViewById(R.id.mr_dialog_area);
+ mDialogAreaLayout.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // Eat unhandled touch events.
+ }
+ });
+ int color = MediaRouterThemeHelper.getButtonTextColor(mContext);
+ mDisconnectButton = findViewById(BUTTON_DISCONNECT_RES_ID);
+ mDisconnectButton.setText(R.string.mr_controller_disconnect);
+ mDisconnectButton.setTextColor(color);
+ mDisconnectButton.setOnClickListener(listener);
+
+ mStopCastingButton = findViewById(BUTTON_STOP_RES_ID);
+ mStopCastingButton.setText(R.string.mr_controller_stop_casting);
+ mStopCastingButton.setTextColor(color);
+ mStopCastingButton.setOnClickListener(listener);
+
+ mRouteNameTextView = findViewById(R.id.mr_name);
+ mCloseButton = findViewById(R.id.mr_close);
+ mCloseButton.setOnClickListener(listener);
+ mCustomControlLayout = findViewById(R.id.mr_custom_control);
+ mDefaultControlLayout = findViewById(R.id.mr_default_control);
+
+ // Start the session activity when a content item (album art, title or subtitle) is clicked.
+ View.OnClickListener onClickListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mMediaController != null) {
+ PendingIntent pi = mMediaController.getSessionActivity();
+ if (pi != null) {
+ try {
+ pi.send();
+ dismiss();
+ } catch (PendingIntent.CanceledException e) {
+ Log.e(TAG, pi + " was not sent, it had been canceled.");
+ }
+ }
+ }
+ }
+ };
+ mArtView = findViewById(R.id.mr_art);
+ mArtView.setOnClickListener(onClickListener);
+ findViewById(R.id.mr_control_title_container).setOnClickListener(onClickListener);
+
+ mMediaMainControlLayout = findViewById(R.id.mr_media_main_control);
+ mDividerView = findViewById(R.id.mr_control_divider);
+
+ mPlaybackControlLayout = findViewById(R.id.mr_playback_control);
+ mTitleView = findViewById(R.id.mr_control_title);
+ mSubtitleView = findViewById(R.id.mr_control_subtitle);
+ mPlaybackControlButton = findViewById(R.id.mr_control_playback_ctrl);
+ mPlaybackControlButton.setOnClickListener(listener);
+
+ mVolumeControlLayout = findViewById(R.id.mr_volume_control);
+ mVolumeControlLayout.setVisibility(View.GONE);
+ mVolumeSlider = findViewById(R.id.mr_volume_slider);
+ mVolumeSlider.setTag(mRoute);
+ mVolumeChangeListener = new VolumeChangeListener();
+ mVolumeSlider.setOnSeekBarChangeListener(mVolumeChangeListener);
+
+ mVolumeGroupList = findViewById(R.id.mr_volume_group_list);
+ mGroupMemberRoutes = new ArrayList<MediaRouter.RouteInfo>();
+ mVolumeGroupAdapter = new VolumeGroupAdapter(mVolumeGroupList.getContext(),
+ mGroupMemberRoutes);
+ mVolumeGroupList.setAdapter(mVolumeGroupAdapter);
+ mGroupMemberRoutesAnimatingWithBitmap = new HashSet<>();
+
+ MediaRouterThemeHelper.setMediaControlsBackgroundColor(mContext,
+ mMediaMainControlLayout, mVolumeGroupList, getGroup() != null);
+ MediaRouterThemeHelper.setVolumeSliderColor(mContext,
+ (MediaRouteVolumeSlider) mVolumeSlider, mMediaMainControlLayout);
+ mVolumeSliderMap = new HashMap<>();
+ mVolumeSliderMap.put(mRoute, mVolumeSlider);
+
+ mGroupExpandCollapseButton =
+ findViewById(R.id.mr_group_expand_collapse);
+ mGroupExpandCollapseButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mIsGroupExpanded = !mIsGroupExpanded;
+ if (mIsGroupExpanded) {
+ mVolumeGroupList.setVisibility(View.VISIBLE);
+ }
+ loadInterpolator();
+ updateLayoutHeight(true);
+ }
+ });
+ loadInterpolator();
+ mGroupListAnimationDurationMs = mContext.getResources().getInteger(
+ R.integer.mr_controller_volume_group_list_animation_duration_ms);
+ mGroupListFadeInDurationMs = mContext.getResources().getInteger(
+ R.integer.mr_controller_volume_group_list_fade_in_duration_ms);
+ mGroupListFadeOutDurationMs = mContext.getResources().getInteger(
+ R.integer.mr_controller_volume_group_list_fade_out_duration_ms);
+
+ mCustomControlView = onCreateMediaControlView(savedInstanceState);
+ if (mCustomControlView != null) {
+ mCustomControlLayout.addView(mCustomControlView);
+ mCustomControlLayout.setVisibility(View.VISIBLE);
+ }
+ mCreated = true;
+ updateLayout();
+ }
+
+ /**
+ * Sets the width of the dialog. Also called when configuration changes.
+ */
+ void updateLayout() {
+ int width = MediaRouteDialogHelper.getDialogWidth(mContext);
+ getWindow().setLayout(width, ViewGroup.LayoutParams.WRAP_CONTENT);
+
+ View decorView = getWindow().getDecorView();
+ mDialogContentWidth = width - decorView.getPaddingLeft() - decorView.getPaddingRight();
+
+ Resources res = mContext.getResources();
+ mVolumeGroupListItemIconSize = res.getDimensionPixelSize(
+ R.dimen.mr_controller_volume_group_list_item_icon_size);
+ mVolumeGroupListItemHeight = res.getDimensionPixelSize(
+ R.dimen.mr_controller_volume_group_list_item_height);
+ mVolumeGroupListMaxHeight = res.getDimensionPixelSize(
+ R.dimen.mr_controller_volume_group_list_max_height);
+
+ // Fetch art icons again for layout changes to resize it accordingly
+ mArtIconBitmap = null;
+ mArtIconUri = null;
+ updateArtIconIfNeeded();
+ update(false);
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mAttachedToWindow = true;
+
+ mRouter.addCallback(MediaRouteSelector.EMPTY, mCallback,
+ MediaRouter.CALLBACK_FLAG_UNFILTERED_EVENTS);
+ setMediaSession(mRouter.getMediaSessionToken());
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ mRouter.removeCallback(mCallback);
+ setMediaSession(null);
+ mAttachedToWindow = false;
+ super.onDetachedFromWindow();
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
+ || keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
+ mRoute.requestUpdateVolume(keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ? -1 : 1);
+ return true;
+ }
+ return super.onKeyDown(keyCode, event);
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
+ || keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
+ return true;
+ }
+ return super.onKeyUp(keyCode, event);
+ }
+
+ void update(boolean animate) {
+ // Defer dialog updates if a user is adjusting a volume in the list
+ if (mRouteInVolumeSliderTouched != null) {
+ mHasPendingUpdate = true;
+ mPendingUpdateAnimationNeeded |= animate;
+ return;
+ }
+ mHasPendingUpdate = false;
+ mPendingUpdateAnimationNeeded = false;
+ if (!mRoute.isSelected() || mRoute.isDefaultOrBluetooth()) {
+ dismiss();
+ return;
+ }
+ if (!mCreated) {
+ return;
+ }
+
+ mRouteNameTextView.setText(mRoute.getName());
+ mDisconnectButton.setVisibility(mRoute.canDisconnect() ? View.VISIBLE : View.GONE);
+ if (mCustomControlView == null && mArtIconIsLoaded) {
+ if (isBitmapRecycled(mArtIconLoadedBitmap)) {
+ Log.w(TAG, "Can't set artwork image with recycled bitmap: " + mArtIconLoadedBitmap);
+ } else {
+ mArtView.setImageBitmap(mArtIconLoadedBitmap);
+ mArtView.setBackgroundColor(mArtIconBackgroundColor);
+ }
+ clearLoadedBitmap();
+ }
+ updateVolumeControlLayout();
+ updatePlaybackControlLayout();
+ updateLayoutHeight(animate);
+ }
+
+ private boolean isBitmapRecycled(Bitmap bitmap) {
+ return bitmap != null && bitmap.isRecycled();
+ }
+
+ private boolean canShowPlaybackControlLayout() {
+ return mCustomControlView == null && (mDescription != null || mState != null);
+ }
+
+ /**
+ * Returns the height of main media controller which includes playback control and master
+ * volume control.
+ */
+ private int getMainControllerHeight(boolean showPlaybackControl) {
+ int height = 0;
+ if (showPlaybackControl || mVolumeControlLayout.getVisibility() == View.VISIBLE) {
+ height += mMediaMainControlLayout.getPaddingTop()
+ + mMediaMainControlLayout.getPaddingBottom();
+ if (showPlaybackControl) {
+ height += mPlaybackControlLayout.getMeasuredHeight();
+ }
+ if (mVolumeControlLayout.getVisibility() == View.VISIBLE) {
+ height += mVolumeControlLayout.getMeasuredHeight();
+ }
+ if (showPlaybackControl && mVolumeControlLayout.getVisibility() == View.VISIBLE) {
+ height += mDividerView.getMeasuredHeight();
+ }
+ }
+ return height;
+ }
+
+ private void updateMediaControlVisibility(boolean canShowPlaybackControlLayout) {
+ // TODO: Update the top and bottom padding of the control layout according to the display
+ // height.
+ mDividerView.setVisibility((mVolumeControlLayout.getVisibility() == View.VISIBLE
+ && canShowPlaybackControlLayout) ? View.VISIBLE : View.GONE);
+ mMediaMainControlLayout.setVisibility((mVolumeControlLayout.getVisibility() == View.GONE
+ && !canShowPlaybackControlLayout) ? View.GONE : View.VISIBLE);
+ }
+
+ void updateLayoutHeight(final boolean animate) {
+ // We need to defer the update until the first layout has occurred, as we don't yet know the
+ // overall visible display size in which the window this view is attached to has been
+ // positioned in.
+ mDefaultControlLayout.requestLayout();
+ ViewTreeObserver observer = mDefaultControlLayout.getViewTreeObserver();
+ observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ mDefaultControlLayout.getViewTreeObserver().removeGlobalOnLayoutListener(this);
+ if (mIsGroupListAnimating) {
+ mIsGroupListAnimationPending = true;
+ } else {
+ updateLayoutHeightInternal(animate);
+ }
+ }
+ });
+ }
+
+ /**
+ * Updates the height of views and hide artwork or metadata if space is limited.
+ */
+ void updateLayoutHeightInternal(boolean animate) {
+ // Measure the size of widgets and get the height of main components.
+ int oldHeight = getLayoutHeight(mMediaMainControlLayout);
+ setLayoutHeight(mMediaMainControlLayout, ViewGroup.LayoutParams.MATCH_PARENT);
+ updateMediaControlVisibility(canShowPlaybackControlLayout());
+ View decorView = getWindow().getDecorView();
+ decorView.measure(
+ MeasureSpec.makeMeasureSpec(getWindow().getAttributes().width, MeasureSpec.EXACTLY),
+ MeasureSpec.UNSPECIFIED);
+ setLayoutHeight(mMediaMainControlLayout, oldHeight);
+ int artViewHeight = 0;
+ if (mCustomControlView == null && mArtView.getDrawable() instanceof BitmapDrawable) {
+ Bitmap art = ((BitmapDrawable) mArtView.getDrawable()).getBitmap();
+ if (art != null) {
+ artViewHeight = getDesiredArtHeight(art.getWidth(), art.getHeight());
+ mArtView.setScaleType(art.getWidth() >= art.getHeight()
+ ? ImageView.ScaleType.FIT_XY : ImageView.ScaleType.FIT_CENTER);
+ }
+ }
+ int mainControllerHeight = getMainControllerHeight(canShowPlaybackControlLayout());
+ int volumeGroupListCount = mGroupMemberRoutes.size();
+ // Scale down volume group list items in landscape mode.
+ int expandedGroupListHeight = getGroup() == null ? 0 :
+ mVolumeGroupListItemHeight * getGroup().getRoutes().size();
+ if (volumeGroupListCount > 0) {
+ expandedGroupListHeight += mVolumeGroupListPaddingTop;
+ }
+ expandedGroupListHeight = Math.min(expandedGroupListHeight, mVolumeGroupListMaxHeight);
+ int visibleGroupListHeight = mIsGroupExpanded ? expandedGroupListHeight : 0;
+
+ int desiredControlLayoutHeight =
+ Math.max(artViewHeight, visibleGroupListHeight) + mainControllerHeight;
+ Rect visibleRect = new Rect();
+ decorView.getWindowVisibleDisplayFrame(visibleRect);
+ // Height of non-control views in decor view.
+ // This includes title bar, button bar, and dialog's vertical padding which should be
+ // always shown.
+ int nonControlViewHeight = mDialogAreaLayout.getMeasuredHeight()
+ - mDefaultControlLayout.getMeasuredHeight();
+ // Maximum allowed height for controls to fit screen.
+ int maximumControlViewHeight = visibleRect.height() - nonControlViewHeight;
+
+ // Show artwork if it fits the screen.
+ if (mCustomControlView == null && artViewHeight > 0
+ && desiredControlLayoutHeight <= maximumControlViewHeight) {
+ mArtView.setVisibility(View.VISIBLE);
+ setLayoutHeight(mArtView, artViewHeight);
+ } else {
+ if (getLayoutHeight(mVolumeGroupList) + mMediaMainControlLayout.getMeasuredHeight()
+ >= mDefaultControlLayout.getMeasuredHeight()) {
+ mArtView.setVisibility(View.GONE);
+ }
+ artViewHeight = 0;
+ desiredControlLayoutHeight = visibleGroupListHeight + mainControllerHeight;
+ }
+ // Show the playback control if it fits the screen.
+ if (canShowPlaybackControlLayout()
+ && desiredControlLayoutHeight <= maximumControlViewHeight) {
+ mPlaybackControlLayout.setVisibility(View.VISIBLE);
+ } else {
+ mPlaybackControlLayout.setVisibility(View.GONE);
+ }
+ updateMediaControlVisibility(mPlaybackControlLayout.getVisibility() == View.VISIBLE);
+ mainControllerHeight = getMainControllerHeight(
+ mPlaybackControlLayout.getVisibility() == View.VISIBLE);
+ desiredControlLayoutHeight =
+ Math.max(artViewHeight, visibleGroupListHeight) + mainControllerHeight;
+
+ // Limit the volume group list height to fit the screen.
+ if (desiredControlLayoutHeight > maximumControlViewHeight) {
+ visibleGroupListHeight -= (desiredControlLayoutHeight - maximumControlViewHeight);
+ desiredControlLayoutHeight = maximumControlViewHeight;
+ }
+ // Update the layouts with the computed heights.
+ mMediaMainControlLayout.clearAnimation();
+ mVolumeGroupList.clearAnimation();
+ mDefaultControlLayout.clearAnimation();
+ if (animate) {
+ animateLayoutHeight(mMediaMainControlLayout, mainControllerHeight);
+ animateLayoutHeight(mVolumeGroupList, visibleGroupListHeight);
+ animateLayoutHeight(mDefaultControlLayout, desiredControlLayoutHeight);
+ } else {
+ setLayoutHeight(mMediaMainControlLayout, mainControllerHeight);
+ setLayoutHeight(mVolumeGroupList, visibleGroupListHeight);
+ setLayoutHeight(mDefaultControlLayout, desiredControlLayoutHeight);
+ }
+ // Maximize the window size with a transparent layout in advance for smooth animation.
+ setLayoutHeight(mExpandableAreaLayout, visibleRect.height());
+ rebuildVolumeGroupList(animate);
+ }
+
+ void updateVolumeGroupItemHeight(View item) {
+ LinearLayout container = (LinearLayout) item.findViewById(R.id.volume_item_container);
+ setLayoutHeight(container, mVolumeGroupListItemHeight);
+ View icon = item.findViewById(R.id.mr_volume_item_icon);
+ ViewGroup.LayoutParams lp = icon.getLayoutParams();
+ lp.width = mVolumeGroupListItemIconSize;
+ lp.height = mVolumeGroupListItemIconSize;
+ icon.setLayoutParams(lp);
+ }
+
+ private void animateLayoutHeight(final View view, int targetHeight) {
+ final int startValue = getLayoutHeight(view);
+ final int endValue = targetHeight;
+ Animation anim = new Animation() {
+ @Override
+ protected void applyTransformation(float interpolatedTime, Transformation t) {
+ int height = startValue - (int) ((startValue - endValue) * interpolatedTime);
+ setLayoutHeight(view, height);
+ }
+ };
+ anim.setDuration(mGroupListAnimationDurationMs);
+ if (android.os.Build.VERSION.SDK_INT >= 21) {
+ anim.setInterpolator(mInterpolator);
+ }
+ view.startAnimation(anim);
+ }
+
+ void loadInterpolator() {
+ if (android.os.Build.VERSION.SDK_INT >= 21) {
+ mInterpolator = mIsGroupExpanded ? mLinearOutSlowInInterpolator
+ : mFastOutSlowInInterpolator;
+ } else {
+ mInterpolator = mAccelerateDecelerateInterpolator;
+ }
+ }
+
+ private void updateVolumeControlLayout() {
+ if (isVolumeControlAvailable(mRoute)) {
+ if (mVolumeControlLayout.getVisibility() == View.GONE) {
+ mVolumeControlLayout.setVisibility(View.VISIBLE);
+ mVolumeSlider.setMax(mRoute.getVolumeMax());
+ mVolumeSlider.setProgress(mRoute.getVolume());
+ mGroupExpandCollapseButton.setVisibility(getGroup() == null ? View.GONE
+ : View.VISIBLE);
+ }
+ } else {
+ mVolumeControlLayout.setVisibility(View.GONE);
+ }
+ }
+
+ private void rebuildVolumeGroupList(boolean animate) {
+ List<MediaRouter.RouteInfo> routes = getGroup() == null ? null : getGroup().getRoutes();
+ if (routes == null) {
+ mGroupMemberRoutes.clear();
+ mVolumeGroupAdapter.notifyDataSetChanged();
+ } else if (MediaRouteDialogHelper.listUnorderedEquals(mGroupMemberRoutes, routes)) {
+ mVolumeGroupAdapter.notifyDataSetChanged();
+ } else {
+ HashMap<MediaRouter.RouteInfo, Rect> previousRouteBoundMap = animate
+ ? MediaRouteDialogHelper.getItemBoundMap(mVolumeGroupList, mVolumeGroupAdapter)
+ : null;
+ HashMap<MediaRouter.RouteInfo, BitmapDrawable> previousRouteBitmapMap = animate
+ ? MediaRouteDialogHelper.getItemBitmapMap(mContext, mVolumeGroupList,
+ mVolumeGroupAdapter) : null;
+ mGroupMemberRoutesAdded =
+ MediaRouteDialogHelper.getItemsAdded(mGroupMemberRoutes, routes);
+ mGroupMemberRoutesRemoved = MediaRouteDialogHelper.getItemsRemoved(mGroupMemberRoutes,
+ routes);
+ mGroupMemberRoutes.addAll(0, mGroupMemberRoutesAdded);
+ mGroupMemberRoutes.removeAll(mGroupMemberRoutesRemoved);
+ mVolumeGroupAdapter.notifyDataSetChanged();
+ if (animate && mIsGroupExpanded
+ && mGroupMemberRoutesAdded.size() + mGroupMemberRoutesRemoved.size() > 0) {
+ animateGroupListItems(previousRouteBoundMap, previousRouteBitmapMap);
+ } else {
+ mGroupMemberRoutesAdded = null;
+ mGroupMemberRoutesRemoved = null;
+ }
+ }
+ }
+
+ private void animateGroupListItems(final Map<MediaRouter.RouteInfo, Rect> previousRouteBoundMap,
+ final Map<MediaRouter.RouteInfo, BitmapDrawable> previousRouteBitmapMap) {
+ mVolumeGroupList.setEnabled(false);
+ mVolumeGroupList.requestLayout();
+ mIsGroupListAnimating = true;
+ ViewTreeObserver observer = mVolumeGroupList.getViewTreeObserver();
+ observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ mVolumeGroupList.getViewTreeObserver().removeGlobalOnLayoutListener(this);
+ animateGroupListItemsInternal(previousRouteBoundMap, previousRouteBitmapMap);
+ }
+ });
+ }
+
+ void animateGroupListItemsInternal(
+ Map<MediaRouter.RouteInfo, Rect> previousRouteBoundMap,
+ Map<MediaRouter.RouteInfo, BitmapDrawable> previousRouteBitmapMap) {
+ if (mGroupMemberRoutesAdded == null || mGroupMemberRoutesRemoved == null) {
+ return;
+ }
+ int groupSizeDelta = mGroupMemberRoutesAdded.size() - mGroupMemberRoutesRemoved.size();
+ boolean listenerRegistered = false;
+ Animation.AnimationListener listener = new Animation.AnimationListener() {
+ @Override
+ public void onAnimationStart(Animation animation) {
+ mVolumeGroupList.startAnimationAll();
+ mVolumeGroupList.postDelayed(mGroupListFadeInAnimation,
+ mGroupListAnimationDurationMs);
+ }
+
+ @Override
+ public void onAnimationEnd(Animation animation) { }
+
+ @Override
+ public void onAnimationRepeat(Animation animation) { }
+ };
+
+ // Animate visible items from previous positions to current positions except routes added
+ // just before. Added routes will remain hidden until translate animation finishes.
+ int first = mVolumeGroupList.getFirstVisiblePosition();
+ for (int i = 0; i < mVolumeGroupList.getChildCount(); ++i) {
+ View view = mVolumeGroupList.getChildAt(i);
+ int position = first + i;
+ MediaRouter.RouteInfo route = mVolumeGroupAdapter.getItem(position);
+ Rect previousBounds = previousRouteBoundMap.get(route);
+ int currentTop = view.getTop();
+ int previousTop = previousBounds != null ? previousBounds.top
+ : (currentTop + mVolumeGroupListItemHeight * groupSizeDelta);
+ AnimationSet animSet = new AnimationSet(true);
+ if (mGroupMemberRoutesAdded != null && mGroupMemberRoutesAdded.contains(route)) {
+ previousTop = currentTop;
+ Animation alphaAnim = new AlphaAnimation(0.0f, 0.0f);
+ alphaAnim.setDuration(mGroupListFadeInDurationMs);
+ animSet.addAnimation(alphaAnim);
+ }
+ Animation translationAnim = new TranslateAnimation(0, 0, previousTop - currentTop, 0);
+ translationAnim.setDuration(mGroupListAnimationDurationMs);
+ animSet.addAnimation(translationAnim);
+ animSet.setFillAfter(true);
+ animSet.setFillEnabled(true);
+ animSet.setInterpolator(mInterpolator);
+ if (!listenerRegistered) {
+ listenerRegistered = true;
+ animSet.setAnimationListener(listener);
+ }
+ view.clearAnimation();
+ view.startAnimation(animSet);
+ previousRouteBoundMap.remove(route);
+ previousRouteBitmapMap.remove(route);
+ }
+
+ // If a member route doesn't exist any longer, it can be either removed or moved out of the
+ // ListView layout boundary. In this case, use the previously captured bitmaps for
+ // animation.
+ for (Map.Entry<MediaRouter.RouteInfo, BitmapDrawable> item
+ : previousRouteBitmapMap.entrySet()) {
+ final MediaRouter.RouteInfo route = item.getKey();
+ final BitmapDrawable bitmap = item.getValue();
+ final Rect bounds = previousRouteBoundMap.get(route);
+ OverlayObject object = null;
+ if (mGroupMemberRoutesRemoved.contains(route)) {
+ object = new OverlayObject(bitmap, bounds).setAlphaAnimation(1.0f, 0.0f)
+ .setDuration(mGroupListFadeOutDurationMs)
+ .setInterpolator(mInterpolator);
+ } else {
+ int deltaY = groupSizeDelta * mVolumeGroupListItemHeight;
+ object = new OverlayObject(bitmap, bounds).setTranslateYAnimation(deltaY)
+ .setDuration(mGroupListAnimationDurationMs)
+ .setInterpolator(mInterpolator)
+ .setAnimationEndListener(new OverlayObject.OnAnimationEndListener() {
+ @Override
+ public void onAnimationEnd() {
+ mGroupMemberRoutesAnimatingWithBitmap.remove(route);
+ mVolumeGroupAdapter.notifyDataSetChanged();
+ }
+ });
+ mGroupMemberRoutesAnimatingWithBitmap.add(route);
+ }
+ mVolumeGroupList.addOverlayObject(object);
+ }
+ }
+
+ void startGroupListFadeInAnimation() {
+ clearGroupListAnimation(true);
+ mVolumeGroupList.requestLayout();
+ ViewTreeObserver observer = mVolumeGroupList.getViewTreeObserver();
+ observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ mVolumeGroupList.getViewTreeObserver().removeGlobalOnLayoutListener(this);
+ startGroupListFadeInAnimationInternal();
+ }
+ });
+ }
+
+ void startGroupListFadeInAnimationInternal() {
+ if (mGroupMemberRoutesAdded != null && mGroupMemberRoutesAdded.size() != 0) {
+ fadeInAddedRoutes();
+ } else {
+ finishAnimation(true);
+ }
+ }
+
+ void finishAnimation(boolean animate) {
+ mGroupMemberRoutesAdded = null;
+ mGroupMemberRoutesRemoved = null;
+ mIsGroupListAnimating = false;
+ if (mIsGroupListAnimationPending) {
+ mIsGroupListAnimationPending = false;
+ updateLayoutHeight(animate);
+ }
+ mVolumeGroupList.setEnabled(true);
+ }
+
+ private void fadeInAddedRoutes() {
+ Animation.AnimationListener listener = new Animation.AnimationListener() {
+ @Override
+ public void onAnimationStart(Animation animation) { }
+
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ finishAnimation(true);
+ }
+
+ @Override
+ public void onAnimationRepeat(Animation animation) { }
+ };
+ boolean listenerRegistered = false;
+ int first = mVolumeGroupList.getFirstVisiblePosition();
+ for (int i = 0; i < mVolumeGroupList.getChildCount(); ++i) {
+ View view = mVolumeGroupList.getChildAt(i);
+ int position = first + i;
+ MediaRouter.RouteInfo route = mVolumeGroupAdapter.getItem(position);
+ if (mGroupMemberRoutesAdded.contains(route)) {
+ Animation alphaAnim = new AlphaAnimation(0.0f, 1.0f);
+ alphaAnim.setDuration(mGroupListFadeInDurationMs);
+ alphaAnim.setFillEnabled(true);
+ alphaAnim.setFillAfter(true);
+ if (!listenerRegistered) {
+ listenerRegistered = true;
+ alphaAnim.setAnimationListener(listener);
+ }
+ view.clearAnimation();
+ view.startAnimation(alphaAnim);
+ }
+ }
+ }
+
+ void clearGroupListAnimation(boolean exceptAddedRoutes) {
+ int first = mVolumeGroupList.getFirstVisiblePosition();
+ for (int i = 0; i < mVolumeGroupList.getChildCount(); ++i) {
+ View view = mVolumeGroupList.getChildAt(i);
+ int position = first + i;
+ MediaRouter.RouteInfo route = mVolumeGroupAdapter.getItem(position);
+ if (exceptAddedRoutes && mGroupMemberRoutesAdded != null
+ && mGroupMemberRoutesAdded.contains(route)) {
+ continue;
+ }
+ LinearLayout container = (LinearLayout) view.findViewById(R.id.volume_item_container);
+ container.setVisibility(View.VISIBLE);
+ AnimationSet animSet = new AnimationSet(true);
+ Animation alphaAnim = new AlphaAnimation(1.0f, 1.0f);
+ alphaAnim.setDuration(0);
+ animSet.addAnimation(alphaAnim);
+ Animation translationAnim = new TranslateAnimation(0, 0, 0, 0);
+ translationAnim.setDuration(0);
+ animSet.setFillAfter(true);
+ animSet.setFillEnabled(true);
+ view.clearAnimation();
+ view.startAnimation(animSet);
+ }
+ mVolumeGroupList.stopAnimationAll();
+ if (!exceptAddedRoutes) {
+ finishAnimation(false);
+ }
+ }
+
+ private void updatePlaybackControlLayout() {
+ if (canShowPlaybackControlLayout()) {
+ CharSequence title = mDescription == null ? null : mDescription.getTitle();
+ boolean hasTitle = !TextUtils.isEmpty(title);
+
+ CharSequence subtitle = mDescription == null ? null : mDescription.getSubtitle();
+ boolean hasSubtitle = !TextUtils.isEmpty(subtitle);
+
+ boolean showTitle = false;
+ boolean showSubtitle = false;
+ if (mRoute.getPresentationDisplayId()
+ != MediaRouter.RouteInfo.PRESENTATION_DISPLAY_ID_NONE) {
+ // The user is currently casting screen.
+ mTitleView.setText(R.string.mr_controller_casting_screen);
+ showTitle = true;
+ } else if (mState == null || mState.getState() == PlaybackStateCompat.STATE_NONE) {
+ // Show "No media selected" as we don't yet know the playback state.
+ mTitleView.setText(R.string.mr_controller_no_media_selected);
+ showTitle = true;
+ } else if (!hasTitle && !hasSubtitle) {
+ mTitleView.setText(R.string.mr_controller_no_info_available);
+ showTitle = true;
+ } else {
+ if (hasTitle) {
+ mTitleView.setText(title);
+ showTitle = true;
+ }
+ if (hasSubtitle) {
+ mSubtitleView.setText(subtitle);
+ showSubtitle = true;
+ }
+ }
+ mTitleView.setVisibility(showTitle ? View.VISIBLE : View.GONE);
+ mSubtitleView.setVisibility(showSubtitle ? View.VISIBLE : View.GONE);
+
+ if (mState != null) {
+ boolean isPlaying = mState.getState() == PlaybackStateCompat.STATE_BUFFERING
+ || mState.getState() == PlaybackStateCompat.STATE_PLAYING;
+ Context playbackControlButtonContext = mPlaybackControlButton.getContext();
+ boolean visible = true;
+ int iconDrawableAttr = 0;
+ int iconDescResId = 0;
+ if (isPlaying && isPauseActionSupported()) {
+ iconDrawableAttr = R.attr.mediaRoutePauseDrawable;
+ iconDescResId = R.string.mr_controller_pause;
+ } else if (isPlaying && isStopActionSupported()) {
+ iconDrawableAttr = R.attr.mediaRouteStopDrawable;
+ iconDescResId = R.string.mr_controller_stop;
+ } else if (!isPlaying && isPlayActionSupported()) {
+ iconDrawableAttr = R.attr.mediaRoutePlayDrawable;
+ iconDescResId = R.string.mr_controller_play;
+ } else {
+ visible = false;
+ }
+ mPlaybackControlButton.setVisibility(visible ? View.VISIBLE : View.GONE);
+ if (visible) {
+ mPlaybackControlButton.setImageResource(
+ MediaRouterThemeHelper.getThemeResource(
+ playbackControlButtonContext, iconDrawableAttr));
+ mPlaybackControlButton.setContentDescription(
+ playbackControlButtonContext.getResources()
+ .getText(iconDescResId));
+ }
+ }
+ }
+ }
+
+ private boolean isPlayActionSupported() {
+ return (mState.getActions() & (ACTION_PLAY | ACTION_PLAY_PAUSE)) != 0;
+ }
+
+ private boolean isPauseActionSupported() {
+ return (mState.getActions() & (ACTION_PAUSE | ACTION_PLAY_PAUSE)) != 0;
+ }
+
+ private boolean isStopActionSupported() {
+ return (mState.getActions() & ACTION_STOP) != 0;
+ }
+
+ boolean isVolumeControlAvailable(MediaRouter.RouteInfo route) {
+ return mVolumeControlEnabled && route.getVolumeHandling()
+ == MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE;
+ }
+
+ private static int getLayoutHeight(View view) {
+ return view.getLayoutParams().height;
+ }
+
+ static void setLayoutHeight(View view, int height) {
+ ViewGroup.LayoutParams lp = view.getLayoutParams();
+ lp.height = height;
+ view.setLayoutParams(lp);
+ }
+
+ private static boolean uriEquals(Uri uri1, Uri uri2) {
+ if (uri1 != null && uri1.equals(uri2)) {
+ return true;
+ } else if (uri1 == null && uri2 == null) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns desired art height to fit into controller dialog.
+ */
+ int getDesiredArtHeight(int originalWidth, int originalHeight) {
+ if (originalWidth >= originalHeight) {
+ // For landscape art, fit width to dialog width.
+ return (int) ((float) mDialogContentWidth * originalHeight / originalWidth + 0.5f);
+ }
+ // For portrait art, fit height to 16:9 ratio case's height.
+ return (int) ((float) mDialogContentWidth * 9 / 16 + 0.5f);
+ }
+
+ void updateArtIconIfNeeded() {
+ if (mCustomControlView != null || !isIconChanged()) {
+ return;
+ }
+ if (mFetchArtTask != null) {
+ mFetchArtTask.cancel(true);
+ }
+ mFetchArtTask = new FetchArtTask();
+ mFetchArtTask.execute();
+ }
+
+ /**
+ * Clear the bitmap loaded by FetchArtTask. Will be called after the loaded bitmaps are applied
+ * to artwork, or no longer valid.
+ */
+ void clearLoadedBitmap() {
+ mArtIconIsLoaded = false;
+ mArtIconLoadedBitmap = null;
+ mArtIconBackgroundColor = 0;
+ }
+
+ /**
+ * Returns whether a new art image is different from an original art image. Compares
+ * Bitmap objects first, and then compares URIs only if bitmap is unchanged with
+ * a null value.
+ */
+ private boolean isIconChanged() {
+ Bitmap newBitmap = mDescription == null ? null : mDescription.getIconBitmap();
+ Uri newUri = mDescription == null ? null : mDescription.getIconUri();
+ Bitmap oldBitmap = mFetchArtTask == null ? mArtIconBitmap : mFetchArtTask.getIconBitmap();
+ Uri oldUri = mFetchArtTask == null ? mArtIconUri : mFetchArtTask.getIconUri();
+ if (oldBitmap != newBitmap) {
+ return true;
+ } else if (oldBitmap == null && !uriEquals(oldUri, newUri)) {
+ return true;
+ }
+ return false;
+ }
+
+ private final class MediaRouterCallback extends MediaRouter.Callback {
+ MediaRouterCallback() {
+ }
+
+ @Override
+ public void onRouteUnselected(MediaRouter router, MediaRouter.RouteInfo route) {
+ update(false);
+ }
+
+ @Override
+ public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo route) {
+ update(true);
+ }
+
+ @Override
+ public void onRouteVolumeChanged(MediaRouter router, MediaRouter.RouteInfo route) {
+ SeekBar volumeSlider = mVolumeSliderMap.get(route);
+ int volume = route.getVolume();
+ if (DEBUG) {
+ Log.d(TAG, "onRouteVolumeChanged(), route.getVolume:" + volume);
+ }
+ if (volumeSlider != null && mRouteInVolumeSliderTouched != route) {
+ volumeSlider.setProgress(volume);
+ }
+ }
+ }
+
+ private final class MediaControllerCallback extends MediaControllerCompat.Callback {
+ MediaControllerCallback() {
+ }
+
+ @Override
+ public void onSessionDestroyed() {
+ if (mMediaController != null) {
+ mMediaController.unregisterCallback(mControllerCallback);
+ mMediaController = null;
+ }
+ }
+
+ @Override
+ public void onPlaybackStateChanged(PlaybackStateCompat state) {
+ mState = state;
+ update(false);
+ }
+
+ @Override
+ public void onMetadataChanged(MediaMetadataCompat metadata) {
+ mDescription = metadata == null ? null : metadata.getDescription();
+ updateArtIconIfNeeded();
+ update(false);
+ }
+ }
+
+ private final class ClickListener implements View.OnClickListener {
+ ClickListener() {
+ }
+
+ @Override
+ public void onClick(View v) {
+ int id = v.getId();
+ if (id == BUTTON_STOP_RES_ID || id == BUTTON_DISCONNECT_RES_ID) {
+ if (mRoute.isSelected()) {
+ mRouter.unselect(id == BUTTON_STOP_RES_ID ?
+ MediaRouter.UNSELECT_REASON_STOPPED :
+ MediaRouter.UNSELECT_REASON_DISCONNECTED);
+ }
+ dismiss();
+ } else if (id == R.id.mr_control_playback_ctrl) {
+ if (mMediaController != null && mState != null) {
+ boolean isPlaying = mState.getState() == PlaybackStateCompat.STATE_PLAYING;
+ int actionDescResId = 0;
+ if (isPlaying && isPauseActionSupported()) {
+ mMediaController.getTransportControls().pause();
+ actionDescResId = R.string.mr_controller_pause;
+ } else if (isPlaying && isStopActionSupported()) {
+ mMediaController.getTransportControls().stop();
+ actionDescResId = R.string.mr_controller_stop;
+ } else if (!isPlaying && isPlayActionSupported()){
+ mMediaController.getTransportControls().play();
+ actionDescResId = R.string.mr_controller_play;
+ }
+ // Announce the action for accessibility.
+ if (mAccessibilityManager != null && mAccessibilityManager.isEnabled()
+ && actionDescResId != 0) {
+ AccessibilityEvent event = AccessibilityEvent.obtain(
+ AccessibilityEventCompat.TYPE_ANNOUNCEMENT);
+ event.setPackageName(mContext.getPackageName());
+ event.setClassName(getClass().getName());
+ event.getText().add(mContext.getString(actionDescResId));
+ mAccessibilityManager.sendAccessibilityEvent(event);
+ }
+ }
+ } else if (id == R.id.mr_close) {
+ dismiss();
+ }
+ }
+ }
+
+ private class VolumeChangeListener implements SeekBar.OnSeekBarChangeListener {
+ private final Runnable mStopTrackingTouch = new Runnable() {
+ @Override
+ public void run() {
+ if (mRouteInVolumeSliderTouched != null) {
+ mRouteInVolumeSliderTouched = null;
+ if (mHasPendingUpdate) {
+ update(mPendingUpdateAnimationNeeded);
+ }
+ }
+ }
+ };
+
+ VolumeChangeListener() {
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ if (mRouteInVolumeSliderTouched != null) {
+ mVolumeSlider.removeCallbacks(mStopTrackingTouch);
+ }
+ mRouteInVolumeSliderTouched = (MediaRouter.RouteInfo) seekBar.getTag();
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ // Defer resetting mVolumeSliderTouched to allow the media route provider
+ // a little time to settle into its new state and publish the final
+ // volume update.
+ mVolumeSlider.postDelayed(mStopTrackingTouch, VOLUME_UPDATE_DELAY_MILLIS);
+ }
+
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ if (fromUser) {
+ MediaRouter.RouteInfo route = (MediaRouter.RouteInfo) seekBar.getTag();
+ if (DEBUG) {
+ Log.d(TAG, "onProgressChanged(): calling "
+ + "MediaRouter.RouteInfo.requestSetVolume(" + progress + ")");
+ }
+ route.requestSetVolume(progress);
+ }
+ }
+ }
+
+ private class VolumeGroupAdapter extends ArrayAdapter<MediaRouter.RouteInfo> {
+ final float mDisabledAlpha;
+
+ public VolumeGroupAdapter(Context context, List<MediaRouter.RouteInfo> objects) {
+ super(context, 0, objects);
+ mDisabledAlpha = MediaRouterThemeHelper.getDisabledAlpha(context);
+ }
+
+ @Override
+ public boolean isEnabled(int position) {
+ return false;
+ }
+
+ @Override
+ public View getView(final int position, View convertView, ViewGroup parent) {
+ View v = convertView;
+ if (v == null) {
+ v = LayoutInflater.from(parent.getContext()).inflate(
+ R.layout.mr_controller_volume_item, parent, false);
+ } else {
+ updateVolumeGroupItemHeight(v);
+ }
+
+ MediaRouter.RouteInfo route = getItem(position);
+ if (route != null) {
+ boolean isEnabled = route.isEnabled();
+
+ TextView routeName = (TextView) v.findViewById(R.id.mr_name);
+ routeName.setEnabled(isEnabled);
+ routeName.setText(route.getName());
+
+ MediaRouteVolumeSlider volumeSlider =
+ (MediaRouteVolumeSlider) v.findViewById(R.id.mr_volume_slider);
+ MediaRouterThemeHelper.setVolumeSliderColor(
+ parent.getContext(), volumeSlider, mVolumeGroupList);
+ volumeSlider.setTag(route);
+ mVolumeSliderMap.put(route, volumeSlider);
+ volumeSlider.setHideThumb(!isEnabled);
+ volumeSlider.setEnabled(isEnabled);
+ if (isEnabled) {
+ if (isVolumeControlAvailable(route)) {
+ volumeSlider.setMax(route.getVolumeMax());
+ volumeSlider.setProgress(route.getVolume());
+ volumeSlider.setOnSeekBarChangeListener(mVolumeChangeListener);
+ } else {
+ volumeSlider.setMax(100);
+ volumeSlider.setProgress(100);
+ volumeSlider.setEnabled(false);
+ }
+ }
+
+ ImageView volumeItemIcon =
+ (ImageView) v.findViewById(R.id.mr_volume_item_icon);
+ volumeItemIcon.setAlpha(isEnabled ? 0xFF : (int) (0xFF * mDisabledAlpha));
+
+ // If overlay bitmap exists, real view should remain hidden until
+ // the animation ends.
+ LinearLayout container = (LinearLayout) v.findViewById(R.id.volume_item_container);
+ container.setVisibility(mGroupMemberRoutesAnimatingWithBitmap.contains(route)
+ ? View.INVISIBLE : View.VISIBLE);
+
+ // Routes which are being added will be invisible until animation ends.
+ if (mGroupMemberRoutesAdded != null && mGroupMemberRoutesAdded.contains(route)) {
+ Animation alphaAnim = new AlphaAnimation(0.0f, 0.0f);
+ alphaAnim.setDuration(0);
+ alphaAnim.setFillEnabled(true);
+ alphaAnim.setFillAfter(true);
+ v.clearAnimation();
+ v.startAnimation(alphaAnim);
+ }
+ }
+ return v;
+ }
+ }
+
+ private class FetchArtTask extends AsyncTask<Void, Void, Bitmap> {
+ // Show animation only when fetching takes a long time.
+ private static final long SHOW_ANIM_TIME_THRESHOLD_MILLIS = 120L;
+
+ private final Bitmap mIconBitmap;
+ private final Uri mIconUri;
+ private int mBackgroundColor;
+ private long mStartTimeMillis;
+
+ FetchArtTask() {
+ Bitmap bitmap = mDescription == null ? null : mDescription.getIconBitmap();
+ if (isBitmapRecycled(bitmap)) {
+ Log.w(TAG, "Can't fetch the given art bitmap because it's already recycled.");
+ bitmap = null;
+ }
+ mIconBitmap = bitmap;
+ mIconUri = mDescription == null ? null : mDescription.getIconUri();
+ }
+
+ public Bitmap getIconBitmap() {
+ return mIconBitmap;
+ }
+
+ public Uri getIconUri() {
+ return mIconUri;
+ }
+
+ @Override
+ protected void onPreExecute() {
+ mStartTimeMillis = SystemClock.uptimeMillis();
+ clearLoadedBitmap();
+ }
+
+ @Override
+ protected Bitmap doInBackground(Void... arg) {
+ Bitmap art = null;
+ if (mIconBitmap != null) {
+ art = mIconBitmap;
+ } else if (mIconUri != null) {
+ InputStream stream = null;
+ try {
+ if ((stream = openInputStreamByScheme(mIconUri)) == null) {
+ Log.w(TAG, "Unable to open: " + mIconUri);
+ return null;
+ }
+ // Query art size.
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeStream(stream, null, options);
+ if (options.outWidth == 0 || options.outHeight == 0) {
+ return null;
+ }
+ // Rewind the stream in order to restart art decoding.
+ try {
+ stream.reset();
+ } catch (IOException e) {
+ // Failed to rewind the stream, try to reopen it.
+ stream.close();
+ if ((stream = openInputStreamByScheme(mIconUri)) == null) {
+ Log.w(TAG, "Unable to open: " + mIconUri);
+ return null;
+ }
+ }
+ // Calculate required size to decode the art and possibly resize it.
+ options.inJustDecodeBounds = false;
+ int reqHeight = getDesiredArtHeight(options.outWidth, options.outHeight);
+ int ratio = options.outHeight / reqHeight;
+ options.inSampleSize = Math.max(1, Integer.highestOneBit(ratio));
+ if (isCancelled()) {
+ return null;
+ }
+ art = BitmapFactory.decodeStream(stream, null, options);
+ } catch (IOException e){
+ Log.w(TAG, "Unable to open: " + mIconUri, e);
+ } finally {
+ if (stream != null) {
+ try {
+ stream.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+ if (isBitmapRecycled(art)) {
+ Log.w(TAG, "Can't use recycled bitmap: " + art);
+ return null;
+ }
+ if (art != null && art.getWidth() < art.getHeight()) {
+ // Portrait art requires dominant color as background color.
+ Palette palette = new Palette.Builder(art).maximumColorCount(1).generate();
+ mBackgroundColor = palette.getSwatches().isEmpty()
+ ? 0 : palette.getSwatches().get(0).getRgb();
+ }
+ return art;
+ }
+
+ @Override
+ protected void onPostExecute(Bitmap art) {
+ mFetchArtTask = null;
+ if (!ObjectsCompat.equals(mArtIconBitmap, mIconBitmap)
+ || !ObjectsCompat.equals(mArtIconUri, mIconUri)) {
+ mArtIconBitmap = mIconBitmap;
+ mArtIconLoadedBitmap = art;
+ mArtIconUri = mIconUri;
+ mArtIconBackgroundColor = mBackgroundColor;
+ mArtIconIsLoaded = true;
+ long elapsedTimeMillis = SystemClock.uptimeMillis() - mStartTimeMillis;
+ // Loaded bitmap will be applied on the next update
+ update(elapsedTimeMillis > SHOW_ANIM_TIME_THRESHOLD_MILLIS);
+ }
+ }
+
+ private InputStream openInputStreamByScheme(Uri uri) throws IOException {
+ String scheme = uri.getScheme().toLowerCase();
+ InputStream stream = null;
+ if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)
+ || ContentResolver.SCHEME_CONTENT.equals(scheme)
+ || ContentResolver.SCHEME_FILE.equals(scheme)) {
+ stream = mContext.getContentResolver().openInputStream(uri);
+ } else {
+ URL url = new URL(uri.toString());
+ URLConnection conn = url.openConnection();
+ conn.setConnectTimeout(CONNECTION_TIMEOUT_MILLIS);
+ conn.setReadTimeout(CONNECTION_TIMEOUT_MILLIS);
+ stream = conn.getInputStream();
+ }
+ return (stream == null) ? null : new BufferedInputStream(stream);
+ }
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteControllerDialogFragment.java b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteControllerDialogFragment.java
new file mode 100644
index 0000000..9442df7
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteControllerDialogFragment.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.support.mediarouter.app;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.support.v4.app.DialogFragment;
+
+/**
+ * Media route controller dialog fragment.
+ * <p>
+ * Creates a {@link MediaRouteControllerDialog}. The application may subclass
+ * this dialog fragment to customize the media route controller dialog.
+ * </p>
+ */
+public class MediaRouteControllerDialogFragment extends DialogFragment {
+ private MediaRouteControllerDialog mDialog;
+ /**
+ * Creates a media route controller dialog fragment.
+ * <p>
+ * All subclasses of this class must also possess a default constructor.
+ * </p>
+ */
+ public MediaRouteControllerDialogFragment() {
+ setCancelable(true);
+ }
+
+ /**
+ * Called when the controller dialog is being created.
+ * <p>
+ * Subclasses may override this method to customize the dialog.
+ * </p>
+ */
+ public MediaRouteControllerDialog onCreateControllerDialog(
+ Context context, Bundle savedInstanceState) {
+ return new MediaRouteControllerDialog(context);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ mDialog = onCreateControllerDialog(getContext(), savedInstanceState);
+ return mDialog;
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ if (mDialog != null) {
+ mDialog.clearGroupListAnimation(false);
+ }
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ if (mDialog != null) {
+ mDialog.updateLayout();
+ }
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteDialogFactory.java b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteDialogFactory.java
new file mode 100644
index 0000000..a9eaf39
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteDialogFactory.java
@@ -0,0 +1,74 @@
+/*
+ * 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.support.mediarouter.app;
+
+import android.support.annotation.NonNull;
+
+/**
+ * The media route dialog factory is responsible for creating the media route
+ * chooser and controller dialogs as needed.
+ * <p>
+ * The application can customize the dialogs by providing a subclass of the
+ * dialog factory to the {@link MediaRouteButton} using the
+ * {@link MediaRouteButton#setDialogFactory setDialogFactory} method.
+ * </p>
+ */
+public class MediaRouteDialogFactory {
+ private static final MediaRouteDialogFactory sDefault = new MediaRouteDialogFactory();
+
+ /**
+ * Creates a default media route dialog factory.
+ */
+ public MediaRouteDialogFactory() {
+ }
+
+ /**
+ * Gets the default factory instance.
+ *
+ * @return The default media route dialog factory, never null.
+ */
+ @NonNull
+ public static MediaRouteDialogFactory getDefault() {
+ return sDefault;
+ }
+
+ /**
+ * Called when the chooser dialog is being opened and it is time to create the fragment.
+ * <p>
+ * Subclasses may override this method to create a customized fragment.
+ * </p>
+ *
+ * @return The media route chooser dialog fragment, must not be null.
+ */
+ @NonNull
+ public MediaRouteChooserDialogFragment onCreateChooserDialogFragment() {
+ return new MediaRouteChooserDialogFragment();
+ }
+
+ /**
+ * Called when the controller dialog is being opened and it is time to create the fragment.
+ * <p>
+ * Subclasses may override this method to create a customized fragment.
+ * </p>
+ *
+ * @return The media route controller dialog fragment, must not be null.
+ */
+ @NonNull
+ public MediaRouteControllerDialogFragment onCreateControllerDialogFragment() {
+ return new MediaRouteControllerDialogFragment();
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteDialogHelper.java b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteDialogHelper.java
new file mode 100644
index 0000000..6f75b46
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteDialogHelper.java
@@ -0,0 +1,152 @@
+/*
+ * 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.support.mediarouter.app;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+
+import com.android.media.update.R;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+final class MediaRouteDialogHelper {
+ /**
+ * The framework should set the dialog width properly, but somehow it doesn't work, hence
+ * duplicating a similar logic here to determine the appropriate dialog width.
+ */
+ public static int getDialogWidth(Context context) {
+ DisplayMetrics metrics = context.getResources().getDisplayMetrics();
+ boolean isPortrait = metrics.widthPixels < metrics.heightPixels;
+
+ TypedValue value = new TypedValue();
+ context.getResources().getValue(isPortrait ? R.dimen.mr_dialog_fixed_width_minor
+ : R.dimen.mr_dialog_fixed_width_major, value, true);
+ if (value.type == TypedValue.TYPE_DIMENSION) {
+ return (int) value.getDimension(metrics);
+ } else if (value.type == TypedValue.TYPE_FRACTION) {
+ return (int) value.getFraction(metrics.widthPixels, metrics.widthPixels);
+ }
+ return ViewGroup.LayoutParams.WRAP_CONTENT;
+ }
+
+ /**
+ * Compares two lists regardless of order.
+ *
+ * @param list1 A list
+ * @param list2 A list to be compared with {@code list1}
+ * @return True if two lists have exactly same items regardless of order, false otherwise.
+ */
+ public static <E> boolean listUnorderedEquals(List<E> list1, List<E> list2) {
+ HashSet<E> set1 = new HashSet<>(list1);
+ HashSet<E> set2 = new HashSet<>(list2);
+ return set1.equals(set2);
+ }
+
+ /**
+ * Compares two lists and returns a set of items which exist
+ * after-list but before-list, which means newly added items.
+ *
+ * @param before A list
+ * @param after A list to be compared with {@code before}
+ * @return A set of items which contains newly added items while
+ * comparing {@code after} to {@code before}.
+ */
+ public static <E> Set<E> getItemsAdded(List<E> before, List<E> after) {
+ HashSet<E> set = new HashSet<>(after);
+ set.removeAll(before);
+ return set;
+ }
+
+ /**
+ * Compares two lists and returns a set of items which exist
+ * before-list but after-list, which means removed items.
+ *
+ * @param before A list
+ * @param after A list to be compared with {@code before}
+ * @return A set of items which contains removed items while
+ * comparing {@code after} to {@code before}.
+ */
+ public static <E> Set<E> getItemsRemoved(List<E> before, List<E> after) {
+ HashSet<E> set = new HashSet<>(before);
+ set.removeAll(after);
+ return set;
+ }
+
+ /**
+ * Generates an item-Rect map which indicates where member
+ * items are located in the given ListView.
+ *
+ * @param listView A list view
+ * @param adapter An array adapter which contains an array of items.
+ * @return A map of items and bounds of their views located in the given list view.
+ */
+ public static <E> HashMap<E, Rect> getItemBoundMap(ListView listView,
+ ArrayAdapter<E> adapter) {
+ HashMap<E, Rect> itemBoundMap = new HashMap<>();
+ int firstVisiblePosition = listView.getFirstVisiblePosition();
+ for (int i = 0; i < listView.getChildCount(); ++i) {
+ int position = firstVisiblePosition + i;
+ E item = adapter.getItem(position);
+ View view = listView.getChildAt(i);
+ itemBoundMap.put(item,
+ new Rect(view.getLeft(), view.getTop(), view.getRight(), view.getBottom()));
+ }
+ return itemBoundMap;
+ }
+
+ /**
+ * Generates an item-BitmapDrawable map which stores snapshots
+ * of member items in the given ListView.
+ *
+ * @param context A context
+ * @param listView A list view
+ * @param adapter An array adapter which contains an array of items.
+ * @return A map of items and snapshots of their views in the given list view.
+ */
+ public static <E> HashMap<E, BitmapDrawable> getItemBitmapMap(Context context,
+ ListView listView, ArrayAdapter<E> adapter) {
+ HashMap<E, BitmapDrawable> itemBitmapMap = new HashMap<>();
+ int firstVisiblePosition = listView.getFirstVisiblePosition();
+ for (int i = 0; i < listView.getChildCount(); ++i) {
+ int position = firstVisiblePosition + i;
+ E item = adapter.getItem(position);
+ View view = listView.getChildAt(i);
+ itemBitmapMap.put(item, getViewBitmap(context, view));
+ }
+ return itemBitmapMap;
+ }
+
+ private static BitmapDrawable getViewBitmap(Context context, View view) {
+ Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(),
+ Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+ view.draw(canvas);
+ return new BitmapDrawable(context.getResources(), bitmap);
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteDiscoveryFragment.java b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteDiscoveryFragment.java
new file mode 100644
index 0000000..02ee118
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteDiscoveryFragment.java
@@ -0,0 +1,164 @@
+/*
+ * 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.support.mediarouter.app;
+
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+
+import com.android.support.mediarouter.media.MediaRouter;
+import com.android.support.mediarouter.media.MediaRouteSelector;
+
+/**
+ * Media route discovery fragment.
+ * <p>
+ * This fragment takes care of registering a callback for media route discovery
+ * during the {@link Fragment#onStart onStart()} phase
+ * and removing it during the {@link Fragment#onStop onStop()} phase.
+ * </p><p>
+ * The application must supply a route selector to specify the kinds of routes
+ * to discover. The application may also override {@link #onCreateCallback} to
+ * provide the {@link MediaRouter} callback to register.
+ * </p><p>
+ * Note that the discovery callback makes the application be connected with all the
+ * {@link android.support.v7.media.MediaRouteProviderService media route provider services}
+ * while it is registered.
+ * </p>
+ */
+public class MediaRouteDiscoveryFragment extends Fragment {
+ private final String ARGUMENT_SELECTOR = "selector";
+
+ private MediaRouter mRouter;
+ private MediaRouteSelector mSelector;
+ private MediaRouter.Callback mCallback;
+
+ public MediaRouteDiscoveryFragment() {
+ }
+
+ /**
+ * Gets the media router instance.
+ */
+ public MediaRouter getMediaRouter() {
+ ensureRouter();
+ return mRouter;
+ }
+
+ private void ensureRouter() {
+ if (mRouter == null) {
+ mRouter = MediaRouter.getInstance(getContext());
+ }
+ }
+
+ /**
+ * Gets the media route selector for filtering the routes to be discovered.
+ *
+ * @return The selector, never null.
+ */
+ public MediaRouteSelector getRouteSelector() {
+ ensureRouteSelector();
+ return mSelector;
+ }
+
+ /**
+ * Sets the media route selector for filtering the routes to be discovered.
+ * This method must be called before the fragment is added.
+ *
+ * @param selector The selector to set.
+ */
+ public void setRouteSelector(MediaRouteSelector selector) {
+ if (selector == null) {
+ throw new IllegalArgumentException("selector must not be null");
+ }
+
+ ensureRouteSelector();
+ if (!mSelector.equals(selector)) {
+ mSelector = selector;
+
+ Bundle args = getArguments();
+ if (args == null) {
+ args = new Bundle();
+ }
+ args.putBundle(ARGUMENT_SELECTOR, selector.asBundle());
+ setArguments(args);
+
+ if (mCallback != null) {
+ mRouter.removeCallback(mCallback);
+ mRouter.addCallback(mSelector, mCallback, onPrepareCallbackFlags());
+ }
+ }
+ }
+
+ private void ensureRouteSelector() {
+ if (mSelector == null) {
+ Bundle args = getArguments();
+ if (args != null) {
+ mSelector = MediaRouteSelector.fromBundle(args.getBundle(ARGUMENT_SELECTOR));
+ }
+ if (mSelector == null) {
+ mSelector = MediaRouteSelector.EMPTY;
+ }
+ }
+ }
+
+ /**
+ * Called to create the {@link android.support.v7.media.MediaRouter.Callback callback}
+ * that will be registered.
+ * <p>
+ * The default callback does nothing. The application may override this method to
+ * supply its own callback.
+ * </p>
+ *
+ * @return The new callback, or null if no callback should be registered.
+ */
+ public MediaRouter.Callback onCreateCallback() {
+ return new MediaRouter.Callback() { };
+ }
+
+ /**
+ * Called to prepare the callback flags that will be used when the
+ * {@link android.support.v7.media.MediaRouter.Callback callback} is registered.
+ * <p>
+ * The default implementation returns {@link MediaRouter#CALLBACK_FLAG_REQUEST_DISCOVERY}.
+ * </p>
+ *
+ * @return The desired callback flags.
+ */
+ public int onPrepareCallbackFlags() {
+ return MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY;
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+
+ ensureRouteSelector();
+ ensureRouter();
+ mCallback = onCreateCallback();
+ if (mCallback != null) {
+ mRouter.addCallback(mSelector, mCallback, onPrepareCallbackFlags());
+ }
+ }
+
+ @Override
+ public void onStop() {
+ if (mCallback != null) {
+ mRouter.removeCallback(mCallback);
+ mCallback = null;
+ }
+
+ super.onStop();
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteExpandCollapseButton.java b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteExpandCollapseButton.java
new file mode 100644
index 0000000..392b39d
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteExpandCollapseButton.java
@@ -0,0 +1,93 @@
+/*
+ * 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.support.mediarouter.app;
+
+import android.content.Context;
+import android.graphics.ColorFilter;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.drawable.AnimationDrawable;
+import android.support.v4.content.ContextCompat;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageButton;
+
+import com.android.media.update.R;
+
+/**
+ * Chevron/Caret button to expand/collapse group volume list with animation.
+ */
+class MediaRouteExpandCollapseButton extends ImageButton {
+ final AnimationDrawable mExpandAnimationDrawable;
+ final AnimationDrawable mCollapseAnimationDrawable;
+ final String mExpandGroupDescription;
+ final String mCollapseGroupDescription;
+ boolean mIsGroupExpanded;
+ OnClickListener mListener;
+
+ public MediaRouteExpandCollapseButton(Context context) {
+ this(context, null);
+ }
+
+ public MediaRouteExpandCollapseButton(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public MediaRouteExpandCollapseButton(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ mExpandAnimationDrawable = (AnimationDrawable) ContextCompat.getDrawable(
+ context, R.drawable.mr_group_expand);
+ mCollapseAnimationDrawable = (AnimationDrawable) ContextCompat.getDrawable(
+ context, R.drawable.mr_group_collapse);
+
+ ColorFilter filter = new PorterDuffColorFilter(
+ MediaRouterThemeHelper.getControllerColor(context, defStyleAttr),
+ PorterDuff.Mode.SRC_IN);
+ mExpandAnimationDrawable.setColorFilter(filter);
+ mCollapseAnimationDrawable.setColorFilter(filter);
+
+ mExpandGroupDescription = context.getString(R.string.mr_controller_expand_group);
+ mCollapseGroupDescription = context.getString(R.string.mr_controller_collapse_group);
+
+ setImageDrawable(mExpandAnimationDrawable.getFrame(0));
+ setContentDescription(mExpandGroupDescription);
+
+ super.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ mIsGroupExpanded = !mIsGroupExpanded;
+ if (mIsGroupExpanded) {
+ setImageDrawable(mExpandAnimationDrawable);
+ mExpandAnimationDrawable.start();
+ setContentDescription(mCollapseGroupDescription);
+ } else {
+ setImageDrawable(mCollapseAnimationDrawable);
+ mCollapseAnimationDrawable.start();
+ setContentDescription(mExpandGroupDescription);
+ }
+ if (mListener != null) {
+ mListener.onClick(view);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void setOnClickListener(OnClickListener listener) {
+ mListener = listener;
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteVolumeSlider.java b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteVolumeSlider.java
new file mode 100644
index 0000000..7a34fb5
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteVolumeSlider.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.support.mediarouter.app;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.support.v7.widget.AppCompatSeekBar;
+import android.util.AttributeSet;
+import android.util.Log;
+
+/**
+ * Volume slider with showing, hiding, and applying alpha supports to the thumb.
+ */
+class MediaRouteVolumeSlider extends AppCompatSeekBar {
+ private static final String TAG = "MediaRouteVolumeSlider";
+
+ private final float mDisabledAlpha;
+
+ private boolean mHideThumb;
+ private Drawable mThumb;
+ private int mColor;
+
+ public MediaRouteVolumeSlider(Context context) {
+ this(context, null);
+ }
+
+ public MediaRouteVolumeSlider(Context context, AttributeSet attrs) {
+ this(context, attrs, android.support.v7.appcompat.R.attr.seekBarStyle);
+ }
+
+ public MediaRouteVolumeSlider(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ mDisabledAlpha = MediaRouterThemeHelper.getDisabledAlpha(context);
+ }
+
+ @Override
+ protected void drawableStateChanged() {
+ super.drawableStateChanged();
+ int alpha = isEnabled() ? 0xFF : (int) (0xFF * mDisabledAlpha);
+
+ // The thumb drawable is a collection of drawables and its current drawables are changed per
+ // state. Apply the color filter and alpha on every state change.
+ mThumb.setColorFilter(mColor, PorterDuff.Mode.SRC_IN);
+ mThumb.setAlpha(alpha);
+
+ getProgressDrawable().setColorFilter(mColor, PorterDuff.Mode.SRC_IN);
+ getProgressDrawable().setAlpha(alpha);
+ }
+
+ @Override
+ public void setThumb(Drawable thumb) {
+ mThumb = thumb;
+ super.setThumb(mHideThumb ? null : mThumb);
+ }
+
+ /**
+ * Sets whether to show or hide thumb.
+ */
+ public void setHideThumb(boolean hideThumb) {
+ if (mHideThumb == hideThumb) {
+ return;
+ }
+ mHideThumb = hideThumb;
+ super.setThumb(mHideThumb ? null : mThumb);
+ }
+
+ /**
+ * Sets the volume slider color. The change takes effect next time drawable state is changed.
+ * <p>
+ * The color cannot be translucent, otherwise the underlying progress bar will be seen through
+ * the thumb.
+ * </p>
+ */
+ public void setColor(int color) {
+ if (mColor == color) {
+ return;
+ }
+ if (Color.alpha(color) != 0xFF) {
+ Log.e(TAG, "Volume slider color cannot be translucent: #" + Integer.toHexString(color));
+ }
+ mColor = color;
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouterThemeHelper.java b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouterThemeHelper.java
new file mode 100644
index 0000000..7440130
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouterThemeHelper.java
@@ -0,0 +1,216 @@
+/*
+ * 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.support.mediarouter.app;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.support.annotation.IntDef;
+import android.support.v4.graphics.ColorUtils;
+import android.util.TypedValue;
+import android.view.ContextThemeWrapper;
+import android.view.View;
+
+import com.android.media.update.R;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+final class MediaRouterThemeHelper {
+ private static final float MIN_CONTRAST = 3.0f;
+
+ @IntDef({COLOR_DARK_ON_LIGHT_BACKGROUND, COLOR_WHITE_ON_DARK_BACKGROUND})
+ @Retention(RetentionPolicy.SOURCE)
+ private @interface ControllerColorType {}
+
+ static final int COLOR_DARK_ON_LIGHT_BACKGROUND = 0xDE000000; /* Opacity of 87% */
+ static final int COLOR_WHITE_ON_DARK_BACKGROUND = Color.WHITE;
+
+ private MediaRouterThemeHelper() {
+ }
+
+ static Context createThemedButtonContext(Context context) {
+ // Apply base Media Router theme.
+ context = new ContextThemeWrapper(context, getRouterThemeId(context));
+
+ // Apply custom Media Router theme.
+ int style = getThemeResource(context, R.attr.mediaRouteTheme);
+ if (style != 0) {
+ context = new ContextThemeWrapper(context, style);
+ }
+
+ return context;
+ }
+
+ /*
+ * The following two methods are to be used in conjunction. They should be used to prepare
+ * the context and theme for a super class constructor (the latter method relies on the
+ * former method to properly prepare the context):
+ * super(context = createThemedDialogContext(context, theme),
+ * createThemedDialogStyle(context));
+ *
+ * It will apply theme in the following order (style lookups will be done in reverse):
+ * 1) Current theme
+ * 2) Supplied theme
+ * 3) Base Media Router theme
+ * 4) Custom Media Router theme, if provided
+ */
+ static Context createThemedDialogContext(Context context, int theme, boolean alertDialog) {
+ // 1) Current theme is already applied to the context
+
+ // 2) If no theme is supplied, look it up from the context (dialogTheme/alertDialogTheme)
+ if (theme == 0) {
+ theme = getThemeResource(context, !alertDialog
+ ? android.support.v7.appcompat.R.attr.dialogTheme
+ : android.support.v7.appcompat.R.attr.alertDialogTheme);
+ }
+ // Apply it
+ context = new ContextThemeWrapper(context, theme);
+
+ // 3) If a custom Media Router theme is provided then apply the base theme
+ if (getThemeResource(context, R.attr.mediaRouteTheme) != 0) {
+ context = new ContextThemeWrapper(context, getRouterThemeId(context));
+ }
+
+ return context;
+ }
+ // This method should be used in conjunction with the previous method.
+ static int createThemedDialogStyle(Context context) {
+ // 4) Apply the custom Media Router theme
+ int theme = getThemeResource(context, R.attr.mediaRouteTheme);
+ if (theme == 0) {
+ // 3) No custom MediaRouther theme was provided so apply the base theme instead
+ theme = getRouterThemeId(context);
+ }
+
+ return theme;
+ }
+ // END. Previous two methods should be used in conjunction.
+
+ static int getThemeResource(Context context, int attr) {
+ TypedValue value = new TypedValue();
+ return context.getTheme().resolveAttribute(attr, value, true) ? value.resourceId : 0;
+ }
+
+ static float getDisabledAlpha(Context context) {
+ TypedValue value = new TypedValue();
+ return context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, value, true)
+ ? value.getFloat() : 0.5f;
+ }
+
+ static @ControllerColorType int getControllerColor(Context context, int style) {
+ int primaryColor = getThemeColor(context, style,
+ android.support.v7.appcompat.R.attr.colorPrimary);
+ if (primaryColor == 0) {
+ primaryColor = getThemeColor(context, style, android.R.attr.colorPrimary);
+ if (primaryColor == 0) {
+ primaryColor = 0xFF000000;
+ }
+ }
+ if (ColorUtils.calculateContrast(COLOR_WHITE_ON_DARK_BACKGROUND, primaryColor)
+ >= MIN_CONTRAST) {
+ return COLOR_WHITE_ON_DARK_BACKGROUND;
+ }
+ return COLOR_DARK_ON_LIGHT_BACKGROUND;
+ }
+
+ static int getButtonTextColor(Context context) {
+ int primaryColor = getThemeColor(context, 0,
+ android.support.v7.appcompat.R.attr.colorPrimary);
+ int backgroundColor = getThemeColor(context, 0, android.R.attr.colorBackground);
+
+ if (ColorUtils.calculateContrast(primaryColor, backgroundColor) < MIN_CONTRAST) {
+ // Default to colorAccent if the contrast ratio is low.
+ return getThemeColor(context, 0, android.support.v7.appcompat.R.attr.colorAccent);
+ }
+ return primaryColor;
+ }
+
+ static void setMediaControlsBackgroundColor(
+ Context context, View mainControls, View groupControls, boolean hasGroup) {
+ int primaryColor = getThemeColor(context, 0,
+ android.support.v7.appcompat.R.attr.colorPrimary);
+ int primaryDarkColor = getThemeColor(context, 0,
+ android.support.v7.appcompat.R.attr.colorPrimaryDark);
+ if (hasGroup && getControllerColor(context, 0) == COLOR_DARK_ON_LIGHT_BACKGROUND) {
+ // Instead of showing dark controls in a possibly dark (i.e. the primary dark), model
+ // the white dialog and use the primary color for the group controls.
+ primaryDarkColor = primaryColor;
+ primaryColor = Color.WHITE;
+ }
+ mainControls.setBackgroundColor(primaryColor);
+ groupControls.setBackgroundColor(primaryDarkColor);
+ // Also store the background colors to the view tags. They are used in
+ // setVolumeSliderColor() below.
+ mainControls.setTag(primaryColor);
+ groupControls.setTag(primaryDarkColor);
+ }
+
+ static void setVolumeSliderColor(
+ Context context, MediaRouteVolumeSlider volumeSlider, View backgroundView) {
+ int controllerColor = getControllerColor(context, 0);
+ if (Color.alpha(controllerColor) != 0xFF) {
+ // Composite with the background in order not to show the underlying progress bar
+ // through the thumb.
+ int backgroundColor = (int) backgroundView.getTag();
+ controllerColor = ColorUtils.compositeColors(controllerColor, backgroundColor);
+ }
+ volumeSlider.setColor(controllerColor);
+ }
+
+ private static boolean isLightTheme(Context context) {
+ TypedValue value = new TypedValue();
+ return context.getTheme().resolveAttribute(android.support.v7.appcompat.R.attr.isLightTheme,
+ value, true) && value.data != 0;
+ }
+
+ private static int getThemeColor(Context context, int style, int attr) {
+ if (style != 0) {
+ int[] attrs = { attr };
+ TypedArray ta = context.obtainStyledAttributes(style, attrs);
+ int color = ta.getColor(0, 0);
+ ta.recycle();
+ if (color != 0) {
+ return color;
+ }
+ }
+ TypedValue value = new TypedValue();
+ context.getTheme().resolveAttribute(attr, value, true);
+ if (value.resourceId != 0) {
+ return context.getResources().getColor(value.resourceId);
+ }
+ return value.data;
+ }
+
+ static int getRouterThemeId(Context context) {
+ int themeId;
+ if (isLightTheme(context)) {
+ if (getControllerColor(context, 0) == COLOR_DARK_ON_LIGHT_BACKGROUND) {
+ themeId = R.style.Theme_MediaRouter_Light;
+ } else {
+ themeId = R.style.Theme_MediaRouter_Light_DarkControlPanel;
+ }
+ } else {
+ if (getControllerColor(context, 0) == COLOR_DARK_ON_LIGHT_BACKGROUND) {
+ themeId = R.style.Theme_MediaRouter_LightControlPanel;
+ } else {
+ themeId = R.style.Theme_MediaRouter;
+ }
+ }
+ return themeId;
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/app/OverlayListView.java b/packages/MediaComponents/src/com/android/support/mediarouter/app/OverlayListView.java
new file mode 100644
index 0000000..59019ff
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/app/OverlayListView.java
@@ -0,0 +1,265 @@
+/*
+ * 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.support.mediarouter.app;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.util.AttributeSet;
+import android.view.animation.Interpolator;
+import android.widget.ListView;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * A ListView which has an additional overlay layer. {@link BitmapDrawable}
+ * can be added to the layer and can be animated.
+ */
+final class OverlayListView extends ListView {
+ private final List<OverlayObject> mOverlayObjects = new ArrayList<>();
+
+ public OverlayListView(Context context) {
+ super(context);
+ }
+
+ public OverlayListView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public OverlayListView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ /**
+ * Adds an object to the overlay layer.
+ *
+ * @param object An object to be added.
+ */
+ public void addOverlayObject(OverlayObject object) {
+ mOverlayObjects.add(object);
+ }
+
+ /**
+ * Starts all animations of objects in the overlay layer.
+ */
+ public void startAnimationAll() {
+ for (OverlayObject object : mOverlayObjects) {
+ if (!object.isAnimationStarted()) {
+ object.startAnimation(getDrawingTime());
+ }
+ }
+ }
+
+ /**
+ * Stops all animations of objects in the overlay layer.
+ */
+ public void stopAnimationAll() {
+ for (OverlayObject object : mOverlayObjects) {
+ object.stopAnimation();
+ }
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ if (mOverlayObjects.size() > 0) {
+ Iterator<OverlayObject> it = mOverlayObjects.iterator();
+ while (it.hasNext()) {
+ OverlayObject object = it.next();
+ BitmapDrawable bitmap = object.getBitmapDrawable();
+ if (bitmap != null) {
+ bitmap.draw(canvas);
+ }
+ if (!object.update(getDrawingTime())) {
+ it.remove();
+ }
+ }
+ }
+ }
+
+ /**
+ * A class that represents an object to be shown in the overlay layer.
+ */
+ public static class OverlayObject {
+ private BitmapDrawable mBitmap;
+ private float mCurrentAlpha = 1.0f;
+ private Rect mCurrentBounds;
+ private Interpolator mInterpolator;
+ private long mDuration;
+ private Rect mStartRect;
+ private int mDeltaY;
+ private float mStartAlpha = 1.0f;
+ private float mEndAlpha = 1.0f;
+ private long mStartTime;
+ private boolean mIsAnimationStarted;
+ private boolean mIsAnimationEnded;
+ private OnAnimationEndListener mListener;
+
+ public OverlayObject(BitmapDrawable bitmap, Rect startRect) {
+ mBitmap = bitmap;
+ mStartRect = startRect;
+ mCurrentBounds = new Rect(startRect);
+ if (mBitmap != null && mCurrentBounds != null) {
+ mBitmap.setAlpha((int) (mCurrentAlpha * 255));
+ mBitmap.setBounds(mCurrentBounds);
+ }
+ }
+
+ /**
+ * Returns the bitmap that this object represents.
+ *
+ * @return BitmapDrawable that this object has.
+ */
+ public BitmapDrawable getBitmapDrawable() {
+ return mBitmap;
+ }
+
+ /**
+ * Returns the started status of the animation.
+ *
+ * @return True if the animation has started, false otherwise.
+ */
+ public boolean isAnimationStarted() {
+ return mIsAnimationStarted;
+ }
+
+ /**
+ * Sets animation for varying alpha.
+ *
+ * @param startAlpha Starting alpha value for the animation, where 1.0 means
+ * fully opaque and 0.0 means fully transparent.
+ * @param endAlpha Ending alpha value for the animation.
+ * @return This OverlayObject to allow for chaining of calls.
+ */
+ public OverlayObject setAlphaAnimation(float startAlpha, float endAlpha) {
+ mStartAlpha = startAlpha;
+ mEndAlpha = endAlpha;
+ return this;
+ }
+
+ /**
+ * Sets animation for moving objects vertically.
+ *
+ * @param deltaY Distance to move in pixels.
+ * @return This OverlayObject to allow for chaining of calls.
+ */
+ public OverlayObject setTranslateYAnimation(int deltaY) {
+ mDeltaY = deltaY;
+ return this;
+ }
+
+ /**
+ * Sets how long the animation will last.
+ *
+ * @param duration Duration in milliseconds
+ * @return This OverlayObject to allow for chaining of calls.
+ */
+ public OverlayObject setDuration(long duration) {
+ mDuration = duration;
+ return this;
+ }
+
+ /**
+ * Sets the acceleration curve for this animation.
+ *
+ * @param interpolator The interpolator which defines the acceleration curve
+ * @return This OverlayObject to allow for chaining of calls.
+ */
+ public OverlayObject setInterpolator(Interpolator interpolator) {
+ mInterpolator = interpolator;
+ return this;
+ }
+
+ /**
+ * Binds an animation end listener to the animation.
+ *
+ * @param listener the animation end listener to be notified.
+ * @return This OverlayObject to allow for chaining of calls.
+ */
+ public OverlayObject setAnimationEndListener(OnAnimationEndListener listener) {
+ mListener = listener;
+ return this;
+ }
+
+ /**
+ * Starts the animation and sets the start time.
+ *
+ * @param startTime Start time to be set in Millis
+ */
+ public void startAnimation(long startTime) {
+ mStartTime = startTime;
+ mIsAnimationStarted = true;
+ }
+
+ /**
+ * Stops the animation.
+ */
+ public void stopAnimation() {
+ mIsAnimationStarted = true;
+ mIsAnimationEnded = true;
+ if (mListener != null) {
+ mListener.onAnimationEnd();
+ }
+ }
+
+ /**
+ * Calculates and updates current bounds and alpha value.
+ *
+ * @param currentTime Current time.in millis
+ */
+ public boolean update(long currentTime) {
+ if (mIsAnimationEnded) {
+ return false;
+ }
+ float normalizedTime = (currentTime - mStartTime) / (float) mDuration;
+ normalizedTime = Math.max(0.0f, Math.min(1.0f, normalizedTime));
+ if (!mIsAnimationStarted) {
+ normalizedTime = 0.0f;
+ }
+ float interpolatedTime = (mInterpolator == null) ? normalizedTime
+ : mInterpolator.getInterpolation(normalizedTime);
+ int deltaY = (int) (mDeltaY * interpolatedTime);
+ mCurrentBounds.top = mStartRect.top + deltaY;
+ mCurrentBounds.bottom = mStartRect.bottom + deltaY;
+ mCurrentAlpha = mStartAlpha + (mEndAlpha - mStartAlpha) * interpolatedTime;
+ if (mBitmap != null && mCurrentBounds != null) {
+ mBitmap.setAlpha((int) (mCurrentAlpha * 255));
+ mBitmap.setBounds(mCurrentBounds);
+ }
+ if (mIsAnimationStarted && normalizedTime >= 1.0f) {
+ mIsAnimationEnded = true;
+ if (mListener != null) {
+ mListener.onAnimationEnd();
+ }
+ }
+ return !mIsAnimationEnded;
+ }
+
+ /**
+ * An animation listener that receives notifications when the animation ends.
+ */
+ public interface OnAnimationEndListener {
+ /**
+ * Notifies the end of the animation.
+ */
+ public void onAnimationEnd();
+ }
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/jellybean-mr1/MediaRouterJellybeanMr1.java b/packages/MediaComponents/src/com/android/support/mediarouter/jellybean-mr1/MediaRouterJellybeanMr1.java
new file mode 100644
index 0000000..f8539bd
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/jellybean-mr1/MediaRouterJellybeanMr1.java
@@ -0,0 +1,185 @@
+/*
+ * 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.support.mediarouter.media;
+
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.os.Build;
+import android.os.Handler;
+import android.support.annotation.RequiresApi;
+import android.util.Log;
+import android.view.Display;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+// @@RequiresApi(17)
+final class MediaRouterJellybeanMr1 {
+ private static final String TAG = "MediaRouterJellybeanMr1";
+
+ public static Object createCallback(Callback callback) {
+ return new CallbackProxy<Callback>(callback);
+ }
+
+ public static final class RouteInfo {
+ public static boolean isEnabled(Object routeObj) {
+ return ((android.media.MediaRouter.RouteInfo)routeObj).isEnabled();
+ }
+
+ public static Display getPresentationDisplay(Object routeObj) {
+ // android.media.MediaRouter.RouteInfo.getPresentationDisplay() was
+ // added in API 17. However, some factory releases of JB MR1 missed it.
+ try {
+ return ((android.media.MediaRouter.RouteInfo)routeObj).getPresentationDisplay();
+ } catch (NoSuchMethodError ex) {
+ Log.w(TAG, "Cannot get presentation display for the route.", ex);
+ }
+ return null;
+ }
+ }
+
+ public static interface Callback extends MediaRouterJellybean.Callback {
+ public void onRoutePresentationDisplayChanged(Object routeObj);
+ }
+
+ /**
+ * Workaround the fact that the version of MediaRouter.addCallback() that accepts a
+ * flag to perform an active scan does not exist in JB MR1 so we need to force
+ * wifi display scans directly through the DisplayManager.
+ * Do not use on JB MR2 and above.
+ */
+ public static final class ActiveScanWorkaround implements Runnable {
+ // Time between wifi display scans when actively scanning in milliseconds.
+ private static final int WIFI_DISPLAY_SCAN_INTERVAL = 15000;
+
+ private final DisplayManager mDisplayManager;
+ private final Handler mHandler;
+ private Method mScanWifiDisplaysMethod;
+
+ private boolean mActivelyScanningWifiDisplays;
+
+ public ActiveScanWorkaround(Context context, Handler handler) {
+ if (Build.VERSION.SDK_INT != 17) {
+ throw new UnsupportedOperationException();
+ }
+
+ mDisplayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
+ mHandler = handler;
+ try {
+ mScanWifiDisplaysMethod = DisplayManager.class.getMethod("scanWifiDisplays");
+ } catch (NoSuchMethodException ex) {
+ }
+ }
+
+ public void setActiveScanRouteTypes(int routeTypes) {
+ // On JB MR1, there is no API to scan wifi display routes.
+ // Instead we must make a direct call into the DisplayManager to scan
+ // wifi displays on this version but only when live video routes are requested.
+ // See also the JellybeanMr2Impl implementation of this method.
+ // This was fixed in JB MR2 by adding a new overload of addCallback() to
+ // enable active scanning on request.
+ if ((routeTypes & MediaRouterJellybean.ROUTE_TYPE_LIVE_VIDEO) != 0) {
+ if (!mActivelyScanningWifiDisplays) {
+ if (mScanWifiDisplaysMethod != null) {
+ mActivelyScanningWifiDisplays = true;
+ mHandler.post(this);
+ } else {
+ Log.w(TAG, "Cannot scan for wifi displays because the "
+ + "DisplayManager.scanWifiDisplays() method is "
+ + "not available on this device.");
+ }
+ }
+ } else {
+ if (mActivelyScanningWifiDisplays) {
+ mActivelyScanningWifiDisplays = false;
+ mHandler.removeCallbacks(this);
+ }
+ }
+ }
+
+ @Override
+ public void run() {
+ if (mActivelyScanningWifiDisplays) {
+ try {
+ mScanWifiDisplaysMethod.invoke(mDisplayManager);
+ } catch (IllegalAccessException ex) {
+ Log.w(TAG, "Cannot scan for wifi displays.", ex);
+ } catch (InvocationTargetException ex) {
+ Log.w(TAG, "Cannot scan for wifi displays.", ex);
+ }
+ mHandler.postDelayed(this, WIFI_DISPLAY_SCAN_INTERVAL);
+ }
+ }
+ }
+
+ /**
+ * Workaround the fact that the isConnecting() method does not exist in JB MR1.
+ * Do not use on JB MR2 and above.
+ */
+ public static final class IsConnectingWorkaround {
+ private Method mGetStatusCodeMethod;
+ private int mStatusConnecting;
+
+ public IsConnectingWorkaround() {
+ if (Build.VERSION.SDK_INT != 17) {
+ throw new UnsupportedOperationException();
+ }
+
+ try {
+ Field statusConnectingField =
+ android.media.MediaRouter.RouteInfo.class.getField("STATUS_CONNECTING");
+ mStatusConnecting = statusConnectingField.getInt(null);
+ mGetStatusCodeMethod =
+ android.media.MediaRouter.RouteInfo.class.getMethod("getStatusCode");
+ } catch (NoSuchFieldException ex) {
+ } catch (NoSuchMethodException ex) {
+ } catch (IllegalAccessException ex) {
+ }
+ }
+
+ public boolean isConnecting(Object routeObj) {
+ android.media.MediaRouter.RouteInfo route =
+ (android.media.MediaRouter.RouteInfo)routeObj;
+
+ if (mGetStatusCodeMethod != null) {
+ try {
+ int statusCode = (Integer)mGetStatusCodeMethod.invoke(route);
+ return statusCode == mStatusConnecting;
+ } catch (IllegalAccessException ex) {
+ } catch (InvocationTargetException ex) {
+ }
+ }
+
+ // Assume not connecting.
+ return false;
+ }
+ }
+
+ static class CallbackProxy<T extends Callback>
+ extends MediaRouterJellybean.CallbackProxy<T> {
+ public CallbackProxy(T callback) {
+ super(callback);
+ }
+
+ @Override
+ public void onRoutePresentationDisplayChanged(android.media.MediaRouter router,
+ android.media.MediaRouter.RouteInfo route) {
+ mCallback.onRoutePresentationDisplayChanged(route);
+ }
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/jellybean-mr2/MediaRouterJellybeanMr2.java b/packages/MediaComponents/src/com/android/support/mediarouter/jellybean-mr2/MediaRouterJellybeanMr2.java
new file mode 100644
index 0000000..1103549
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/jellybean-mr2/MediaRouterJellybeanMr2.java
@@ -0,0 +1,45 @@
+/*
+ * 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.support.mediarouter.media;
+
+// @@RequiresApi(18)
+final class MediaRouterJellybeanMr2 {
+ public static Object getDefaultRoute(Object routerObj) {
+ return ((android.media.MediaRouter)routerObj).getDefaultRoute();
+ }
+
+ public static void addCallback(Object routerObj, int types, Object callbackObj, int flags) {
+ ((android.media.MediaRouter)routerObj).addCallback(types,
+ (android.media.MediaRouter.Callback)callbackObj, flags);
+ }
+
+ public static final class RouteInfo {
+ public static CharSequence getDescription(Object routeObj) {
+ return ((android.media.MediaRouter.RouteInfo)routeObj).getDescription();
+ }
+
+ public static boolean isConnecting(Object routeObj) {
+ return ((android.media.MediaRouter.RouteInfo)routeObj).isConnecting();
+ }
+ }
+
+ public static final class UserRouteInfo {
+ public static void setDescription(Object routeObj, CharSequence description) {
+ ((android.media.MediaRouter.UserRouteInfo)routeObj).setDescription(description);
+ }
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/jellybean/MediaRouterJellybean.java b/packages/MediaComponents/src/com/android/support/mediarouter/jellybean/MediaRouterJellybean.java
new file mode 100644
index 0000000..0bb59b8
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/jellybean/MediaRouterJellybean.java
@@ -0,0 +1,462 @@
+/*
+ * 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.support.mediarouter.media;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.media.AudioManager;
+import android.os.Build;
+import android.util.Log;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+// @@RequiresApi(16)
+final class MediaRouterJellybean {
+ private static final String TAG = "MediaRouterJellybean";
+
+ // android.media.AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP = 0x80;
+ // android.media.AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES = 0x100;
+ // android.media.AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER = 0x200;
+ public static final int DEVICE_OUT_BLUETOOTH = 0x80 | 0x100 | 0x200;
+
+ public static final int ROUTE_TYPE_LIVE_AUDIO = 0x1;
+ public static final int ROUTE_TYPE_LIVE_VIDEO = 0x2;
+ public static final int ROUTE_TYPE_USER = 0x00800000;
+
+ public static final int ALL_ROUTE_TYPES =
+ MediaRouterJellybean.ROUTE_TYPE_LIVE_AUDIO
+ | MediaRouterJellybean.ROUTE_TYPE_LIVE_VIDEO
+ | MediaRouterJellybean.ROUTE_TYPE_USER;
+
+ public static Object getMediaRouter(Context context) {
+ return context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
+ }
+
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ public static List getRoutes(Object routerObj) {
+ final android.media.MediaRouter router = (android.media.MediaRouter)routerObj;
+ final int count = router.getRouteCount();
+ List out = new ArrayList(count);
+ for (int i = 0; i < count; i++) {
+ out.add(router.getRouteAt(i));
+ }
+ return out;
+ }
+
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ public static List getCategories(Object routerObj) {
+ final android.media.MediaRouter router = (android.media.MediaRouter)routerObj;
+ final int count = router.getCategoryCount();
+ List out = new ArrayList(count);
+ for (int i = 0; i < count; i++) {
+ out.add(router.getCategoryAt(i));
+ }
+ return out;
+ }
+
+ public static Object getSelectedRoute(Object routerObj, int type) {
+ return ((android.media.MediaRouter)routerObj).getSelectedRoute(type);
+ }
+
+ public static void selectRoute(Object routerObj, int types, Object routeObj) {
+ ((android.media.MediaRouter)routerObj).selectRoute(types,
+ (android.media.MediaRouter.RouteInfo)routeObj);
+ }
+
+ public static void addCallback(Object routerObj, int types, Object callbackObj) {
+ ((android.media.MediaRouter)routerObj).addCallback(types,
+ (android.media.MediaRouter.Callback)callbackObj);
+ }
+
+ public static void removeCallback(Object routerObj, Object callbackObj) {
+ ((android.media.MediaRouter)routerObj).removeCallback(
+ (android.media.MediaRouter.Callback)callbackObj);
+ }
+
+ public static Object createRouteCategory(Object routerObj,
+ String name, boolean isGroupable) {
+ return ((android.media.MediaRouter)routerObj).createRouteCategory(name, isGroupable);
+ }
+
+ public static Object createUserRoute(Object routerObj, Object categoryObj) {
+ return ((android.media.MediaRouter)routerObj).createUserRoute(
+ (android.media.MediaRouter.RouteCategory)categoryObj);
+ }
+
+ public static void addUserRoute(Object routerObj, Object routeObj) {
+ ((android.media.MediaRouter)routerObj).addUserRoute(
+ (android.media.MediaRouter.UserRouteInfo)routeObj);
+ }
+
+ public static void removeUserRoute(Object routerObj, Object routeObj) {
+ ((android.media.MediaRouter)routerObj).removeUserRoute(
+ (android.media.MediaRouter.UserRouteInfo)routeObj);
+ }
+
+ public static Object createCallback(Callback callback) {
+ return new CallbackProxy<Callback>(callback);
+ }
+
+ public static Object createVolumeCallback(VolumeCallback callback) {
+ return new VolumeCallbackProxy<VolumeCallback>(callback);
+ }
+
+ static boolean checkRoutedToBluetooth(Context context) {
+ try {
+ AudioManager audioManager = (AudioManager) context.getSystemService(
+ Context.AUDIO_SERVICE);
+ Method method = audioManager.getClass().getDeclaredMethod(
+ "getDevicesForStream", int.class);
+ int device = (Integer) method.invoke(audioManager, AudioManager.STREAM_MUSIC);
+ return (device & DEVICE_OUT_BLUETOOTH) != 0;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ public static final class RouteInfo {
+ public static CharSequence getName(Object routeObj, Context context) {
+ return ((android.media.MediaRouter.RouteInfo)routeObj).getName(context);
+ }
+
+ public static CharSequence getStatus(Object routeObj) {
+ return ((android.media.MediaRouter.RouteInfo)routeObj).getStatus();
+ }
+
+ public static int getSupportedTypes(Object routeObj) {
+ return ((android.media.MediaRouter.RouteInfo)routeObj).getSupportedTypes();
+ }
+
+ public static Object getCategory(Object routeObj) {
+ return ((android.media.MediaRouter.RouteInfo)routeObj).getCategory();
+ }
+
+ public static Drawable getIconDrawable(Object routeObj) {
+ return ((android.media.MediaRouter.RouteInfo)routeObj).getIconDrawable();
+ }
+
+ public static int getPlaybackType(Object routeObj) {
+ return ((android.media.MediaRouter.RouteInfo)routeObj).getPlaybackType();
+ }
+
+ public static int getPlaybackStream(Object routeObj) {
+ return ((android.media.MediaRouter.RouteInfo)routeObj).getPlaybackStream();
+ }
+
+ public static int getVolume(Object routeObj) {
+ return ((android.media.MediaRouter.RouteInfo)routeObj).getVolume();
+ }
+
+ public static int getVolumeMax(Object routeObj) {
+ return ((android.media.MediaRouter.RouteInfo)routeObj).getVolumeMax();
+ }
+
+ public static int getVolumeHandling(Object routeObj) {
+ return ((android.media.MediaRouter.RouteInfo)routeObj).getVolumeHandling();
+ }
+
+ public static Object getTag(Object routeObj) {
+ return ((android.media.MediaRouter.RouteInfo)routeObj).getTag();
+ }
+
+ public static void setTag(Object routeObj, Object tag) {
+ ((android.media.MediaRouter.RouteInfo)routeObj).setTag(tag);
+ }
+
+ public static void requestSetVolume(Object routeObj, int volume) {
+ ((android.media.MediaRouter.RouteInfo)routeObj).requestSetVolume(volume);
+ }
+
+ public static void requestUpdateVolume(Object routeObj, int direction) {
+ ((android.media.MediaRouter.RouteInfo)routeObj).requestUpdateVolume(direction);
+ }
+
+ public static Object getGroup(Object routeObj) {
+ return ((android.media.MediaRouter.RouteInfo)routeObj).getGroup();
+ }
+
+ public static boolean isGroup(Object routeObj) {
+ return routeObj instanceof android.media.MediaRouter.RouteGroup;
+ }
+ }
+
+ public static final class RouteGroup {
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ public static List getGroupedRoutes(Object groupObj) {
+ final android.media.MediaRouter.RouteGroup group =
+ (android.media.MediaRouter.RouteGroup)groupObj;
+ final int count = group.getRouteCount();
+ List out = new ArrayList(count);
+ for (int i = 0; i < count; i++) {
+ out.add(group.getRouteAt(i));
+ }
+ return out;
+ }
+ }
+
+ public static final class UserRouteInfo {
+ public static void setName(Object routeObj, CharSequence name) {
+ ((android.media.MediaRouter.UserRouteInfo)routeObj).setName(name);
+ }
+
+ public static void setStatus(Object routeObj, CharSequence status) {
+ ((android.media.MediaRouter.UserRouteInfo)routeObj).setStatus(status);
+ }
+
+ public static void setIconDrawable(Object routeObj, Drawable icon) {
+ ((android.media.MediaRouter.UserRouteInfo)routeObj).setIconDrawable(icon);
+ }
+
+ public static void setPlaybackType(Object routeObj, int type) {
+ ((android.media.MediaRouter.UserRouteInfo)routeObj).setPlaybackType(type);
+ }
+
+ public static void setPlaybackStream(Object routeObj, int stream) {
+ ((android.media.MediaRouter.UserRouteInfo)routeObj).setPlaybackStream(stream);
+ }
+
+ public static void setVolume(Object routeObj, int volume) {
+ ((android.media.MediaRouter.UserRouteInfo)routeObj).setVolume(volume);
+ }
+
+ public static void setVolumeMax(Object routeObj, int volumeMax) {
+ ((android.media.MediaRouter.UserRouteInfo)routeObj).setVolumeMax(volumeMax);
+ }
+
+ public static void setVolumeHandling(Object routeObj, int volumeHandling) {
+ ((android.media.MediaRouter.UserRouteInfo)routeObj).setVolumeHandling(volumeHandling);
+ }
+
+ public static void setVolumeCallback(Object routeObj, Object volumeCallbackObj) {
+ ((android.media.MediaRouter.UserRouteInfo)routeObj).setVolumeCallback(
+ (android.media.MediaRouter.VolumeCallback)volumeCallbackObj);
+ }
+
+ public static void setRemoteControlClient(Object routeObj, Object rccObj) {
+ ((android.media.MediaRouter.UserRouteInfo)routeObj).setRemoteControlClient(
+ (android.media.RemoteControlClient)rccObj);
+ }
+ }
+
+ public static final class RouteCategory {
+ public static CharSequence getName(Object categoryObj, Context context) {
+ return ((android.media.MediaRouter.RouteCategory)categoryObj).getName(context);
+ }
+
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ public static List getRoutes(Object categoryObj) {
+ ArrayList out = new ArrayList();
+ ((android.media.MediaRouter.RouteCategory)categoryObj).getRoutes(out);
+ return out;
+ }
+
+ public static int getSupportedTypes(Object categoryObj) {
+ return ((android.media.MediaRouter.RouteCategory)categoryObj).getSupportedTypes();
+ }
+
+ public static boolean isGroupable(Object categoryObj) {
+ return ((android.media.MediaRouter.RouteCategory)categoryObj).isGroupable();
+ }
+ }
+
+ public static interface Callback {
+ public void onRouteSelected(int type, Object routeObj);
+ public void onRouteUnselected(int type, Object routeObj);
+ public void onRouteAdded(Object routeObj);
+ public void onRouteRemoved(Object routeObj);
+ public void onRouteChanged(Object routeObj);
+ public void onRouteGrouped(Object routeObj, Object groupObj, int index);
+ public void onRouteUngrouped(Object routeObj, Object groupObj);
+ public void onRouteVolumeChanged(Object routeObj);
+ }
+
+ public static interface VolumeCallback {
+ public void onVolumeSetRequest(Object routeObj, int volume);
+ public void onVolumeUpdateRequest(Object routeObj, int direction);
+ }
+
+ /**
+ * Workaround for limitations of selectRoute() on JB and JB MR1.
+ * Do not use on JB MR2 and above.
+ */
+ public static final class SelectRouteWorkaround {
+ private Method mSelectRouteIntMethod;
+
+ public SelectRouteWorkaround() {
+ if (Build.VERSION.SDK_INT < 16 || Build.VERSION.SDK_INT > 17) {
+ throw new UnsupportedOperationException();
+ }
+ try {
+ mSelectRouteIntMethod = android.media.MediaRouter.class.getMethod(
+ "selectRouteInt", int.class, android.media.MediaRouter.RouteInfo.class);
+ } catch (NoSuchMethodException ex) {
+ }
+ }
+
+ public void selectRoute(Object routerObj, int types, Object routeObj) {
+ android.media.MediaRouter router = (android.media.MediaRouter)routerObj;
+ android.media.MediaRouter.RouteInfo route =
+ (android.media.MediaRouter.RouteInfo)routeObj;
+
+ int routeTypes = route.getSupportedTypes();
+ if ((routeTypes & ROUTE_TYPE_USER) == 0) {
+ // Handle non-user routes.
+ // On JB and JB MR1, the selectRoute() API only supports programmatically
+ // selecting user routes. So instead we rely on the hidden selectRouteInt()
+ // method on these versions of the platform.
+ // This limitation was removed in JB MR2.
+ if (mSelectRouteIntMethod != null) {
+ try {
+ mSelectRouteIntMethod.invoke(router, types, route);
+ return; // success!
+ } catch (IllegalAccessException ex) {
+ Log.w(TAG, "Cannot programmatically select non-user route. "
+ + "Media routing may not work.", ex);
+ } catch (InvocationTargetException ex) {
+ Log.w(TAG, "Cannot programmatically select non-user route. "
+ + "Media routing may not work.", ex);
+ }
+ } else {
+ Log.w(TAG, "Cannot programmatically select non-user route "
+ + "because the platform is missing the selectRouteInt() "
+ + "method. Media routing may not work.");
+ }
+ }
+
+ // Default handling.
+ router.selectRoute(types, route);
+ }
+ }
+
+ /**
+ * Workaround the fact that the getDefaultRoute() method does not exist in JB and JB MR1.
+ * Do not use on JB MR2 and above.
+ */
+ public static final class GetDefaultRouteWorkaround {
+ private Method mGetSystemAudioRouteMethod;
+
+ public GetDefaultRouteWorkaround() {
+ if (Build.VERSION.SDK_INT < 16 || Build.VERSION.SDK_INT > 17) {
+ throw new UnsupportedOperationException();
+ }
+ try {
+ mGetSystemAudioRouteMethod =
+ android.media.MediaRouter.class.getMethod("getSystemAudioRoute");
+ } catch (NoSuchMethodException ex) {
+ }
+ }
+
+ public Object getDefaultRoute(Object routerObj) {
+ android.media.MediaRouter router = (android.media.MediaRouter)routerObj;
+
+ if (mGetSystemAudioRouteMethod != null) {
+ try {
+ return mGetSystemAudioRouteMethod.invoke(router);
+ } catch (IllegalAccessException ex) {
+ } catch (InvocationTargetException ex) {
+ }
+ }
+
+ // Could not find the method or it does not work.
+ // Return the first route and hope for the best.
+ return router.getRouteAt(0);
+ }
+ }
+
+ static class CallbackProxy<T extends Callback>
+ extends android.media.MediaRouter.Callback {
+ protected final T mCallback;
+
+ public CallbackProxy(T callback) {
+ mCallback = callback;
+ }
+
+ @Override
+ public void onRouteSelected(android.media.MediaRouter router,
+ int type, android.media.MediaRouter.RouteInfo route) {
+ mCallback.onRouteSelected(type, route);
+ }
+
+ @Override
+ public void onRouteUnselected(android.media.MediaRouter router,
+ int type, android.media.MediaRouter.RouteInfo route) {
+ mCallback.onRouteUnselected(type, route);
+ }
+
+ @Override
+ public void onRouteAdded(android.media.MediaRouter router,
+ android.media.MediaRouter.RouteInfo route) {
+ mCallback.onRouteAdded(route);
+ }
+
+ @Override
+ public void onRouteRemoved(android.media.MediaRouter router,
+ android.media.MediaRouter.RouteInfo route) {
+ mCallback.onRouteRemoved(route);
+ }
+
+ @Override
+ public void onRouteChanged(android.media.MediaRouter router,
+ android.media.MediaRouter.RouteInfo route) {
+ mCallback.onRouteChanged(route);
+ }
+
+ @Override
+ public void onRouteGrouped(android.media.MediaRouter router,
+ android.media.MediaRouter.RouteInfo route,
+ android.media.MediaRouter.RouteGroup group, int index) {
+ mCallback.onRouteGrouped(route, group, index);
+ }
+
+ @Override
+ public void onRouteUngrouped(android.media.MediaRouter router,
+ android.media.MediaRouter.RouteInfo route,
+ android.media.MediaRouter.RouteGroup group) {
+ mCallback.onRouteUngrouped(route, group);
+ }
+
+ @Override
+ public void onRouteVolumeChanged(android.media.MediaRouter router,
+ android.media.MediaRouter.RouteInfo route) {
+ mCallback.onRouteVolumeChanged(route);
+ }
+ }
+
+ static class VolumeCallbackProxy<T extends VolumeCallback>
+ extends android.media.MediaRouter.VolumeCallback {
+ protected final T mCallback;
+
+ public VolumeCallbackProxy(T callback) {
+ mCallback = callback;
+ }
+
+ @Override
+ public void onVolumeSetRequest(android.media.MediaRouter.RouteInfo route,
+ int volume) {
+ mCallback.onVolumeSetRequest(route, volume);
+ }
+
+ @Override
+ public void onVolumeUpdateRequest(android.media.MediaRouter.RouteInfo route,
+ int direction) {
+ mCallback.onVolumeUpdateRequest(route, direction);
+ }
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaControlIntent.java b/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaControlIntent.java
new file mode 100644
index 0000000..1d9e777
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaControlIntent.java
@@ -0,0 +1,1228 @@
+/*
+ * 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.support.mediarouter.media;
+
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.net.Uri;
+
+/**
+ * Constants for media control intents.
+ * <p>
+ * This class declares a set of standard media control intent categories and actions that
+ * applications can use to identify the capabilities of media routes and control them.
+ * </p>
+ *
+ * <h3>Media control intent categories</h3>
+ * <p>
+ * Media control intent categories specify means by which applications can
+ * send media to the destination of a media route. Categories are sometimes referred
+ * to as describing "types" or "kinds" of routes.
+ * </p><p>
+ * For example, if a route supports the {@link #CATEGORY_REMOTE_PLAYBACK remote playback category},
+ * then an application can ask it to play media remotely by sending a
+ * {@link #ACTION_PLAY play} or {@link #ACTION_ENQUEUE enqueue} intent with the Uri of the
+ * media content to play. Such a route may then be referred to as
+ * a "remote playback route" because it supports remote playback requests. It is common
+ * for a route to support multiple categories of requests at the same time, such as
+ * live audio and live video.
+ * </p><p>
+ * The following standard route categories are defined.
+ * </p><ul>
+ * <li>{@link #CATEGORY_LIVE_AUDIO Live audio}: The route supports streaming live audio
+ * from the device to the destination. Live audio routes include local speakers
+ * and Bluetooth headsets.
+ * <li>{@link #CATEGORY_LIVE_VIDEO Live video}: The route supports streaming live video
+ * from the device to the destination. Live video routes include local displays
+ * and wireless displays that support mirroring and
+ * {@link android.app.Presentation presentations}. Live video routes typically also
+ * support live audio capabilities.
+ * <li>{@link #CATEGORY_REMOTE_PLAYBACK Remote playback}: The route supports sending
+ * remote playback requests for media content to the destination. The content to be
+ * played is identified by a Uri and mime-type.
+ * </ul><p>
+ * Media route providers may define custom media control intent categories of their own in
+ * addition to the standard ones. Custom categories can be used to provide a variety
+ * of features to applications that recognize and know how to use them. For example,
+ * a media route provider might define a custom category to indicate that its routes
+ * support a special device-specific control interface in addition to other
+ * standard features.
+ * </p><p>
+ * Applications can determine which categories a route supports by using the
+ * {@link MediaRouter.RouteInfo#supportsControlCategory MediaRouter.RouteInfo.supportsControlCategory}
+ * or {@link MediaRouter.RouteInfo#getControlFilters MediaRouter.RouteInfo.getControlFilters}
+ * methods. Applications can also specify the types of routes that they want to use by
+ * creating {@link MediaRouteSelector media route selectors} that contain the desired
+ * categories and are used to filter routes in several parts of the media router API.
+ * </p>
+ *
+ * <h3>Media control intent actions</h3>
+ * <p>
+ * Media control intent actions specify particular functions that applications
+ * can ask the destination of a media route to perform. Media route control requests
+ * take the form of intents in a similar manner to other intents used to start activities
+ * or send broadcasts. The difference is that media control intents are directed to
+ * routes rather than activity or broadcast receiver components.
+ * </p><p>
+ * Each media route control intent specifies an action, a category and some number of parameters
+ * that are supplied as extras. Applications send media control requests to routes using the
+ * {@link MediaRouter.RouteInfo#sendControlRequest MediaRouter.RouteInfo.sendControlRequest}
+ * method and receive results via a callback.
+ * </p><p>
+ * All media control intent actions are associated with the media control intent categories
+ * that support them. Thus only remote playback routes may perform remote playback actions.
+ * The documentation of each action specifies the category to which the action belongs,
+ * the parameters it requires, and the results it returns.
+ * </p>
+ *
+ * <h3>Live audio and live video routes</h3>
+ * <p>
+ * {@link #CATEGORY_LIVE_AUDIO Live audio} and {@link #CATEGORY_LIVE_VIDEO live video}
+ * routes present media using standard system interfaces such as audio streams,
+ * {@link android.app.Presentation presentations} or display mirroring. These routes are
+ * the easiest to use because applications simply render content locally on the device
+ * and the system streams it to the route destination automatically.
+ * </p><p>
+ * In most cases, applications can stream content to live audio and live video routes in
+ * the same way they would play the content locally without any modification. However,
+ * applications may also be able to take advantage of more sophisticated features such
+ * as second-screen presentation APIs that are particular to these routes.
+ * </p>
+ *
+ * <h3>Remote playback routes</h3>
+ * <p>
+ * {@link #CATEGORY_REMOTE_PLAYBACK Remote playback} routes present media remotely
+ * by playing content from a Uri.
+ * These routes destinations take responsibility for fetching and rendering content
+ * on their own. Applications do not render the content themselves; instead, applications
+ * send control requests to initiate play, pause, resume, or stop media items and receive
+ * status updates as they change state.
+ * </p>
+ *
+ * <h4>Sessions</h4>
+ * <p>
+ * Each remote media playback action is conducted within the scope of a session.
+ * Sessions are used to prevent applications from accidentally interfering with one
+ * another because at most one session can be valid at a time.
+ * </p><p>
+ * A session can be created using the {@link #ACTION_START_SESSION start session action}
+ * and terminated using the {@link #ACTION_END_SESSION end session action} when the
+ * route provides explicit session management features.
+ * </p><p>
+ * Explicit session management was added in a later revision of the protocol so not
+ * all routes support it. If the route does not support explicit session management
+ * then implicit session management may still be used. Implicit session management
+ * relies on the use of the {@link #ACTION_PLAY play} and {@link #ACTION_ENQUEUE enqueue}
+ * actions which have the side-effect of creating a new session if none is provided
+ * as argument.
+ * </p><p>
+ * When a new session is created, the previous session is invalidated and any ongoing
+ * media playback is stopped before the requested action is performed. Any attempt
+ * to use an invalidated session will result in an error. (Protocol implementations
+ * are encouraged to aggressively discard information associated with invalidated sessions
+ * since it is no longer of use.)
+ * </p><p>
+ * Each session is identified by a unique session id that may be used to control
+ * the session using actions such as pause, resume, stop and end session.
+ * </p>
+ *
+ * <h4>Media items</h4>
+ * <p>
+ * Each successful {@link #ACTION_PLAY play} or {@link #ACTION_ENQUEUE enqueue} action
+ * returns a unique media item id that an application can use to monitor and control
+ * playback. The media item id may be passed to other actions such as
+ * {@link #ACTION_SEEK seek} or {@link #ACTION_GET_STATUS get status}. It will also appear
+ * as a parameter in status update broadcasts to identify the associated playback request.
+ * </p><p>
+ * Each media item is scoped to the session in which it was created. Therefore media item
+ * ids are only ever used together with session ids. Media item ids are meaningless
+ * on their own. When the session is invalidated, all of its media items are also
+ * invalidated.
+ * </p>
+ *
+ * <h4>The playback queue</h4>
+ * <p>
+ * Each session has its own playback queue that consists of the media items that
+ * are pending, playing, buffering or paused. Items are added to the queue when
+ * a playback request is issued. Items are removed from the queue when they are no
+ * longer eligible for playback (enter terminal states).
+ * </p><p>
+ * As described in the {@link MediaItemStatus} class, media items initially
+ * start in a pending state, transition to the playing (or buffering or paused) state
+ * during playback, and end in a finished, canceled, invalidated or error state.
+ * Once the current item enters a terminal state, playback proceeds on to the
+ * next item.
+ * </p><p>
+ * The application should determine whether the route supports queuing by checking
+ * whether the {@link #ACTION_ENQUEUE} action is declared in the route's control filter
+ * using {@link MediaRouter.RouteInfo#supportsControlRequest RouteInfo.supportsControlRequest}.
+ * </p><p>
+ * If the {@link #ACTION_ENQUEUE} action is supported by the route, then the route promises
+ * to allow at least two items (possibly more) to be enqueued at a time. Enqueued items play
+ * back to back one after the other as the previous item completes. Ideally there should
+ * be no audible pause between items for standard audio content types.
+ * </p><p>
+ * If the {@link #ACTION_ENQUEUE} action is not supported by the route, then the queue
+ * effectively contains at most one item at a time. Each play action has the effect of
+ * clearing the queue and resetting its state before the next item is played.
+ * </p>
+ *
+ * <h4>Impact of pause, resume, stop and play actions on the playback queue</h4>
+ * <p>
+ * The pause, resume and stop actions affect the session's whole queue. Pause causes
+ * the playback queue to be suspended no matter which item is currently playing.
+ * Resume reverses the effects of pause. Stop clears the queue and also resets
+ * the pause flag just like resume.
+ * </p><p>
+ * As described earlier, the play action has the effect of clearing the queue
+ * and completely resetting its state (like the stop action) then enqueuing a
+ * new media item to be played immediately. Play is therefore equivalent
+ * to stop followed by an action to enqueue an item.
+ * </p><p>
+ * The play action is also special in that it can be used to create new sessions.
+ * An application with simple needs may find that it only needs to use play
+ * (and occasionally stop) to control playback.
+ * </p>
+ *
+ * <h4>Resolving conflicts between applications</h4>
+ * <p>
+ * When an application has a valid session, it is essentially in control of remote playback
+ * on the route. No other application can view or modify the remote playback state
+ * of that application's session without knowing its id.
+ * </p><p>
+ * However, other applications can perform actions that have the effect of stopping
+ * playback and invalidating the current session. When this occurs, the former application
+ * will be informed that it has lost control by way of individual media item status
+ * update broadcasts that indicate that its queued media items have become
+ * {@link MediaItemStatus#PLAYBACK_STATE_INVALIDATED invalidated}. This broadcast
+ * implies that playback was terminated abnormally by an external cause.
+ * </p><p>
+ * Applications should handle conflicts conservatively to allow other applications to
+ * smoothly assume control over the route. When a conflict occurs, the currently playing
+ * application should release its session and allow the new application to use the
+ * route until such time as the user intervenes to take over the route again and begin
+ * a new playback session.
+ * </p>
+ *
+ * <h4>Basic actions</h4>
+ * <p>
+ * The following basic actions must be supported (all or nothing) by all remote
+ * playback routes. These actions form the basis of the remote playback protocol
+ * and are required in all implementations.
+ * </p><ul>
+ * <li>{@link #ACTION_PLAY Play}: Starts playing content specified by a given Uri
+ * and returns a new media item id to describe the request. Implicitly creates a new
+ * session if no session id was specified as a parameter.
+ * <li>{@link #ACTION_SEEK Seek}: Sets the content playback position of a specific media item.
+ * <li>{@link #ACTION_GET_STATUS Get status}: Gets the status of a media item
+ * including the item's current playback position and progress.
+ * <li>{@link #ACTION_PAUSE Pause}: Pauses playback of the queue.
+ * <li>{@link #ACTION_RESUME Resume}: Resumes playback of the queue.
+ * <li>{@link #ACTION_STOP Stop}: Stops playback, clears the queue, and resets the
+ * pause state.
+ * </ul>
+ *
+ * <h4>Queue actions</h4>
+ * <p>
+ * The following queue actions must be supported (all or nothing) by remote
+ * playback routes that offer optional queuing capabilities.
+ * </p><ul>
+ * <li>{@link #ACTION_ENQUEUE Enqueue}: Enqueues content specified by a given Uri
+ * and returns a new media item id to describe the request. Implicitly creates a new
+ * session if no session id was specified as a parameter.
+ * <li>{@link #ACTION_REMOVE Remove}: Removes a specified media item from the queue.
+ * </ul>
+ *
+ * <h4>Session actions</h4>
+ * <p>
+ * The following session actions must be supported (all or nothing) by remote
+ * playback routes that offer optional session management capabilities.
+ * </p><ul>
+ * <li>{@link #ACTION_START_SESSION Start session}: Starts a new session explicitly.
+ * <li>{@link #ACTION_GET_SESSION_STATUS Get session status}: Gets the status of a session.
+ * <li>{@link #ACTION_END_SESSION End session}: Ends a session explicitly.
+ * </ul>
+ *
+ * <h4>Implementation note</h4>
+ * <p>
+ * Implementations of the remote playback protocol must implement <em>all</em> of the
+ * documented actions, parameters and results. Note that the documentation is written from
+ * the perspective of a client of the protocol. In particular, whenever a parameter
+ * is described as being "optional", it is only from the perspective of the client.
+ * Compliant media route provider implementations of this protocol must support all
+ * of the features described herein.
+ * </p>
+ */
+public final class MediaControlIntent {
+ /* Route categories. */
+
+ /**
+ * Media control category: Live audio.
+ * <p>
+ * A route that supports live audio routing will allow the media audio stream
+ * to be sent to supported destinations. This can include internal speakers or
+ * audio jacks on the device itself, A2DP devices, and more.
+ * </p><p>
+ * When a live audio route is selected, audio routing is transparent to the application.
+ * All audio played on the media stream will be routed to the selected destination.
+ * </p><p>
+ * Refer to the class documentation for details about live audio routes.
+ * </p>
+ */
+ public static final String CATEGORY_LIVE_AUDIO = "android.media.intent.category.LIVE_AUDIO";
+
+ /**
+ * Media control category: Live video.
+ * <p>
+ * A route that supports live video routing will allow a mirrored version
+ * of the device's primary display or a customized
+ * {@link android.app.Presentation Presentation} to be sent to supported
+ * destinations.
+ * </p><p>
+ * When a live video route is selected, audio and video routing is transparent
+ * to the application. By default, audio and video is routed to the selected
+ * destination. For certain live video routes, the application may also use a
+ * {@link android.app.Presentation Presentation} to replace the mirrored view
+ * on the external display with different content.
+ * </p><p>
+ * Refer to the class documentation for details about live video routes.
+ * </p>
+ *
+ * @see MediaRouter.RouteInfo#getPresentationDisplay()
+ * @see android.app.Presentation
+ */
+ public static final String CATEGORY_LIVE_VIDEO = "android.media.intent.category.LIVE_VIDEO";
+
+ /**
+ * Media control category: Remote playback.
+ * <p>
+ * A route that supports remote playback routing will allow an application to send
+ * requests to play content remotely to supported destinations.
+ * </p><p>
+ * Remote playback routes destinations operate independently of the local device.
+ * When a remote playback route is selected, the application can control the content
+ * playing on the destination by sending media control actions to the route.
+ * The application may also receive status updates from the route regarding
+ * remote playback.
+ * </p><p>
+ * Refer to the class documentation for details about remote playback routes.
+ * </p>
+ *
+ * @see MediaRouter.RouteInfo#sendControlRequest
+ */
+ public static final String CATEGORY_REMOTE_PLAYBACK =
+ "android.media.intent.category.REMOTE_PLAYBACK";
+
+ /* Remote playback actions that affect individual items. */
+
+ /**
+ * Remote playback media control action: Play media item.
+ * <p>
+ * Used with routes that support {@link #CATEGORY_REMOTE_PLAYBACK remote playback}
+ * media control.
+ * </p><p>
+ * This action causes a remote playback route to start playing content with
+ * the {@link Uri} specified in the {@link Intent}'s {@link Intent#getData() data uri}.
+ * The action returns a media session id and media item id which can be used
+ * to control playback using other remote playback actions.
+ * </p><p>
+ * Once initiated, playback of the specified content will be managed independently
+ * by the destination. The application will receive status updates as the state
+ * of the media item changes.
+ * </p><p>
+ * If the data uri specifies an HTTP or HTTPS scheme, then the destination is
+ * responsible for following HTTP redirects to a reasonable depth of at least 3
+ * levels as might typically be handled by a web browser. If an HTTP error
+ * occurs, then the destination should send a {@link MediaItemStatus status update}
+ * back to the client indicating the {@link MediaItemStatus#PLAYBACK_STATE_ERROR error}
+ * {@link MediaItemStatus#getPlaybackState() playback state}.
+ * </p>
+ *
+ * <h3>One item at a time</h3>
+ * <p>
+ * Each successful play action <em>replaces</em> the previous play action.
+ * If an item is already playing, then it is canceled, the session's playback queue
+ * is cleared and the new item begins playing immediately (regardless of
+ * whether the previously playing item had been paused).
+ * </p><p>
+ * Play is therefore equivalent to {@link #ACTION_STOP stop} followed by an action
+ * to enqueue a new media item to be played immediately.
+ * </p>
+ *
+ * <h3>Sessions</h3>
+ * <p>
+ * This request has the effect of implicitly creating a media session whenever the
+ * application does not specify the {@link #EXTRA_SESSION_ID session id} parameter.
+ * Because there can only be at most one valid session at a time, creating a new session
+ * has the side-effect of invalidating any existing sessions and their media items,
+ * then handling the playback request with a new session.
+ * </p><p>
+ * If the application specifies an invalid session id, then an error is returned.
+ * When this happens, the application should assume that its session
+ * is no longer valid. To obtain a new session, the application may try again
+ * and omit the session id parameter. However, the application should
+ * only retry requests due to an explicit action performed by the user,
+ * such as the user clicking on a "play" button in the UI, since another
+ * application may be trying to take control of the route and the former
+ * application should try to stay out of its way.
+ * </p><p>
+ * For more information on sessions, queues and media items, please refer to the
+ * class documentation.
+ * </p>
+ *
+ * <h3>Request parameters</h3>
+ * <ul>
+ * <li>{@link #EXTRA_SESSION_ID} <em>(optional)</em>: Specifies the session id of the
+ * session to which the playback request belongs. If omitted, a new session
+ * is created implicitly.
+ * <li>{@link #EXTRA_ITEM_CONTENT_POSITION} <em>(optional)</em>: Specifies the initial
+ * content playback position as a long integer number of milliseconds from
+ * the beginning of the content.
+ * <li>{@link #EXTRA_ITEM_METADATA} <em>(optional)</em>: Specifies metadata associated
+ * with the content such as the title of a song.
+ * <li>{@link #EXTRA_ITEM_STATUS_UPDATE_RECEIVER} <em>(optional)</em>: Specifies a
+ * {@link PendingIntent} for a broadcast receiver that will receive status updates
+ * about the media item.
+ * </ul>
+ *
+ * <h3>Result data</h3>
+ * <ul>
+ * <li>{@link #EXTRA_SESSION_ID} <em>(always returned)</em>: Specifies the session id of the
+ * session that was affected by the request. This will be a new session in
+ * the case where no session id was supplied as a parameter.
+ * <li>{@link #EXTRA_SESSION_STATUS} <em>(optional, old implementations may
+ * omit this key)</em>: Specifies the status of the media session.
+ * <li>{@link #EXTRA_ITEM_ID} <em>(always returned)</em>: Specifies an opaque string identifier
+ * to use to refer to the media item in subsequent requests such as
+ * {@link #ACTION_GET_STATUS}.
+ * <li>{@link #EXTRA_ITEM_STATUS} <em>(always returned)</em>: Specifies the initial status of
+ * the new media item.
+ * </ul>
+ *
+ * <h3>Status updates</h3>
+ * <p>
+ * If the client supplies an
+ * {@link #EXTRA_ITEM_STATUS_UPDATE_RECEIVER item status update receiver}
+ * then the media route provider is responsible for sending status updates to the receiver
+ * when significant media item state changes occur such as when playback starts or
+ * stops. The receiver will not be invoked for content playback position changes.
+ * The application may retrieve the current playback position when necessary
+ * using the {@link #ACTION_GET_STATUS} request.
+ * </p><p>
+ * Refer to {@link MediaItemStatus} for details.
+ * </p>
+ *
+ * <h3>Errors</h3>
+ * <p>
+ * This action returns an error if a session id was provided but is unknown or
+ * no longer valid, if the item Uri or content type is not supported, or if
+ * any other arguments are invalid.
+ * </p><ul>
+ * <li>{@link #EXTRA_ERROR_CODE} <em>(optional)</em>: Specifies the cause of the error.
+ * </ul>
+ *
+ * <h3>Example</h3>
+ * <pre>
+ * MediaRouter mediaRouter = MediaRouter.getInstance(context);
+ * MediaRouter.RouteInfo route = mediaRouter.getSelectedRoute();
+ * Intent intent = new Intent(MediaControlIntent.ACTION_PLAY);
+ * intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
+ * intent.setDataAndType("http://example.com/videos/movie.mp4", "video/mp4");
+ * if (route.supportsControlRequest(intent)) {
+ * MediaRouter.ControlRequestCallback callback = new MediaRouter.ControlRequestCallback() {
+ * public void onResult(Bundle data) {
+ * // The request succeeded.
+ * // Playback may be controlled using the returned session and item id.
+ * String sessionId = data.getString(MediaControlIntent.EXTRA_SESSION_ID);
+ * String itemId = data.getString(MediaControlIntent.EXTRA_ITEM_ID);
+ * MediaItemStatus status = MediaItemStatus.fromBundle(data.getBundle(
+ * MediaControlIntent.EXTRA_ITEM_STATUS));
+ * // ...
+ * }
+ *
+ * public void onError(String message, Bundle data) {
+ * // An error occurred!
+ * }
+ * };
+ * route.sendControlRequest(intent, callback);
+ * }</pre>
+ *
+ * @see MediaRouter.RouteInfo#sendControlRequest
+ * @see #CATEGORY_REMOTE_PLAYBACK
+ * @see #ACTION_SEEK
+ * @see #ACTION_GET_STATUS
+ * @see #ACTION_PAUSE
+ * @see #ACTION_RESUME
+ * @see #ACTION_STOP
+ */
+ public static final String ACTION_PLAY = "android.media.intent.action.PLAY";
+
+ /**
+ * Remote playback media control action: Enqueue media item.
+ * <p>
+ * Used with routes that support {@link #CATEGORY_REMOTE_PLAYBACK remote playback}
+ * media control.
+ * </p><p>
+ * This action works just like {@link #ACTION_PLAY play} except that it does
+ * not clear the queue or reset the pause state when it enqueues the
+ * new media item into the session's playback queue. This action only
+ * enqueues a media item with no other side-effects on the queue.
+ * </p><p>
+ * If the queue is currently empty and then the item will play immediately
+ * (assuming the queue is not paused). Otherwise, the item will play
+ * after all earlier items in the queue have finished or been removed.
+ * </p><p>
+ * The enqueue action can be used to create new sessions just like play.
+ * Its parameters and results are also the same. Only the queuing behavior
+ * is different.
+ * </p>
+ *
+ * @see #ACTION_PLAY
+ */
+ public static final String ACTION_ENQUEUE = "android.media.intent.action.ENQUEUE";
+
+ /**
+ * Remote playback media control action: Seek media item to a new playback position.
+ * <p>
+ * Used with routes that support {@link #CATEGORY_REMOTE_PLAYBACK remote playback}
+ * media control.
+ * </p><p>
+ * This action causes a remote playback route to modify the current playback position
+ * of the specified media item.
+ * </p><p>
+ * This action only affects the playback position of the media item; not its playback state.
+ * If the playback queue is paused, then seeking sets the position but the item
+ * remains paused. Likewise if the item is playing, then seeking will cause playback
+ * to jump to the new position and continue playing from that point. If the item has
+ * not yet started playing, then the new playback position is remembered by the
+ * queue and used as the item's initial content position when playback eventually begins.
+ * </p><p>
+ * If successful, the media item's playback position is changed.
+ * </p>
+ *
+ * <h3>Request parameters</h3>
+ * <ul>
+ * <li>{@link #EXTRA_SESSION_ID} <em>(required)</em>: Specifies the session id of the session
+ * to which the media item belongs.
+ * <li>{@link #EXTRA_ITEM_ID} <em>(required)</em>: Specifies the media item id of
+ * the media item to seek.
+ * <li>{@link #EXTRA_ITEM_CONTENT_POSITION} <em>(required)</em>: Specifies the new
+ * content position for playback as a long integer number of milliseconds from
+ * the beginning of the content.
+ * </ul>
+ *
+ * <h3>Result data</h3>
+ * <ul>
+ * <li>{@link #EXTRA_SESSION_STATUS} <em>(optional, old implementations may
+ * omit this key)</em>: Specifies the status of the media session.
+ * <li>{@link #EXTRA_ITEM_STATUS} <em>(always returned)</em>: Specifies the new status of
+ * the media item.
+ * </ul>
+ *
+ * <h3>Errors</h3>
+ * <p>
+ * This action returns an error if the session id or media item id are unknown
+ * or no longer valid, if the content position is invalid, or if the media item
+ * is in a terminal state.
+ * </p><ul>
+ * <li>{@link #EXTRA_ERROR_CODE} <em>(optional)</em>: Specifies the cause of the error.
+ * </ul>
+ *
+ * @see MediaRouter.RouteInfo#sendControlRequest
+ * @see #CATEGORY_REMOTE_PLAYBACK
+ */
+ public static final String ACTION_SEEK = "android.media.intent.action.SEEK";
+
+ /**
+ * Remote playback media control action: Get media item playback status
+ * and progress information.
+ * <p>
+ * Used with routes that support {@link #CATEGORY_REMOTE_PLAYBACK remote playback}
+ * media control.
+ * </p><p>
+ * This action asks a remote playback route to provide updated playback status and progress
+ * information about the specified media item.
+ * </p>
+ *
+ * <h3>Request parameters</h3>
+ * <ul>
+ * <li>{@link #EXTRA_SESSION_ID} <em>(required)</em>: Specifies the session id of the session
+ * to which the media item belongs.
+ * <li>{@link #EXTRA_ITEM_ID} <em>(required)</em>: Specifies the media item id of
+ * the media item to query.
+ * </ul>
+ *
+ * <h3>Result data</h3>
+ * <ul>
+ * <li>{@link #EXTRA_SESSION_STATUS} <em>(optional, old implementations may
+ * omit this key)</em>: Specifies the status of the media session.
+ * <li>{@link #EXTRA_ITEM_STATUS} <em>(always returned)</em>: Specifies the current status of
+ * the media item.
+ * </ul>
+ *
+ * <h3>Errors</h3>
+ * <p>
+ * This action returns an error if the session id or media item id are unknown
+ * or no longer valid.
+ * </p><ul>
+ * <li>{@link #EXTRA_ERROR_CODE} <em>(optional)</em>: Specifies the cause of the error.
+ * </ul>
+ *
+ * @see MediaRouter.RouteInfo#sendControlRequest
+ * @see #CATEGORY_REMOTE_PLAYBACK
+ * @see #EXTRA_ITEM_STATUS_UPDATE_RECEIVER
+ */
+ public static final String ACTION_GET_STATUS = "android.media.intent.action.GET_STATUS";
+
+ /**
+ * Remote playback media control action: Remove media item from session's queue.
+ * <p>
+ * Used with routes that support {@link #CATEGORY_REMOTE_PLAYBACK remote playback}
+ * media control.
+ * </p><p>
+ * This action asks a remote playback route to remove the specified media item
+ * from the session's playback queue. If the current item is removed, then
+ * playback will proceed to the next media item (assuming the queue has not been
+ * paused).
+ * </p><p>
+ * This action does not affect the pause state of the queue. If the queue was paused
+ * then it remains paused (even if it is now empty) until a resume, stop or play
+ * action is issued that causes the pause state to be cleared.
+ * </p>
+ *
+ * <h3>Request parameters</h3>
+ * <ul>
+ * <li>{@link #EXTRA_SESSION_ID} <em>(required)</em>: Specifies the session id of the session
+ * to which the media item belongs.
+ * <li>{@link #EXTRA_ITEM_ID} <em>(required)</em>: Specifies the media item id of
+ * the media item to remove.
+ * </ul>
+ *
+ * <h3>Result data</h3>
+ * <ul>
+ * <li>{@link #EXTRA_SESSION_STATUS} <em>(optional, old implementations may
+ * omit this key)</em>: Specifies the status of the media session.
+ * <li>{@link #EXTRA_ITEM_STATUS} <em>(always returned)</em>: Specifies the new status of
+ * the media item.
+ * </ul>
+ *
+ * <h3>Errors</h3>
+ * <p>
+ * This action returns an error if the session id or media item id are unknown
+ * or no longer valid, or if the media item is in a terminal state (and therefore
+ * no longer in the queue).
+ * </p><ul>
+ * <li>{@link #EXTRA_ERROR_CODE} <em>(optional)</em>: Specifies the cause of the error.
+ * </ul>
+ *
+ * @see MediaRouter.RouteInfo#sendControlRequest
+ * @see #CATEGORY_REMOTE_PLAYBACK
+ */
+ public static final String ACTION_REMOVE = "android.media.intent.action.REMOVE";
+
+ /* Remote playback actions that affect the whole playback queue. */
+
+ /**
+ * Remote playback media control action: Pause media playback.
+ * <p>
+ * Used with routes that support {@link #CATEGORY_REMOTE_PLAYBACK remote playback}
+ * media control.
+ * </p><p>
+ * This action causes the playback queue of the specified session to be paused.
+ * </p>
+ *
+ * <h3>Request parameters</h3>
+ * <ul>
+ * <li>{@link #EXTRA_SESSION_ID} <em>(required)</em>: Specifies the session id of the session
+ * whose playback queue is to be paused.
+ * </ul>
+ *
+ * <h3>Result data</h3>
+ * <ul>
+ * <li>{@link #EXTRA_SESSION_STATUS} <em>(optional, old implementations may
+ * omit this key)</em>: Specifies the status of the media session.
+ * </ul>
+ *
+ * <h3>Errors</h3>
+ * <p>
+ * This action returns an error if the session id is unknown or no longer valid.
+ * </p><ul>
+ * <li>{@link #EXTRA_ERROR_CODE} <em>(optional)</em>: Specifies the cause of the error.
+ * </ul>
+ *
+ * @see MediaRouter.RouteInfo#sendControlRequest
+ * @see #CATEGORY_REMOTE_PLAYBACK
+ * @see #ACTION_RESUME
+ */
+ public static final String ACTION_PAUSE = "android.media.intent.action.PAUSE";
+
+ /**
+ * Remote playback media control action: Resume media playback (unpause).
+ * <p>
+ * Used with routes that support {@link #CATEGORY_REMOTE_PLAYBACK remote playback}
+ * media control.
+ * </p><p>
+ * This action causes the playback queue of the specified session to be resumed.
+ * Reverses the effects of {@link #ACTION_PAUSE}.
+ * </p>
+ *
+ * <h3>Request parameters</h3>
+ * <ul>
+ * <li>{@link #EXTRA_SESSION_ID} <em>(required)</em>: Specifies the session id of the session
+ * whose playback queue is to be resumed.
+ * </ul>
+ *
+ * <h3>Result data</h3>
+ * <ul>
+ * <li>{@link #EXTRA_SESSION_STATUS} <em>(optional, old implementations may
+ * omit this key)</em>: Specifies the status of the media session.
+ * </ul>
+ *
+ * <h3>Errors</h3>
+ * <p>
+ * This action returns an error if the session id is unknown or no longer valid.
+ * </p><ul>
+ * <li>{@link #EXTRA_ERROR_CODE} <em>(optional)</em>: Specifies the cause of the error.
+ * </ul>
+ *
+ * @see MediaRouter.RouteInfo#sendControlRequest
+ * @see #CATEGORY_REMOTE_PLAYBACK
+ * @see #ACTION_PAUSE
+ */
+ public static final String ACTION_RESUME = "android.media.intent.action.RESUME";
+
+ /**
+ * Remote playback media control action: Stop media playback (clear queue and unpause).
+ * <p>
+ * Used with routes that support {@link #CATEGORY_REMOTE_PLAYBACK remote playback}
+ * media control.
+ * </p><p>
+ * This action causes a remote playback route to stop playback, cancel and remove
+ * all media items from the session's media item queue and, reset the queue's
+ * pause state.
+ * </p><p>
+ * If successful, the status of all media items in the queue is set to
+ * {@link MediaItemStatus#PLAYBACK_STATE_CANCELED canceled} and a status update is sent
+ * to the appropriate status update receivers indicating the new status of each item.
+ * </p>
+ *
+ * <h3>Request parameters</h3>
+ * <ul>
+ * <li>{@link #EXTRA_SESSION_ID} <em>(required)</em>: Specifies the session id of
+ * the session whose playback queue is to be stopped (cleared and unpaused).
+ * </ul>
+ *
+ * <h3>Result data</h3>
+ * <ul>
+ * <li>{@link #EXTRA_SESSION_STATUS} <em>(optional, old implementations may
+ * omit this key)</em>: Specifies the status of the media session.
+ * </ul>
+ *
+ * <h3>Errors</h3>
+ * <p>
+ * This action returns an error if the session id is unknown or no longer valid.
+ * </p><ul>
+ * <li>{@link #EXTRA_ERROR_CODE} <em>(optional)</em>: Specifies the cause of the error.
+ * </ul>
+ *
+ * @see MediaRouter.RouteInfo#sendControlRequest
+ * @see #CATEGORY_REMOTE_PLAYBACK
+ */
+ public static final String ACTION_STOP = "android.media.intent.action.STOP";
+
+ /**
+ * Remote playback media control action: Start session.
+ * <p>
+ * Used with routes that support {@link #CATEGORY_REMOTE_PLAYBACK remote playback}
+ * media control.
+ * </p><p>
+ * This action causes a remote playback route to invalidate the current session
+ * and start a new session. The new session initially has an empty queue.
+ * </p><p>
+ * If successful, the status of all media items in the previous session's queue is set to
+ * {@link MediaItemStatus#PLAYBACK_STATE_INVALIDATED invalidated} and a status update
+ * is sent to the appropriate status update receivers indicating the new status
+ * of each item. The previous session becomes no longer valid and the new session
+ * takes control of the route.
+ * </p>
+ *
+ * <h3>Request parameters</h3>
+ * <ul>
+ * <li>{@link #EXTRA_SESSION_STATUS_UPDATE_RECEIVER} <em>(optional)</em>: Specifies a
+ * {@link PendingIntent} for a broadcast receiver that will receive status updates
+ * about the media session.
+ * <li>{@link #EXTRA_MESSAGE_RECEIVER} <em>(optional)</em>: Specifies a
+ * {@link PendingIntent} for a broadcast receiver that will receive messages from
+ * the media session.
+ * </ul>
+ *
+ * <h3>Result data</h3>
+ * <ul>
+ * <li>{@link #EXTRA_SESSION_ID} <em>(always returned)</em>: Specifies the session id of the
+ * session that was started by the request. This will always be a brand new session
+ * distinct from any other previously created sessions.
+ * <li>{@link #EXTRA_SESSION_STATUS} <em>(always returned)</em>: Specifies the
+ * status of the media session.
+ * </ul>
+ *
+ * <h3>Status updates</h3>
+ * <p>
+ * If the client supplies a
+ * {@link #EXTRA_SESSION_STATUS_UPDATE_RECEIVER status update receiver}
+ * then the media route provider is responsible for sending status updates to the receiver
+ * when significant media session state changes occur such as when the session's
+ * queue is paused or resumed or when the session is terminated or invalidated.
+ * </p><p>
+ * Refer to {@link MediaSessionStatus} for details.
+ * </p>
+ *
+ * <h3>Custom messages</h3>
+ * <p>
+ * If the client supplies a {@link #EXTRA_MESSAGE_RECEIVER message receiver}
+ * then the media route provider is responsible for sending messages to the receiver
+ * when the session has any messages to send.
+ * </p><p>
+ * Refer to {@link #EXTRA_MESSAGE} for details.
+ * </p>
+ *
+ * <h3>Errors</h3>
+ * <p>
+ * This action returns an error if the session could not be created.
+ * </p><ul>
+ * <li>{@link #EXTRA_ERROR_CODE} <em>(optional)</em>: Specifies the cause of the error.
+ * </ul>
+ *
+ * @see MediaRouter.RouteInfo#sendControlRequest
+ * @see #CATEGORY_REMOTE_PLAYBACK
+ */
+ public static final String ACTION_START_SESSION = "android.media.intent.action.START_SESSION";
+
+ /**
+ * Remote playback media control action: Get media session status information.
+ * <p>
+ * Used with routes that support {@link #CATEGORY_REMOTE_PLAYBACK remote playback}
+ * media control.
+ * </p><p>
+ * This action asks a remote playback route to provide updated status information
+ * about the specified media session.
+ * </p>
+ *
+ * <h3>Request parameters</h3>
+ * <ul>
+ * <li>{@link #EXTRA_SESSION_ID} <em>(required)</em>: Specifies the session id of the
+ * session whose status is to be retrieved.
+ * </ul>
+ *
+ * <h3>Result data</h3>
+ * <ul>
+ * <li>{@link #EXTRA_SESSION_STATUS} <em>(always returned)</em>: Specifies the
+ * current status of the media session.
+ * </ul>
+ *
+ * <h3>Errors</h3>
+ * <p>
+ * This action returns an error if the session id is unknown or no longer valid.
+ * </p><ul>
+ * <li>{@link #EXTRA_ERROR_CODE} <em>(optional)</em>: Specifies the cause of the error.
+ * </ul>
+ *
+ * @see MediaRouter.RouteInfo#sendControlRequest
+ * @see #CATEGORY_REMOTE_PLAYBACK
+ * @see #EXTRA_SESSION_STATUS_UPDATE_RECEIVER
+ */
+ public static final String ACTION_GET_SESSION_STATUS =
+ "android.media.intent.action.GET_SESSION_STATUS";
+
+ /**
+ * Remote playback media control action: End session.
+ * <p>
+ * Used with routes that support {@link #CATEGORY_REMOTE_PLAYBACK remote playback}
+ * media control.
+ * </p><p>
+ * This action causes a remote playback route to end the specified session.
+ * The session becomes no longer valid and the route ceases to be under control
+ * of the session.
+ * </p><p>
+ * If successful, the status of the session is set to
+ * {@link MediaSessionStatus#SESSION_STATE_ENDED} and a status update is sent to
+ * the session's status update receiver.
+ * </p><p>
+ * Additionally, the status of all media items in the queue is set to
+ * {@link MediaItemStatus#PLAYBACK_STATE_CANCELED canceled} and a status update is sent
+ * to the appropriate status update receivers indicating the new status of each item.
+ * </p>
+ *
+ * <h3>Request parameters</h3>
+ * <ul>
+ * <li>{@link #EXTRA_SESSION_ID} <em>(required)</em>: Specifies the session id of
+ * the session to end.
+ * </ul>
+ *
+ * <h3>Result data</h3>
+ * <ul>
+ * <li>{@link #EXTRA_SESSION_STATUS} <em>(always returned)</em>: Specifies the
+ * status of the media session.
+ * </ul>
+ *
+ * <h3>Errors</h3>
+ * <p>
+ * This action returns an error if the session id is unknown or no longer valid.
+ * In other words, it is an error to attempt to end a session other than the
+ * current session.
+ * </p><ul>
+ * <li>{@link #EXTRA_ERROR_CODE} <em>(optional)</em>: Specifies the cause of the error.
+ * </ul>
+ *
+ * @see MediaRouter.RouteInfo#sendControlRequest
+ * @see #CATEGORY_REMOTE_PLAYBACK
+ */
+ public static final String ACTION_END_SESSION = "android.media.intent.action.END_SESSION";
+
+ /**
+ * Custom media control action: Send {@link #EXTRA_MESSAGE}.
+ * <p>
+ * This action asks a route to handle a message described by EXTRA_MESSAGE.
+ * </p>
+ *
+ * <h3>Request parameters</h3>
+ * <ul>
+ * <li>{@link #EXTRA_SESSION_ID} <em>(required)</em>: Specifies the session id of the session
+ * to which will handle this message.
+ * <li>{@link #EXTRA_MESSAGE} <em>(required)</em>: Specifies the message to send.
+ * </ul>
+ *
+ * <h3>Result data</h3>
+ * Any messages defined by each media route provider.
+ *
+ * <h3>Errors</h3>
+ * Any error messages defined by each media route provider.
+ *
+ * @see MediaRouter.RouteInfo#sendControlRequest
+ */
+ public static final String ACTION_SEND_MESSAGE = "android.media.intent.action.SEND_MESSAGE";
+
+ /* Extras and related constants. */
+
+ /**
+ * Bundle extra: Media session id.
+ * <p>
+ * An opaque unique identifier that identifies the remote playback media session.
+ * </p><p>
+ * Used with various actions to specify the id of the media session to be controlled.
+ * </p><p>
+ * Included in broadcast intents sent to
+ * {@link #EXTRA_ITEM_STATUS_UPDATE_RECEIVER item status update receivers} to identify
+ * the session to which the item in question belongs.
+ * </p><p>
+ * Included in broadcast intents sent to
+ * {@link #EXTRA_SESSION_STATUS_UPDATE_RECEIVER session status update receivers} to identify
+ * the session.
+ * </p><p>
+ * The value is a unique string value generated by the media route provider
+ * to represent one particular media session.
+ * </p>
+ *
+ * @see #ACTION_PLAY
+ * @see #ACTION_SEEK
+ * @see #ACTION_GET_STATUS
+ * @see #ACTION_PAUSE
+ * @see #ACTION_RESUME
+ * @see #ACTION_STOP
+ * @see #ACTION_START_SESSION
+ * @see #ACTION_GET_SESSION_STATUS
+ * @see #ACTION_END_SESSION
+ */
+ public static final String EXTRA_SESSION_ID =
+ "android.media.intent.extra.SESSION_ID";
+
+ /**
+ * Bundle extra: Media session status.
+ * <p>
+ * Returned as a result from media session actions such as {@link #ACTION_START_SESSION},
+ * {@link #ACTION_PAUSE}, and {@link #ACTION_GET_SESSION_STATUS}
+ * to describe the status of the specified media session.
+ * </p><p>
+ * Included in broadcast intents sent to
+ * {@link #EXTRA_SESSION_STATUS_UPDATE_RECEIVER session status update receivers} to provide
+ * updated status information.
+ * </p><p>
+ * The value is a {@link android.os.Bundle} of data that can be converted into
+ * a {@link MediaSessionStatus} object using
+ * {@link MediaSessionStatus#fromBundle MediaSessionStatus.fromBundle}.
+ * </p>
+ *
+ * @see #ACTION_PLAY
+ * @see #ACTION_SEEK
+ * @see #ACTION_GET_STATUS
+ * @see #ACTION_PAUSE
+ * @see #ACTION_RESUME
+ * @see #ACTION_STOP
+ * @see #ACTION_START_SESSION
+ * @see #ACTION_GET_SESSION_STATUS
+ * @see #ACTION_END_SESSION
+ */
+ public static final String EXTRA_SESSION_STATUS =
+ "android.media.intent.extra.SESSION_STATUS";
+
+ /**
+ * Bundle extra: Media session status update receiver.
+ * <p>
+ * Used with {@link #ACTION_START_SESSION} to specify a {@link PendingIntent} for a
+ * broadcast receiver that will receive status updates about the media session.
+ * </p><p>
+ * Whenever the status of the media session changes, the media route provider will
+ * send a broadcast to the pending intent with extras that identify the session
+ * id and its updated status.
+ * </p><p>
+ * The value is a {@link PendingIntent}.
+ * </p>
+ *
+ * <h3>Broadcast extras</h3>
+ * <ul>
+ * <li>{@link #EXTRA_SESSION_ID} <em>(required)</em>: Specifies the session id of
+ * the session.
+ * <li>{@link #EXTRA_SESSION_STATUS} <em>(required)</em>: Specifies the status of the
+ * session as a bundle that can be decoded into a {@link MediaSessionStatus} object.
+ * </ul>
+ *
+ * @see #ACTION_START_SESSION
+ */
+ public static final String EXTRA_SESSION_STATUS_UPDATE_RECEIVER =
+ "android.media.intent.extra.SESSION_STATUS_UPDATE_RECEIVER";
+
+ /**
+ * Bundle extra: Media message receiver.
+ * <p>
+ * Used with {@link #ACTION_START_SESSION} to specify a {@link PendingIntent} for a
+ * broadcast receiver that will receive messages from the media session.
+ * </p><p>
+ * When the media session has a message to send, the media route provider will
+ * send a broadcast to the pending intent with extras that identify the session
+ * id and its message.
+ * </p><p>
+ * The value is a {@link PendingIntent}.
+ * </p>
+ *
+ * <h3>Broadcast extras</h3>
+ * <ul>
+ * <li>{@link #EXTRA_SESSION_ID} <em>(required)</em>: Specifies the session id of
+ * the session.
+ * <li>{@link #EXTRA_MESSAGE} <em>(required)</em>: Specifies the message from
+ * the session as a bundle object.
+ * </ul>
+ *
+ * @see #ACTION_START_SESSION
+ */
+ public static final String EXTRA_MESSAGE_RECEIVER =
+ "android.media.intent.extra.MESSAGE_RECEIVER";
+
+ /**
+ * Bundle extra: Media item id.
+ * <p>
+ * An opaque unique identifier returned as a result from {@link #ACTION_PLAY} or
+ * {@link #ACTION_ENQUEUE} that represents the media item that was created by the
+ * playback request.
+ * </p><p>
+ * Used with various actions to specify the id of the media item to be controlled.
+ * </p><p>
+ * Included in broadcast intents sent to
+ * {@link #EXTRA_ITEM_STATUS_UPDATE_RECEIVER status update receivers} to identify
+ * the item in question.
+ * </p><p>
+ * The value is a unique string value generated by the media route provider
+ * to represent one particular media item.
+ * </p>
+ *
+ * @see #ACTION_PLAY
+ * @see #ACTION_ENQUEUE
+ * @see #ACTION_SEEK
+ * @see #ACTION_GET_STATUS
+ */
+ public static final String EXTRA_ITEM_ID =
+ "android.media.intent.extra.ITEM_ID";
+
+ /**
+ * Bundle extra: Media item status.
+ * <p>
+ * Returned as a result from media item actions such as {@link #ACTION_PLAY},
+ * {@link #ACTION_ENQUEUE}, {@link #ACTION_SEEK}, and {@link #ACTION_GET_STATUS}
+ * to describe the status of the specified media item.
+ * </p><p>
+ * Included in broadcast intents sent to
+ * {@link #EXTRA_ITEM_STATUS_UPDATE_RECEIVER item status update receivers} to provide
+ * updated status information.
+ * </p><p>
+ * The value is a {@link android.os.Bundle} of data that can be converted into
+ * a {@link MediaItemStatus} object using
+ * {@link MediaItemStatus#fromBundle MediaItemStatus.fromBundle}.
+ * </p>
+ *
+ * @see #ACTION_PLAY
+ * @see #ACTION_ENQUEUE
+ * @see #ACTION_SEEK
+ * @see #ACTION_GET_STATUS
+ */
+ public static final String EXTRA_ITEM_STATUS =
+ "android.media.intent.extra.ITEM_STATUS";
+
+ /**
+ * Long extra: Media item content position.
+ * <p>
+ * Used with {@link #ACTION_PLAY} or {@link #ACTION_ENQUEUE} to specify the
+ * starting playback position.
+ * </p><p>
+ * Used with {@link #ACTION_SEEK} to set a new playback position.
+ * </p><p>
+ * The value is a long integer number of milliseconds from the beginning of the content.
+ * <p>
+ *
+ * @see #ACTION_PLAY
+ * @see #ACTION_ENQUEUE
+ * @see #ACTION_SEEK
+ */
+ public static final String EXTRA_ITEM_CONTENT_POSITION =
+ "android.media.intent.extra.ITEM_POSITION";
+
+ /**
+ * Bundle extra: Media item metadata.
+ * <p>
+ * Used with {@link #ACTION_PLAY} or {@link #ACTION_ENQUEUE} to specify metadata
+ * associated with the content of a media item.
+ * </p><p>
+ * The value is a {@link android.os.Bundle} of metadata key-value pairs as defined
+ * in {@link MediaItemMetadata}.
+ * </p>
+ *
+ * @see #ACTION_PLAY
+ * @see #ACTION_ENQUEUE
+ */
+ public static final String EXTRA_ITEM_METADATA =
+ "android.media.intent.extra.ITEM_METADATA";
+
+ /**
+ * Bundle extra: HTTP request headers.
+ * <p>
+ * Used with {@link #ACTION_PLAY} or {@link #ACTION_ENQUEUE} to specify HTTP request
+ * headers to be included when fetching to the content indicated by the media
+ * item's data Uri.
+ * </p><p>
+ * This extra may be used to provide authentication tokens and other
+ * parameters to the server separately from the media item's data Uri.
+ * </p><p>
+ * The value is a {@link android.os.Bundle} of string based key-value pairs
+ * that describe the HTTP request headers.
+ * </p>
+ *
+ * @see #ACTION_PLAY
+ * @see #ACTION_ENQUEUE
+ */
+ public static final String EXTRA_ITEM_HTTP_HEADERS =
+ "android.media.intent.extra.HTTP_HEADERS";
+
+ /**
+ * Bundle extra: Media item status update receiver.
+ * <p>
+ * Used with {@link #ACTION_PLAY} or {@link #ACTION_ENQUEUE} to specify
+ * a {@link PendingIntent} for a
+ * broadcast receiver that will receive status updates about a particular
+ * media item.
+ * </p><p>
+ * Whenever the status of the media item changes, the media route provider will
+ * send a broadcast to the pending intent with extras that identify the session
+ * to which the item belongs, the session status, the item's id
+ * and the item's updated status.
+ * </p><p>
+ * The same pending intent and broadcast receiver may be shared by any number of
+ * media items since the broadcast intent includes the media session id
+ * and media item id.
+ * </p><p>
+ * The value is a {@link PendingIntent}.
+ * </p>
+ *
+ * <h3>Broadcast extras</h3>
+ * <ul>
+ * <li>{@link #EXTRA_SESSION_ID} <em>(required)</em>: Specifies the session id of
+ * the session to which the item in question belongs.
+ * <li>{@link #EXTRA_SESSION_STATUS} <em>(optional, old implementations may
+ * omit this key)</em>: Specifies the status of the media session.
+ * <li>{@link #EXTRA_ITEM_ID} <em>(required)</em>: Specifies the media item id of the
+ * media item in question.
+ * <li>{@link #EXTRA_ITEM_STATUS} <em>(required)</em>: Specifies the status of the
+ * item as a bundle that can be decoded into a {@link MediaItemStatus} object.
+ * </ul>
+ *
+ * @see #ACTION_PLAY
+ * @see #ACTION_ENQUEUE
+ */
+ public static final String EXTRA_ITEM_STATUS_UPDATE_RECEIVER =
+ "android.media.intent.extra.ITEM_STATUS_UPDATE_RECEIVER";
+
+ /**
+ * Bundle extra: Message.
+ * <p>
+ * Used with {@link #ACTION_SEND_MESSAGE}, and included in broadcast intents sent to
+ * {@link #EXTRA_MESSAGE_RECEIVER message receivers} to describe a message between a
+ * session and a media route provider.
+ * </p><p>
+ * The value is a {@link android.os.Bundle}.
+ * </p>
+ */
+ public static final String EXTRA_MESSAGE = "android.media.intent.extra.MESSAGE";
+
+ /**
+ * Integer extra: Error code.
+ * <p>
+ * Used with all media control requests to describe the cause of an error.
+ * This extra may be omitted when the error is unknown.
+ * </p><p>
+ * The value is one of: {@link #ERROR_UNKNOWN}, {@link #ERROR_UNSUPPORTED_OPERATION},
+ * {@link #ERROR_INVALID_SESSION_ID}, {@link #ERROR_INVALID_ITEM_ID}.
+ * </p>
+ */
+ public static final String EXTRA_ERROR_CODE = "android.media.intent.extra.ERROR_CODE";
+
+ /**
+ * Error code: An unknown error occurred.
+ *
+ * @see #EXTRA_ERROR_CODE
+ */
+ public static final int ERROR_UNKNOWN = 0;
+
+ /**
+ * Error code: The operation is not supported.
+ *
+ * @see #EXTRA_ERROR_CODE
+ */
+ public static final int ERROR_UNSUPPORTED_OPERATION = 1;
+
+ /**
+ * Error code: The session id specified in the request was invalid.
+ *
+ * @see #EXTRA_ERROR_CODE
+ */
+ public static final int ERROR_INVALID_SESSION_ID = 2;
+
+ /**
+ * Error code: The item id specified in the request was invalid.
+ *
+ * @see #EXTRA_ERROR_CODE
+ */
+ public static final int ERROR_INVALID_ITEM_ID = 3;
+
+ private MediaControlIntent() {
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaItemMetadata.java b/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaItemMetadata.java
new file mode 100644
index 0000000..d52ddb6
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaItemMetadata.java
@@ -0,0 +1,138 @@
+/*
+ * 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.support.mediarouter.media;
+
+import android.os.Bundle;
+
+/**
+ * Constants for specifying metadata about a media item as a {@link Bundle}.
+ * <p>
+ * This class is part of the remote playback protocol described by the
+ * {@link MediaControlIntent MediaControlIntent} class.
+ * </p><p>
+ * Media item metadata is described as a bundle of key/value pairs as defined
+ * in this class. The documentation specifies the type of value associated
+ * with each key.
+ * </p><p>
+ * An application may specify additional custom metadata keys but there is no guarantee
+ * that they will be recognized by the destination.
+ * </p>
+ */
+public final class MediaItemMetadata {
+ /*
+ * Note: MediaMetadataRetriever also defines a collection of metadata keys that can be
+ * retrieved from a content stream although the representation is somewhat different here
+ * since we are sending the data to a remote endpoint.
+ */
+
+ private MediaItemMetadata() {
+ }
+
+ /**
+ * String key: Album artist name.
+ * <p>
+ * The value is a string suitable for display.
+ * </p>
+ */
+ public static final String KEY_ALBUM_ARTIST = "android.media.metadata.ALBUM_ARTIST";
+
+ /**
+ * String key: Album title.
+ * <p>
+ * The value is a string suitable for display.
+ * </p>
+ */
+ public static final String KEY_ALBUM_TITLE = "android.media.metadata.ALBUM_TITLE";
+
+ /**
+ * String key: Artwork Uri.
+ * <p>
+ * The value is a string URI for an image file associated with the media item,
+ * such as album or cover art.
+ * </p>
+ */
+ public static final String KEY_ARTWORK_URI = "android.media.metadata.ARTWORK_URI";
+
+ /**
+ * String key: Artist name.
+ * <p>
+ * The value is a string suitable for display.
+ * </p>
+ */
+ public static final String KEY_ARTIST = "android.media.metadata.ARTIST";
+
+ /**
+ * String key: Author name.
+ * <p>
+ * The value is a string suitable for display.
+ * </p>
+ */
+ public static final String KEY_AUTHOR = "android.media.metadata.AUTHOR";
+
+ /**
+ * String key: Composer name.
+ * <p>
+ * The value is a string suitable for display.
+ * </p>
+ */
+ public static final String KEY_COMPOSER = "android.media.metadata.COMPOSER";
+
+ /**
+ * String key: Track title.
+ * <p>
+ * The value is a string suitable for display.
+ * </p>
+ */
+ public static final String KEY_TITLE = "android.media.metadata.TITLE";
+
+ /**
+ * Integer key: Year of publication.
+ * <p>
+ * The value is an integer year number.
+ * </p>
+ */
+ public static final String KEY_YEAR = "android.media.metadata.YEAR";
+
+ /**
+ * Integer key: Track number (such as a track on a CD).
+ * <p>
+ * The value is a one-based integer track number.
+ * </p>
+ */
+ public static final String KEY_TRACK_NUMBER = "android.media.metadata.TRACK_NUMBER";
+
+ /**
+ * Integer key: Disc number within a collection.
+ * <p>
+ * The value is a one-based integer disc number.
+ * </p>
+ */
+ public static final String KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER";
+
+ /**
+ * Long key: Item playback duration in milliseconds.
+ * <p>
+ * The value is a <code>long</code> number of milliseconds.
+ * </p><p>
+ * The duration metadata is only a hint to enable a remote media player to
+ * guess the duration of the content before it actually opens the media stream.
+ * The remote media player should still determine the actual content duration from
+ * the media stream itself independent of the value that may be specified by this key.
+ * </p>
+ */
+ public static final String KEY_DURATION = "android.media.metadata.DURATION";
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaItemStatus.java b/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaItemStatus.java
new file mode 100644
index 0000000..90ea2d5
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaItemStatus.java
@@ -0,0 +1,392 @@
+/*
+ * 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.support.mediarouter.media;
+
+import android.app.PendingIntent;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.support.v4.util.TimeUtils;
+
+/**
+ * Describes the playback status of a media item.
+ * <p>
+ * This class is part of the remote playback protocol described by the
+ * {@link MediaControlIntent MediaControlIntent} class.
+ * </p><p>
+ * As a media item is played, it transitions through a sequence of states including:
+ * {@link #PLAYBACK_STATE_PENDING pending}, {@link #PLAYBACK_STATE_BUFFERING buffering},
+ * {@link #PLAYBACK_STATE_PLAYING playing}, {@link #PLAYBACK_STATE_PAUSED paused},
+ * {@link #PLAYBACK_STATE_FINISHED finished}, {@link #PLAYBACK_STATE_CANCELED canceled},
+ * {@link #PLAYBACK_STATE_INVALIDATED invalidated}, and
+ * {@link #PLAYBACK_STATE_ERROR error}. Refer to the documentation of each state
+ * for an explanation of its meaning.
+ * </p><p>
+ * While the item is playing, the playback status may also include progress information
+ * about the {@link #getContentPosition content position} and
+ * {@link #getContentDuration content duration} although not all route destinations
+ * will report it.
+ * </p><p>
+ * To monitor playback status, the application should supply a {@link PendingIntent} to use as the
+ * {@link MediaControlIntent#EXTRA_ITEM_STATUS_UPDATE_RECEIVER item status update receiver}
+ * for a given {@link MediaControlIntent#ACTION_PLAY playback request}. Note that
+ * the status update receiver will only be invoked for major status changes such as a
+ * transition from playing to finished.
+ * </p><p class="note">
+ * The status update receiver will not be invoked for minor progress updates such as
+ * changes to playback position or duration. If the application wants to monitor
+ * playback progress, then it must use the
+ * {@link MediaControlIntent#ACTION_GET_STATUS get status request} to poll for changes
+ * periodically and estimate the playback position while playing. Note that there may
+ * be a significant power impact to polling so the application is advised only
+ * to poll when the screen is on and never more than about once every 5 seconds or so.
+ * </p><p>
+ * This object is immutable once created using a {@link Builder} instance.
+ * </p>
+ */
+public final class MediaItemStatus {
+ static final String KEY_TIMESTAMP = "timestamp";
+ static final String KEY_PLAYBACK_STATE = "playbackState";
+ static final String KEY_CONTENT_POSITION = "contentPosition";
+ static final String KEY_CONTENT_DURATION = "contentDuration";
+ static final String KEY_EXTRAS = "extras";
+
+ final Bundle mBundle;
+
+ /**
+ * Playback state: Pending.
+ * <p>
+ * Indicates that the media item has not yet started playback but will be played eventually.
+ * </p>
+ */
+ public static final int PLAYBACK_STATE_PENDING = 0;
+
+ /**
+ * Playback state: Playing.
+ * <p>
+ * Indicates that the media item is currently playing.
+ * </p>
+ */
+ public static final int PLAYBACK_STATE_PLAYING = 1;
+
+ /**
+ * Playback state: Paused.
+ * <p>
+ * Indicates that playback of the media item has been paused. Playback can be
+ * resumed using the {@link MediaControlIntent#ACTION_RESUME resume} action.
+ * </p>
+ */
+ public static final int PLAYBACK_STATE_PAUSED = 2;
+
+ /**
+ * Playback state: Buffering or seeking to a new position.
+ * <p>
+ * Indicates that the media item has been temporarily interrupted
+ * to fetch more content. Playback will continue automatically
+ * when enough content has been buffered.
+ * </p>
+ */
+ public static final int PLAYBACK_STATE_BUFFERING = 3;
+
+ /**
+ * Playback state: Finished.
+ * <p>
+ * Indicates that the media item played to the end of the content and finished normally.
+ * </p><p>
+ * A finished media item cannot be resumed. To play the content again, the application
+ * must send a new {@link MediaControlIntent#ACTION_PLAY play} or
+ * {@link MediaControlIntent#ACTION_ENQUEUE enqueue} action.
+ * </p>
+ */
+ public static final int PLAYBACK_STATE_FINISHED = 4;
+
+ /**
+ * Playback state: Canceled.
+ * <p>
+ * Indicates that the media item was explicitly removed from the queue by the
+ * application. Items may be canceled and removed from the queue using
+ * the {@link MediaControlIntent#ACTION_REMOVE remove} or
+ * {@link MediaControlIntent#ACTION_STOP stop} action or by issuing
+ * another {@link MediaControlIntent#ACTION_PLAY play} action that has the
+ * side-effect of clearing the queue.
+ * </p><p>
+ * A canceled media item cannot be resumed. To play the content again, the
+ * application must send a new {@link MediaControlIntent#ACTION_PLAY play} or
+ * {@link MediaControlIntent#ACTION_ENQUEUE enqueue} action.
+ * </p>
+ */
+ public static final int PLAYBACK_STATE_CANCELED = 5;
+
+ /**
+ * Playback state: Invalidated.
+ * <p>
+ * Indicates that the media item was invalidated permanently and involuntarily.
+ * This state is used to indicate that the media item was invalidated and removed
+ * from the queue because the session to which it belongs was invalidated
+ * (typically by another application taking control of the route).
+ * </p><p>
+ * When invalidation occurs, the application should generally wait for the user
+ * to perform an explicit action, such as clicking on a play button in the UI,
+ * before creating a new media session to avoid unnecessarily interrupting
+ * another application that may have just started using the route.
+ * </p><p>
+ * An invalidated media item cannot be resumed. To play the content again, the application
+ * must send a new {@link MediaControlIntent#ACTION_PLAY play} or
+ * {@link MediaControlIntent#ACTION_ENQUEUE enqueue} action.
+ * </p>
+ */
+ public static final int PLAYBACK_STATE_INVALIDATED = 6;
+
+ /**
+ * Playback state: Playback halted or aborted due to an error.
+ * <p>
+ * Examples of errors are no network connectivity when attempting to retrieve content
+ * from a server, or expired user credentials when trying to play subscription-based
+ * content.
+ * </p><p>
+ * A media item in the error state cannot be resumed. To play the content again,
+ * the application must send a new {@link MediaControlIntent#ACTION_PLAY play} or
+ * {@link MediaControlIntent#ACTION_ENQUEUE enqueue} action.
+ * </p>
+ */
+ public static final int PLAYBACK_STATE_ERROR = 7;
+
+ /**
+ * Integer extra: HTTP status code.
+ * <p>
+ * Specifies the HTTP status code that was encountered when the content
+ * was requested after all redirects were followed. This key only needs to
+ * specified when the content uri uses the HTTP or HTTPS scheme and an error
+ * occurred. This key may be omitted if the content was able to be played
+ * successfully; there is no need to report a 200 (OK) status code.
+ * </p><p>
+ * The value is an integer HTTP status code, such as 401 (Unauthorized),
+ * 404 (Not Found), or 500 (Server Error), or 0 if none.
+ * </p>
+ */
+ public static final String EXTRA_HTTP_STATUS_CODE =
+ "android.media.status.extra.HTTP_STATUS_CODE";
+
+ /**
+ * Bundle extra: HTTP response headers.
+ * <p>
+ * Specifies the HTTP response headers that were returned when the content was
+ * requested from the network. The headers may include additional information
+ * about the content or any errors conditions that were encountered while
+ * trying to fetch the content.
+ * </p><p>
+ * The value is a {@link android.os.Bundle} of string based key-value pairs
+ * that describe the HTTP response headers.
+ * </p>
+ */
+ public static final String EXTRA_HTTP_RESPONSE_HEADERS =
+ "android.media.status.extra.HTTP_RESPONSE_HEADERS";
+
+ MediaItemStatus(Bundle bundle) {
+ mBundle = bundle;
+ }
+
+ /**
+ * Gets the timestamp associated with the status information in
+ * milliseconds since boot in the {@link SystemClock#elapsedRealtime} time base.
+ *
+ * @return The status timestamp in the {@link SystemClock#elapsedRealtime()} time base.
+ */
+ public long getTimestamp() {
+ return mBundle.getLong(KEY_TIMESTAMP);
+ }
+
+ /**
+ * Gets the playback state of the media item.
+ *
+ * @return The playback state. One of {@link #PLAYBACK_STATE_PENDING},
+ * {@link #PLAYBACK_STATE_PLAYING}, {@link #PLAYBACK_STATE_PAUSED},
+ * {@link #PLAYBACK_STATE_BUFFERING}, {@link #PLAYBACK_STATE_FINISHED},
+ * {@link #PLAYBACK_STATE_CANCELED}, {@link #PLAYBACK_STATE_INVALIDATED},
+ * or {@link #PLAYBACK_STATE_ERROR}.
+ */
+ public int getPlaybackState() {
+ return mBundle.getInt(KEY_PLAYBACK_STATE, PLAYBACK_STATE_ERROR);
+ }
+
+ /**
+ * Gets the content playback position as a long integer number of milliseconds
+ * from the beginning of the content.
+ *
+ * @return The content playback position in milliseconds, or -1 if unknown.
+ */
+ public long getContentPosition() {
+ return mBundle.getLong(KEY_CONTENT_POSITION, -1);
+ }
+
+ /**
+ * Gets the total duration of the content to be played as a long integer number of
+ * milliseconds.
+ *
+ * @return The content duration in milliseconds, or -1 if unknown.
+ */
+ public long getContentDuration() {
+ return mBundle.getLong(KEY_CONTENT_DURATION, -1);
+ }
+
+ /**
+ * Gets a bundle of extras for this status object.
+ * The extras will be ignored by the media router but they may be used
+ * by applications.
+ */
+ public Bundle getExtras() {
+ return mBundle.getBundle(KEY_EXTRAS);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder();
+ result.append("MediaItemStatus{ ");
+ result.append("timestamp=");
+ TimeUtils.formatDuration(SystemClock.elapsedRealtime() - getTimestamp(), result);
+ result.append(" ms ago");
+ result.append(", playbackState=").append(playbackStateToString(getPlaybackState()));
+ result.append(", contentPosition=").append(getContentPosition());
+ result.append(", contentDuration=").append(getContentDuration());
+ result.append(", extras=").append(getExtras());
+ result.append(" }");
+ return result.toString();
+ }
+
+ private static String playbackStateToString(int playbackState) {
+ switch (playbackState) {
+ case PLAYBACK_STATE_PENDING:
+ return "pending";
+ case PLAYBACK_STATE_BUFFERING:
+ return "buffering";
+ case PLAYBACK_STATE_PLAYING:
+ return "playing";
+ case PLAYBACK_STATE_PAUSED:
+ return "paused";
+ case PLAYBACK_STATE_FINISHED:
+ return "finished";
+ case PLAYBACK_STATE_CANCELED:
+ return "canceled";
+ case PLAYBACK_STATE_INVALIDATED:
+ return "invalidated";
+ case PLAYBACK_STATE_ERROR:
+ return "error";
+ }
+ return Integer.toString(playbackState);
+ }
+
+ /**
+ * Converts this object to a bundle for serialization.
+ *
+ * @return The contents of the object represented as a bundle.
+ */
+ public Bundle asBundle() {
+ return mBundle;
+ }
+
+ /**
+ * Creates an instance from a bundle.
+ *
+ * @param bundle The bundle, or null if none.
+ * @return The new instance, or null if the bundle was null.
+ */
+ public static MediaItemStatus fromBundle(Bundle bundle) {
+ return bundle != null ? new MediaItemStatus(bundle) : null;
+ }
+
+ /**
+ * Builder for {@link MediaItemStatus media item status objects}.
+ */
+ public static final class Builder {
+ private final Bundle mBundle;
+
+ /**
+ * Creates a media item status builder using the current time as the
+ * reference timestamp.
+ *
+ * @param playbackState The item playback state.
+ */
+ public Builder(int playbackState) {
+ mBundle = new Bundle();
+ setTimestamp(SystemClock.elapsedRealtime());
+ setPlaybackState(playbackState);
+ }
+
+ /**
+ * Creates a media item status builder whose initial contents are
+ * copied from an existing status.
+ */
+ public Builder(MediaItemStatus status) {
+ if (status == null) {
+ throw new IllegalArgumentException("status must not be null");
+ }
+
+ mBundle = new Bundle(status.mBundle);
+ }
+
+ /**
+ * Sets the timestamp associated with the status information in
+ * milliseconds since boot in the {@link SystemClock#elapsedRealtime} time base.
+ */
+ public Builder setTimestamp(long elapsedRealtimeTimestamp) {
+ mBundle.putLong(KEY_TIMESTAMP, elapsedRealtimeTimestamp);
+ return this;
+ }
+
+ /**
+ * Sets the playback state of the media item.
+ */
+ public Builder setPlaybackState(int playbackState) {
+ mBundle.putInt(KEY_PLAYBACK_STATE, playbackState);
+ return this;
+ }
+
+ /**
+ * Sets the content playback position as a long integer number of milliseconds
+ * from the beginning of the content.
+ */
+ public Builder setContentPosition(long positionMilliseconds) {
+ mBundle.putLong(KEY_CONTENT_POSITION, positionMilliseconds);
+ return this;
+ }
+
+ /**
+ * Sets the total duration of the content to be played as a long integer number
+ * of milliseconds.
+ */
+ public Builder setContentDuration(long durationMilliseconds) {
+ mBundle.putLong(KEY_CONTENT_DURATION, durationMilliseconds);
+ return this;
+ }
+
+ /**
+ * Sets a bundle of extras for this status object.
+ * The extras will be ignored by the media router but they may be used
+ * by applications.
+ */
+ public Builder setExtras(Bundle extras) {
+ mBundle.putBundle(KEY_EXTRAS, extras);
+ return this;
+ }
+
+ /**
+ * Builds the {@link MediaItemStatus media item status object}.
+ */
+ public MediaItemStatus build() {
+ return new MediaItemStatus(mBundle);
+ }
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaRouteDescriptor.java b/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaRouteDescriptor.java
new file mode 100644
index 0000000..6bc84fc
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaRouteDescriptor.java
@@ -0,0 +1,693 @@
+/*
+ * 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.support.mediarouter.media;
+
+import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.net.Uri;
+import android.os.Bundle;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Describes the properties of a route.
+ * <p>
+ * Each route is uniquely identified by an opaque id string. This token
+ * may take any form as long as it is unique within the media route provider.
+ * </p><p>
+ * This object is immutable once created using a {@link Builder} instance.
+ * </p>
+ */
+public final class MediaRouteDescriptor {
+ static final String KEY_ID = "id";
+ static final String KEY_GROUP_MEMBER_IDS = "groupMemberIds";
+ static final String KEY_NAME = "name";
+ static final String KEY_DESCRIPTION = "status";
+ static final String KEY_ICON_URI = "iconUri";
+ static final String KEY_ENABLED = "enabled";
+ static final String KEY_CONNECTING = "connecting";
+ static final String KEY_CONNECTION_STATE = "connectionState";
+ static final String KEY_CONTROL_FILTERS = "controlFilters";
+ static final String KEY_PLAYBACK_TYPE = "playbackType";
+ static final String KEY_PLAYBACK_STREAM = "playbackStream";
+ static final String KEY_DEVICE_TYPE = "deviceType";
+ static final String KEY_VOLUME = "volume";
+ static final String KEY_VOLUME_MAX = "volumeMax";
+ static final String KEY_VOLUME_HANDLING = "volumeHandling";
+ static final String KEY_PRESENTATION_DISPLAY_ID = "presentationDisplayId";
+ static final String KEY_EXTRAS = "extras";
+ static final String KEY_CAN_DISCONNECT = "canDisconnect";
+ static final String KEY_SETTINGS_INTENT = "settingsIntent";
+ static final String KEY_MIN_CLIENT_VERSION = "minClientVersion";
+ static final String KEY_MAX_CLIENT_VERSION = "maxClientVersion";
+
+ final Bundle mBundle;
+ List<IntentFilter> mControlFilters;
+
+ MediaRouteDescriptor(Bundle bundle, List<IntentFilter> controlFilters) {
+ mBundle = bundle;
+ mControlFilters = controlFilters;
+ }
+
+ /**
+ * Gets the unique id of the route.
+ * <p>
+ * The route id associated with a route descriptor functions as a stable
+ * identifier for the route and must be unique among all routes offered
+ * by the provider.
+ * </p>
+ */
+ public String getId() {
+ return mBundle.getString(KEY_ID);
+ }
+
+ /**
+ * Gets the group member ids of the route.
+ * <p>
+ * A route descriptor that has one or more group member route ids
+ * represents a route group. A member route may belong to another group.
+ * </p>
+ * @hide
+ */
+ // @RestrictTo(LIBRARY_GROUP)
+ public List<String> getGroupMemberIds() {
+ return mBundle.getStringArrayList(KEY_GROUP_MEMBER_IDS);
+ }
+
+ /**
+ * Gets the user-visible name of the route.
+ * <p>
+ * The route name identifies the destination represented by the route.
+ * It may be a user-supplied name, an alias, or device serial number.
+ * </p>
+ */
+ public String getName() {
+ return mBundle.getString(KEY_NAME);
+ }
+
+ /**
+ * Gets the user-visible description of the route.
+ * <p>
+ * The route description describes the kind of destination represented by the route.
+ * It may be a user-supplied string, a model number or brand of device.
+ * </p>
+ */
+ public String getDescription() {
+ return mBundle.getString(KEY_DESCRIPTION);
+ }
+
+ /**
+ * Gets the URI of the icon representing this route.
+ * <p>
+ * This icon will be used in picker UIs if available.
+ * </p>
+ */
+ public Uri getIconUri() {
+ String iconUri = mBundle.getString(KEY_ICON_URI);
+ return iconUri == null ? null : Uri.parse(iconUri);
+ }
+
+ /**
+ * Gets whether the route is enabled.
+ */
+ public boolean isEnabled() {
+ return mBundle.getBoolean(KEY_ENABLED, true);
+ }
+
+ /**
+ * Gets whether the route is connecting.
+ * @deprecated Use {@link #getConnectionState} instead
+ */
+ @Deprecated
+ public boolean isConnecting() {
+ return mBundle.getBoolean(KEY_CONNECTING, false);
+ }
+
+ /**
+ * Gets the connection state of the route.
+ *
+ * @return The connection state of this route:
+ * {@link MediaRouter.RouteInfo#CONNECTION_STATE_DISCONNECTED},
+ * {@link MediaRouter.RouteInfo#CONNECTION_STATE_CONNECTING}, or
+ * {@link MediaRouter.RouteInfo#CONNECTION_STATE_CONNECTED}.
+ */
+ public int getConnectionState() {
+ return mBundle.getInt(KEY_CONNECTION_STATE,
+ MediaRouter.RouteInfo.CONNECTION_STATE_DISCONNECTED);
+ }
+
+ /**
+ * Gets whether the route can be disconnected without stopping playback.
+ * <p>
+ * The route can normally be disconnected without stopping playback when
+ * the destination device on the route is connected to two or more source
+ * devices. The route provider should update the route immediately when the
+ * number of connected devices changes.
+ * </p><p>
+ * To specify that the route should disconnect without stopping use
+ * {@link MediaRouter#unselect(int)} with
+ * {@link MediaRouter#UNSELECT_REASON_DISCONNECTED}.
+ * </p>
+ */
+ public boolean canDisconnectAndKeepPlaying() {
+ return mBundle.getBoolean(KEY_CAN_DISCONNECT, false);
+ }
+
+ /**
+ * Gets an {@link IntentSender} for starting a settings activity for this
+ * route. The activity may have specific route settings or general settings
+ * for the connected device or route provider.
+ *
+ * @return An {@link IntentSender} to start a settings activity.
+ */
+ public IntentSender getSettingsActivity() {
+ return mBundle.getParcelable(KEY_SETTINGS_INTENT);
+ }
+
+ /**
+ * Gets the route's {@link MediaControlIntent media control intent} filters.
+ */
+ public List<IntentFilter> getControlFilters() {
+ ensureControlFilters();
+ return mControlFilters;
+ }
+
+ void ensureControlFilters() {
+ if (mControlFilters == null) {
+ mControlFilters = mBundle.<IntentFilter>getParcelableArrayList(KEY_CONTROL_FILTERS);
+ if (mControlFilters == null) {
+ mControlFilters = Collections.<IntentFilter>emptyList();
+ }
+ }
+ }
+
+ /**
+ * Gets the type of playback associated with this route.
+ *
+ * @return The type of playback associated with this route:
+ * {@link MediaRouter.RouteInfo#PLAYBACK_TYPE_LOCAL} or
+ * {@link MediaRouter.RouteInfo#PLAYBACK_TYPE_REMOTE}.
+ */
+ public int getPlaybackType() {
+ return mBundle.getInt(KEY_PLAYBACK_TYPE, MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE);
+ }
+
+ /**
+ * Gets the route's playback stream.
+ */
+ public int getPlaybackStream() {
+ return mBundle.getInt(KEY_PLAYBACK_STREAM, -1);
+ }
+
+ /**
+ * Gets the type of the receiver device associated with this route.
+ *
+ * @return The type of the receiver device associated with this route:
+ * {@link MediaRouter.RouteInfo#DEVICE_TYPE_TV} or
+ * {@link MediaRouter.RouteInfo#DEVICE_TYPE_SPEAKER}.
+ */
+ public int getDeviceType() {
+ return mBundle.getInt(KEY_DEVICE_TYPE);
+ }
+
+ /**
+ * Gets the route's current volume, or 0 if unknown.
+ */
+ public int getVolume() {
+ return mBundle.getInt(KEY_VOLUME);
+ }
+
+ /**
+ * Gets the route's maximum volume, or 0 if unknown.
+ */
+ public int getVolumeMax() {
+ return mBundle.getInt(KEY_VOLUME_MAX);
+ }
+
+ /**
+ * Gets information about how volume is handled on the route.
+ *
+ * @return How volume is handled on the route:
+ * {@link MediaRouter.RouteInfo#PLAYBACK_VOLUME_FIXED} or
+ * {@link MediaRouter.RouteInfo#PLAYBACK_VOLUME_VARIABLE}.
+ */
+ public int getVolumeHandling() {
+ return mBundle.getInt(KEY_VOLUME_HANDLING,
+ MediaRouter.RouteInfo.PLAYBACK_VOLUME_FIXED);
+ }
+
+ /**
+ * Gets the route's presentation display id, or -1 if none.
+ */
+ public int getPresentationDisplayId() {
+ return mBundle.getInt(
+ KEY_PRESENTATION_DISPLAY_ID, MediaRouter.RouteInfo.PRESENTATION_DISPLAY_ID_NONE);
+ }
+
+ /**
+ * Gets a bundle of extras for this route descriptor.
+ * The extras will be ignored by the media router but they may be used
+ * by applications.
+ */
+ public Bundle getExtras() {
+ return mBundle.getBundle(KEY_EXTRAS);
+ }
+
+ /**
+ * Gets the minimum client version required for this route.
+ * @hide
+ */
+ // @RestrictTo(LIBRARY_GROUP)
+ public int getMinClientVersion() {
+ return mBundle.getInt(KEY_MIN_CLIENT_VERSION,
+ MediaRouteProviderProtocol.CLIENT_VERSION_START);
+ }
+
+ /**
+ * Gets the maximum client version required for this route.
+ * @hide
+ */
+ // @RestrictTo(LIBRARY_GROUP)
+ public int getMaxClientVersion() {
+ return mBundle.getInt(KEY_MAX_CLIENT_VERSION, Integer.MAX_VALUE);
+ }
+
+ /**
+ * Returns true if the route descriptor has all of the required fields.
+ */
+ public boolean isValid() {
+ ensureControlFilters();
+ if (TextUtils.isEmpty(getId())
+ || TextUtils.isEmpty(getName())
+ || mControlFilters.contains(null)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder();
+ result.append("MediaRouteDescriptor{ ");
+ result.append("id=").append(getId());
+ result.append(", groupMemberIds=").append(getGroupMemberIds());
+ result.append(", name=").append(getName());
+ result.append(", description=").append(getDescription());
+ result.append(", iconUri=").append(getIconUri());
+ result.append(", isEnabled=").append(isEnabled());
+ result.append(", isConnecting=").append(isConnecting());
+ result.append(", connectionState=").append(getConnectionState());
+ result.append(", controlFilters=").append(Arrays.toString(getControlFilters().toArray()));
+ result.append(", playbackType=").append(getPlaybackType());
+ result.append(", playbackStream=").append(getPlaybackStream());
+ result.append(", deviceType=").append(getDeviceType());
+ result.append(", volume=").append(getVolume());
+ result.append(", volumeMax=").append(getVolumeMax());
+ result.append(", volumeHandling=").append(getVolumeHandling());
+ result.append(", presentationDisplayId=").append(getPresentationDisplayId());
+ result.append(", extras=").append(getExtras());
+ result.append(", isValid=").append(isValid());
+ result.append(", minClientVersion=").append(getMinClientVersion());
+ result.append(", maxClientVersion=").append(getMaxClientVersion());
+ result.append(" }");
+ return result.toString();
+ }
+
+ /**
+ * Converts this object to a bundle for serialization.
+ *
+ * @return The contents of the object represented as a bundle.
+ */
+ public Bundle asBundle() {
+ return mBundle;
+ }
+
+ /**
+ * Creates an instance from a bundle.
+ *
+ * @param bundle The bundle, or null if none.
+ * @return The new instance, or null if the bundle was null.
+ */
+ public static MediaRouteDescriptor fromBundle(Bundle bundle) {
+ return bundle != null ? new MediaRouteDescriptor(bundle, null) : null;
+ }
+
+ /**
+ * Builder for {@link MediaRouteDescriptor media route descriptors}.
+ */
+ public static final class Builder {
+ private final Bundle mBundle;
+ private ArrayList<String> mGroupMemberIds;
+ private ArrayList<IntentFilter> mControlFilters;
+
+ /**
+ * Creates a media route descriptor builder.
+ *
+ * @param id The unique id of the route.
+ * @param name The user-visible name of the route.
+ */
+ public Builder(String id, String name) {
+ mBundle = new Bundle();
+ setId(id);
+ setName(name);
+ }
+
+ /**
+ * Creates a media route descriptor builder whose initial contents are
+ * copied from an existing descriptor.
+ */
+ public Builder(MediaRouteDescriptor descriptor) {
+ if (descriptor == null) {
+ throw new IllegalArgumentException("descriptor must not be null");
+ }
+
+ mBundle = new Bundle(descriptor.mBundle);
+
+ descriptor.ensureControlFilters();
+ if (!descriptor.mControlFilters.isEmpty()) {
+ mControlFilters = new ArrayList<IntentFilter>(descriptor.mControlFilters);
+ }
+ }
+
+ /**
+ * Sets the unique id of the route.
+ * <p>
+ * The route id associated with a route descriptor functions as a stable
+ * identifier for the route and must be unique among all routes offered
+ * by the provider.
+ * </p>
+ */
+ public Builder setId(String id) {
+ mBundle.putString(KEY_ID, id);
+ return this;
+ }
+
+ /**
+ * Adds a group member id of the route.
+ * <p>
+ * A route descriptor that has one or more group member route ids
+ * represents a route group. A member route may belong to another group.
+ * </p>
+ * @hide
+ */
+ // @RestrictTo(LIBRARY_GROUP)
+ public Builder addGroupMemberId(String groupMemberId) {
+ if (TextUtils.isEmpty(groupMemberId)) {
+ throw new IllegalArgumentException("groupMemberId must not be empty");
+ }
+
+ if (mGroupMemberIds == null) {
+ mGroupMemberIds = new ArrayList<>();
+ }
+ if (!mGroupMemberIds.contains(groupMemberId)) {
+ mGroupMemberIds.add(groupMemberId);
+ }
+ return this;
+ }
+
+ /**
+ * Adds a list of group member ids of the route.
+ * <p>
+ * A route descriptor that has one or more group member route ids
+ * represents a route group. A member route may belong to another group.
+ * </p>
+ * @hide
+ */
+ // @RestrictTo(LIBRARY_GROUP)
+ public Builder addGroupMemberIds(Collection<String> groupMemberIds) {
+ if (groupMemberIds == null) {
+ throw new IllegalArgumentException("groupMemberIds must not be null");
+ }
+
+ if (!groupMemberIds.isEmpty()) {
+ for (String groupMemberId : groupMemberIds) {
+ addGroupMemberId(groupMemberId);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Sets the user-visible name of the route.
+ * <p>
+ * The route name identifies the destination represented by the route.
+ * It may be a user-supplied name, an alias, or device serial number.
+ * </p>
+ */
+ public Builder setName(String name) {
+ mBundle.putString(KEY_NAME, name);
+ return this;
+ }
+
+ /**
+ * Sets the user-visible description of the route.
+ * <p>
+ * The route description describes the kind of destination represented by the route.
+ * It may be a user-supplied string, a model number or brand of device.
+ * </p>
+ */
+ public Builder setDescription(String description) {
+ mBundle.putString(KEY_DESCRIPTION, description);
+ return this;
+ }
+
+ /**
+ * Sets the URI of the icon representing this route.
+ * <p>
+ * This icon will be used in picker UIs if available.
+ * </p><p>
+ * The URI must be one of the following formats:
+ * <ul>
+ * <li>content ({@link android.content.ContentResolver#SCHEME_CONTENT})</li>
+ * <li>android.resource ({@link android.content.ContentResolver#SCHEME_ANDROID_RESOURCE})
+ * </li>
+ * <li>file ({@link android.content.ContentResolver#SCHEME_FILE})</li>
+ * </ul>
+ * </p>
+ */
+ public Builder setIconUri(Uri iconUri) {
+ if (iconUri == null) {
+ throw new IllegalArgumentException("iconUri must not be null");
+ }
+ mBundle.putString(KEY_ICON_URI, iconUri.toString());
+ return this;
+ }
+
+ /**
+ * Sets whether the route is enabled.
+ * <p>
+ * Disabled routes represent routes that a route provider knows about, such as paired
+ * Wifi Display receivers, but that are not currently available for use.
+ * </p>
+ */
+ public Builder setEnabled(boolean enabled) {
+ mBundle.putBoolean(KEY_ENABLED, enabled);
+ return this;
+ }
+
+ /**
+ * Sets whether the route is in the process of connecting and is not yet
+ * ready for use.
+ * @deprecated Use {@link #setConnectionState} instead.
+ */
+ @Deprecated
+ public Builder setConnecting(boolean connecting) {
+ mBundle.putBoolean(KEY_CONNECTING, connecting);
+ return this;
+ }
+
+ /**
+ * Sets the route's connection state.
+ *
+ * @param connectionState The connection state of the route:
+ * {@link MediaRouter.RouteInfo#CONNECTION_STATE_DISCONNECTED},
+ * {@link MediaRouter.RouteInfo#CONNECTION_STATE_CONNECTING}, or
+ * {@link MediaRouter.RouteInfo#CONNECTION_STATE_CONNECTED}.
+ */
+ public Builder setConnectionState(int connectionState) {
+ mBundle.putInt(KEY_CONNECTION_STATE, connectionState);
+ return this;
+ }
+
+ /**
+ * Sets whether the route can be disconnected without stopping playback.
+ */
+ public Builder setCanDisconnect(boolean canDisconnect) {
+ mBundle.putBoolean(KEY_CAN_DISCONNECT, canDisconnect);
+ return this;
+ }
+
+ /**
+ * Sets an intent sender for launching the settings activity for this
+ * route.
+ */
+ public Builder setSettingsActivity(IntentSender is) {
+ mBundle.putParcelable(KEY_SETTINGS_INTENT, is);
+ return this;
+ }
+
+ /**
+ * Adds a {@link MediaControlIntent media control intent} filter for the route.
+ */
+ public Builder addControlFilter(IntentFilter filter) {
+ if (filter == null) {
+ throw new IllegalArgumentException("filter must not be null");
+ }
+
+ if (mControlFilters == null) {
+ mControlFilters = new ArrayList<IntentFilter>();
+ }
+ if (!mControlFilters.contains(filter)) {
+ mControlFilters.add(filter);
+ }
+ return this;
+ }
+
+ /**
+ * Adds a list of {@link MediaControlIntent media control intent} filters for the route.
+ */
+ public Builder addControlFilters(Collection<IntentFilter> filters) {
+ if (filters == null) {
+ throw new IllegalArgumentException("filters must not be null");
+ }
+
+ if (!filters.isEmpty()) {
+ for (IntentFilter filter : filters) {
+ addControlFilter(filter);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Sets the route's playback type.
+ *
+ * @param playbackType The playback type of the route:
+ * {@link MediaRouter.RouteInfo#PLAYBACK_TYPE_LOCAL} or
+ * {@link MediaRouter.RouteInfo#PLAYBACK_TYPE_REMOTE}.
+ */
+ public Builder setPlaybackType(int playbackType) {
+ mBundle.putInt(KEY_PLAYBACK_TYPE, playbackType);
+ return this;
+ }
+
+ /**
+ * Sets the route's playback stream.
+ */
+ public Builder setPlaybackStream(int playbackStream) {
+ mBundle.putInt(KEY_PLAYBACK_STREAM, playbackStream);
+ return this;
+ }
+
+ /**
+ * Sets the route's receiver device type.
+ *
+ * @param deviceType The receive device type of the route:
+ * {@link MediaRouter.RouteInfo#DEVICE_TYPE_TV} or
+ * {@link MediaRouter.RouteInfo#DEVICE_TYPE_SPEAKER}.
+ */
+ public Builder setDeviceType(int deviceType) {
+ mBundle.putInt(KEY_DEVICE_TYPE, deviceType);
+ return this;
+ }
+
+ /**
+ * Sets the route's current volume, or 0 if unknown.
+ */
+ public Builder setVolume(int volume) {
+ mBundle.putInt(KEY_VOLUME, volume);
+ return this;
+ }
+
+ /**
+ * Sets the route's maximum volume, or 0 if unknown.
+ */
+ public Builder setVolumeMax(int volumeMax) {
+ mBundle.putInt(KEY_VOLUME_MAX, volumeMax);
+ return this;
+ }
+
+ /**
+ * Sets the route's volume handling.
+ *
+ * @param volumeHandling how volume is handled on the route:
+ * {@link MediaRouter.RouteInfo#PLAYBACK_VOLUME_FIXED} or
+ * {@link MediaRouter.RouteInfo#PLAYBACK_VOLUME_VARIABLE}.
+ */
+ public Builder setVolumeHandling(int volumeHandling) {
+ mBundle.putInt(KEY_VOLUME_HANDLING, volumeHandling);
+ return this;
+ }
+
+ /**
+ * Sets the route's presentation display id, or -1 if none.
+ */
+ public Builder setPresentationDisplayId(int presentationDisplayId) {
+ mBundle.putInt(KEY_PRESENTATION_DISPLAY_ID, presentationDisplayId);
+ return this;
+ }
+
+ /**
+ * Sets a bundle of extras for this route descriptor.
+ * The extras will be ignored by the media router but they may be used
+ * by applications.
+ */
+ public Builder setExtras(Bundle extras) {
+ mBundle.putBundle(KEY_EXTRAS, extras);
+ return this;
+ }
+
+ /**
+ * Sets the route's minimum client version.
+ * A router whose version is lower than this will not be able to connect to this route.
+ * @hide
+ */
+ // @RestrictTo(LIBRARY_GROUP)
+ public Builder setMinClientVersion(int minVersion) {
+ mBundle.putInt(KEY_MIN_CLIENT_VERSION, minVersion);
+ return this;
+ }
+
+ /**
+ * Sets the route's maximum client version.
+ * A router whose version is higher than this will not be able to connect to this route.
+ * @hide
+ */
+ // @RestrictTo(LIBRARY_GROUP)
+ public Builder setMaxClientVersion(int maxVersion) {
+ mBundle.putInt(KEY_MAX_CLIENT_VERSION, maxVersion);
+ return this;
+ }
+
+ /**
+ * Builds the {@link MediaRouteDescriptor media route descriptor}.
+ */
+ public MediaRouteDescriptor build() {
+ if (mControlFilters != null) {
+ mBundle.putParcelableArrayList(KEY_CONTROL_FILTERS, mControlFilters);
+ }
+ if (mGroupMemberIds != null) {
+ mBundle.putStringArrayList(KEY_GROUP_MEMBER_IDS, mGroupMemberIds);
+ }
+ return new MediaRouteDescriptor(mBundle, mControlFilters);
+ }
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaRouteDiscoveryRequest.java b/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaRouteDiscoveryRequest.java
new file mode 100644
index 0000000..039627f
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaRouteDiscoveryRequest.java
@@ -0,0 +1,132 @@
+/*
+ * 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.support.mediarouter.media;
+
+import android.os.Bundle;
+
+/**
+ * Describes the kinds of routes that the media router would like to discover
+ * and whether to perform active scanning.
+ * <p>
+ * This object is immutable once created.
+ * </p>
+ */
+public final class MediaRouteDiscoveryRequest {
+ private static final String KEY_SELECTOR = "selector";
+ private static final String KEY_ACTIVE_SCAN = "activeScan";
+
+ private final Bundle mBundle;
+ private MediaRouteSelector mSelector;
+
+ /**
+ * Creates a media route discovery request.
+ *
+ * @param selector The route selector that specifies the kinds of routes to discover.
+ * @param activeScan True if active scanning should be performed.
+ */
+ public MediaRouteDiscoveryRequest(MediaRouteSelector selector, boolean activeScan) {
+ if (selector == null) {
+ throw new IllegalArgumentException("selector must not be null");
+ }
+
+ mBundle = new Bundle();
+ mSelector = selector;
+ mBundle.putBundle(KEY_SELECTOR, selector.asBundle());
+ mBundle.putBoolean(KEY_ACTIVE_SCAN, activeScan);
+ }
+
+ private MediaRouteDiscoveryRequest(Bundle bundle) {
+ mBundle = bundle;
+ }
+
+ /**
+ * Gets the route selector that specifies the kinds of routes to discover.
+ */
+ public MediaRouteSelector getSelector() {
+ ensureSelector();
+ return mSelector;
+ }
+
+ private void ensureSelector() {
+ if (mSelector == null) {
+ mSelector = MediaRouteSelector.fromBundle(mBundle.getBundle(KEY_SELECTOR));
+ if (mSelector == null) {
+ mSelector = MediaRouteSelector.EMPTY;
+ }
+ }
+ }
+
+ /**
+ * Returns true if active scanning should be performed.
+ *
+ * @see MediaRouter#CALLBACK_FLAG_PERFORM_ACTIVE_SCAN
+ */
+ public boolean isActiveScan() {
+ return mBundle.getBoolean(KEY_ACTIVE_SCAN);
+ }
+
+ /**
+ * Returns true if the discovery request has all of the required fields.
+ */
+ public boolean isValid() {
+ ensureSelector();
+ return mSelector.isValid();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof MediaRouteDiscoveryRequest) {
+ MediaRouteDiscoveryRequest other = (MediaRouteDiscoveryRequest)o;
+ return getSelector().equals(other.getSelector())
+ && isActiveScan() == other.isActiveScan();
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return getSelector().hashCode() ^ (isActiveScan() ? 1 : 0);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder();
+ result.append("DiscoveryRequest{ selector=").append(getSelector());
+ result.append(", activeScan=").append(isActiveScan());
+ result.append(", isValid=").append(isValid());
+ result.append(" }");
+ return result.toString();
+ }
+
+ /**
+ * Converts this object to a bundle for serialization.
+ *
+ * @return The contents of the object represented as a bundle.
+ */
+ public Bundle asBundle() {
+ return mBundle;
+ }
+
+ /**
+ * Creates an instance from a bundle.
+ *
+ * @param bundle The bundle, or null if none.
+ * @return The new instance, or null if the bundle was null.
+ */
+ public static MediaRouteDiscoveryRequest fromBundle(Bundle bundle) {
+ return bundle != null ? new MediaRouteDiscoveryRequest(bundle) : null;
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaRouteProvider.java b/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaRouteProvider.java
new file mode 100644
index 0000000..91a2e1a
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaRouteProvider.java
@@ -0,0 +1,447 @@
+/*
+ * 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.support.mediarouter.media;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.Message;
+import android.support.v4.util.ObjectsCompat;
+
+import com.android.support.mediarouter.media.MediaRouter.ControlRequestCallback;
+
+/**
+ * Media route providers are used to publish additional media routes for
+ * use within an application. Media route providers may also be declared
+ * as a service to publish additional media routes to all applications
+ * in the system.
+ * <p>
+ * The purpose of a media route provider is to discover media routes that satisfy
+ * the criteria specified by the current {@link MediaRouteDiscoveryRequest} and publish a
+ * {@link MediaRouteProviderDescriptor} with information about each route by calling
+ * {@link #setDescriptor} to notify the currently registered {@link Callback}.
+ * </p><p>
+ * The provider should watch for changes to the discovery request by implementing
+ * {@link #onDiscoveryRequestChanged} and updating the set of routes that it is
+ * attempting to discover. It should also handle route control requests such
+ * as volume changes or {@link MediaControlIntent media control intents}
+ * by implementing {@link #onCreateRouteController} to return a {@link RouteController}
+ * for a particular route.
+ * </p><p>
+ * A media route provider may be used privately within the scope of a single
+ * application process by calling {@link MediaRouter#addProvider MediaRouter.addProvider}
+ * to add it to the local {@link MediaRouter}. A media route provider may also be made
+ * available globally to all applications by registering a {@link MediaRouteProviderService}
+ * in the provider's manifest. When the media route provider is registered
+ * as a service, all applications that use the media router API will be able to
+ * discover and used the provider's routes without having to install anything else.
+ * </p><p>
+ * This object must only be accessed on the main thread.
+ * </p>
+ */
+public abstract class MediaRouteProvider {
+ static final int MSG_DELIVER_DESCRIPTOR_CHANGED = 1;
+ static final int MSG_DELIVER_DISCOVERY_REQUEST_CHANGED = 2;
+
+ private final Context mContext;
+ private final ProviderMetadata mMetadata;
+ private final ProviderHandler mHandler = new ProviderHandler();
+
+ private Callback mCallback;
+
+ private MediaRouteDiscoveryRequest mDiscoveryRequest;
+ private boolean mPendingDiscoveryRequestChange;
+
+ private MediaRouteProviderDescriptor mDescriptor;
+ private boolean mPendingDescriptorChange;
+
+ /**
+ * Creates a media route provider.
+ *
+ * @param context The context.
+ */
+ public MediaRouteProvider(@NonNull Context context) {
+ this(context, null);
+ }
+
+ MediaRouteProvider(Context context, ProviderMetadata metadata) {
+ if (context == null) {
+ throw new IllegalArgumentException("context must not be null");
+ }
+
+ mContext = context;
+ if (metadata == null) {
+ mMetadata = new ProviderMetadata(new ComponentName(context, getClass()));
+ } else {
+ mMetadata = metadata;
+ }
+ }
+
+ /**
+ * Gets the context of the media route provider.
+ */
+ public final Context getContext() {
+ return mContext;
+ }
+
+ /**
+ * Gets the provider's handler which is associated with the main thread.
+ */
+ public final Handler getHandler() {
+ return mHandler;
+ }
+
+ /**
+ * Gets some metadata about the provider's implementation.
+ */
+ public final ProviderMetadata getMetadata() {
+ return mMetadata;
+ }
+
+ /**
+ * Sets a callback to invoke when the provider's descriptor changes.
+ *
+ * @param callback The callback to use, or null if none.
+ */
+ public final void setCallback(@Nullable Callback callback) {
+ MediaRouter.checkCallingThread();
+ mCallback = callback;
+ }
+
+ /**
+ * Gets the current discovery request which informs the provider about the
+ * kinds of routes to discover and whether to perform active scanning.
+ *
+ * @return The current discovery request, or null if no discovery is needed at this time.
+ *
+ * @see #onDiscoveryRequestChanged
+ */
+ @Nullable
+ public final MediaRouteDiscoveryRequest getDiscoveryRequest() {
+ return mDiscoveryRequest;
+ }
+
+ /**
+ * Sets a discovery request to inform the provider about the kinds of
+ * routes that its clients would like to discover and whether to perform active scanning.
+ *
+ * @param request The discovery request, or null if no discovery is needed at this time.
+ *
+ * @see #onDiscoveryRequestChanged
+ */
+ public final void setDiscoveryRequest(MediaRouteDiscoveryRequest request) {
+ MediaRouter.checkCallingThread();
+
+ if (ObjectsCompat.equals(mDiscoveryRequest, request)) {
+ return;
+ }
+
+ mDiscoveryRequest = request;
+ if (!mPendingDiscoveryRequestChange) {
+ mPendingDiscoveryRequestChange = true;
+ mHandler.sendEmptyMessage(MSG_DELIVER_DISCOVERY_REQUEST_CHANGED);
+ }
+ }
+
+ void deliverDiscoveryRequestChanged() {
+ mPendingDiscoveryRequestChange = false;
+ onDiscoveryRequestChanged(mDiscoveryRequest);
+ }
+
+ /**
+ * Called by the media router when the {@link MediaRouteDiscoveryRequest discovery request}
+ * has changed.
+ * <p>
+ * Whenever an applications calls {@link MediaRouter#addCallback} to register
+ * a callback, it also provides a selector to specify the kinds of routes that
+ * it is interested in. The media router combines all of these selectors together
+ * to generate a {@link MediaRouteDiscoveryRequest} and notifies each provider when a change
+ * occurs by calling {@link #setDiscoveryRequest} which posts a message to invoke
+ * this method asynchronously.
+ * </p><p>
+ * The provider should examine the {@link MediaControlIntent media control categories}
+ * in the discovery request's {@link MediaRouteSelector selector} to determine what
+ * kinds of routes it should try to discover and whether it should perform active
+ * or passive scans. In many cases, the provider may be able to save power by
+ * determining that the selector does not contain any categories that it supports
+ * and it can therefore avoid performing any scans at all.
+ * </p>
+ *
+ * @param request The new discovery request, or null if no discovery is needed at this time.
+ *
+ * @see MediaRouter#addCallback
+ */
+ public void onDiscoveryRequestChanged(@Nullable MediaRouteDiscoveryRequest request) {
+ }
+
+ /**
+ * Gets the provider's descriptor.
+ * <p>
+ * The descriptor describes the state of the media route provider and
+ * the routes that it publishes. Watch for changes to the descriptor
+ * by registering a {@link Callback callback} with {@link #setCallback}.
+ * </p>
+ *
+ * @return The media route provider descriptor, or null if none.
+ *
+ * @see Callback#onDescriptorChanged
+ */
+ @Nullable
+ public final MediaRouteProviderDescriptor getDescriptor() {
+ return mDescriptor;
+ }
+
+ /**
+ * Sets the provider's descriptor.
+ * <p>
+ * The provider must call this method to notify the currently registered
+ * {@link Callback callback} about the change to the provider's descriptor.
+ * </p>
+ *
+ * @param descriptor The updated route provider descriptor, or null if none.
+ *
+ * @see Callback#onDescriptorChanged
+ */
+ public final void setDescriptor(@Nullable MediaRouteProviderDescriptor descriptor) {
+ MediaRouter.checkCallingThread();
+
+ if (mDescriptor != descriptor) {
+ mDescriptor = descriptor;
+ if (!mPendingDescriptorChange) {
+ mPendingDescriptorChange = true;
+ mHandler.sendEmptyMessage(MSG_DELIVER_DESCRIPTOR_CHANGED);
+ }
+ }
+ }
+
+ void deliverDescriptorChanged() {
+ mPendingDescriptorChange = false;
+
+ if (mCallback != null) {
+ mCallback.onDescriptorChanged(this, mDescriptor);
+ }
+ }
+
+ /**
+ * Called by the media router to obtain a route controller for a particular route.
+ * <p>
+ * The media router will invoke the {@link RouteController#onRelease} method of the route
+ * controller when it is no longer needed to allow it to free its resources.
+ * </p>
+ *
+ * @param routeId The unique id of the route.
+ * @return The route controller. Returns null if there is no such route or if the route
+ * cannot be controlled using the route controller interface.
+ */
+ @Nullable
+ public RouteController onCreateRouteController(@NonNull String routeId) {
+ if (routeId == null) {
+ throw new IllegalArgumentException("routeId cannot be null");
+ }
+ return null;
+ }
+
+ /**
+ * Called by the media router to obtain a route controller for a particular route which is a
+ * member of {@link MediaRouter.RouteGroup}.
+ * <p>
+ * The media router will invoke the {@link RouteController#onRelease} method of the route
+ * controller when it is no longer needed to allow it to free its resources.
+ * </p>
+ *
+ * @param routeId The unique id of the member route.
+ * @param routeGroupId The unique id of the route group.
+ * @return The route controller. Returns null if there is no such route or if the route
+ * cannot be controlled using the route controller interface.
+ * @hide
+ */
+ // @RestrictTo(LIBRARY_GROUP)
+ @Nullable
+ public RouteController onCreateRouteController(@NonNull String routeId,
+ @NonNull String routeGroupId) {
+ if (routeId == null) {
+ throw new IllegalArgumentException("routeId cannot be null");
+ }
+ if (routeGroupId == null) {
+ throw new IllegalArgumentException("routeGroupId cannot be null");
+ }
+ return onCreateRouteController(routeId);
+ }
+
+ /**
+ * Describes properties of the route provider's implementation.
+ * <p>
+ * This object is immutable once created.
+ * </p>
+ */
+ public static final class ProviderMetadata {
+ private final ComponentName mComponentName;
+
+ ProviderMetadata(ComponentName componentName) {
+ if (componentName == null) {
+ throw new IllegalArgumentException("componentName must not be null");
+ }
+ mComponentName = componentName;
+ }
+
+ /**
+ * Gets the provider's package name.
+ */
+ public String getPackageName() {
+ return mComponentName.getPackageName();
+ }
+
+ /**
+ * Gets the provider's component name.
+ */
+ public ComponentName getComponentName() {
+ return mComponentName;
+ }
+
+ @Override
+ public String toString() {
+ return "ProviderMetadata{ componentName="
+ + mComponentName.flattenToShortString() + " }";
+ }
+ }
+
+ /**
+ * Provides control over a particular route.
+ * <p>
+ * The media router obtains a route controller for a route whenever it needs
+ * to control a route. When a route is selected, the media router invokes
+ * the {@link #onSelect} method of its route controller. While selected,
+ * the media router may call other methods of the route controller to
+ * request that it perform certain actions to the route. When a route is
+ * unselected, the media router invokes the {@link #onUnselect} method of its
+ * route controller. When the media route no longer needs the route controller
+ * it will invoke the {@link #onRelease} method to allow the route controller
+ * to free its resources.
+ * </p><p>
+ * There may be multiple route controllers simultaneously active for the
+ * same route. Each route controller will be released separately.
+ * </p><p>
+ * All operations on the route controller are asynchronous and
+ * results are communicated via callbacks.
+ * </p>
+ */
+ public static abstract class RouteController {
+ /**
+ * Releases the route controller, allowing it to free its resources.
+ */
+ public void onRelease() {
+ }
+
+ /**
+ * Selects the route.
+ */
+ public void onSelect() {
+ }
+
+ /**
+ * Unselects the route.
+ */
+ public void onUnselect() {
+ }
+
+ /**
+ * Unselects the route and provides a reason. The default implementation
+ * calls {@link #onUnselect()}.
+ * <p>
+ * The reason provided will be one of the following:
+ * <ul>
+ * <li>{@link MediaRouter#UNSELECT_REASON_UNKNOWN}</li>
+ * <li>{@link MediaRouter#UNSELECT_REASON_DISCONNECTED}</li>
+ * <li>{@link MediaRouter#UNSELECT_REASON_STOPPED}</li>
+ * <li>{@link MediaRouter#UNSELECT_REASON_ROUTE_CHANGED}</li>
+ * </ul>
+ *
+ * @param reason The reason for unselecting the route.
+ */
+ public void onUnselect(int reason) {
+ onUnselect();
+ }
+
+ /**
+ * Requests to set the volume of the route.
+ *
+ * @param volume The new volume value between 0 and {@link MediaRouteDescriptor#getVolumeMax}.
+ */
+ public void onSetVolume(int volume) {
+ }
+
+ /**
+ * Requests an incremental volume update for the route.
+ *
+ * @param delta The delta to add to the current volume.
+ */
+ public void onUpdateVolume(int delta) {
+ }
+
+ /**
+ * Performs a {@link MediaControlIntent media control} request
+ * asynchronously on behalf of the route.
+ *
+ * @param intent A {@link MediaControlIntent media control intent}.
+ * @param callback A {@link ControlRequestCallback} to invoke with the result
+ * of the request, or null if no result is required.
+ * @return True if the controller intends to handle the request and will
+ * invoke the callback when finished. False if the controller will not
+ * handle the request and will not invoke the callback.
+ *
+ * @see MediaControlIntent
+ */
+ public boolean onControlRequest(Intent intent, @Nullable ControlRequestCallback callback) {
+ return false;
+ }
+ }
+
+ /**
+ * Callback which is invoked when route information becomes available or changes.
+ */
+ public static abstract class Callback {
+ /**
+ * Called when information about a route provider and its routes changes.
+ *
+ * @param provider The media route provider that changed, never null.
+ * @param descriptor The new media route provider descriptor, or null if none.
+ */
+ public void onDescriptorChanged(@NonNull MediaRouteProvider provider,
+ @Nullable MediaRouteProviderDescriptor descriptor) {
+ }
+ }
+
+ private final class ProviderHandler extends Handler {
+ ProviderHandler() {
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_DELIVER_DESCRIPTOR_CHANGED:
+ deliverDescriptorChanged();
+ break;
+ case MSG_DELIVER_DISCOVERY_REQUEST_CHANGED:
+ deliverDiscoveryRequestChanged();
+ break;
+ }
+ }
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaRouteProviderDescriptor.java b/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaRouteProviderDescriptor.java
new file mode 100644
index 0000000..eb1ce09
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaRouteProviderDescriptor.java
@@ -0,0 +1,208 @@
+/*
+ * 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.support.mediarouter.media;
+
+import android.os.Bundle;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Describes the state of a media route provider and the routes that it publishes.
+ * <p>
+ * This object is immutable once created using a {@link Builder} instance.
+ * </p>
+ */
+public final class MediaRouteProviderDescriptor {
+ private static final String KEY_ROUTES = "routes";
+
+ private final Bundle mBundle;
+ private List<MediaRouteDescriptor> mRoutes;
+
+ private MediaRouteProviderDescriptor(Bundle bundle, List<MediaRouteDescriptor> routes) {
+ mBundle = bundle;
+ mRoutes = routes;
+ }
+
+ /**
+ * Gets the list of all routes that this provider has published.
+ */
+ public List<MediaRouteDescriptor> getRoutes() {
+ ensureRoutes();
+ return mRoutes;
+ }
+
+ private void ensureRoutes() {
+ if (mRoutes == null) {
+ ArrayList<Bundle> routeBundles = mBundle.<Bundle>getParcelableArrayList(KEY_ROUTES);
+ if (routeBundles == null || routeBundles.isEmpty()) {
+ mRoutes = Collections.<MediaRouteDescriptor>emptyList();
+ } else {
+ final int count = routeBundles.size();
+ mRoutes = new ArrayList<MediaRouteDescriptor>(count);
+ for (int i = 0; i < count; i++) {
+ mRoutes.add(MediaRouteDescriptor.fromBundle(routeBundles.get(i)));
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns true if the route provider descriptor and all of the routes that
+ * it contains have all of the required fields.
+ * <p>
+ * This verification is deep. If the provider descriptor is known to be
+ * valid then it is not necessary to call {@link #isValid} on each of its routes.
+ * </p>
+ */
+ public boolean isValid() {
+ ensureRoutes();
+ final int routeCount = mRoutes.size();
+ for (int i = 0; i < routeCount; i++) {
+ MediaRouteDescriptor route = mRoutes.get(i);
+ if (route == null || !route.isValid()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder();
+ result.append("MediaRouteProviderDescriptor{ ");
+ result.append("routes=").append(
+ Arrays.toString(getRoutes().toArray()));
+ result.append(", isValid=").append(isValid());
+ result.append(" }");
+ return result.toString();
+ }
+
+ /**
+ * Converts this object to a bundle for serialization.
+ *
+ * @return The contents of the object represented as a bundle.
+ */
+ public Bundle asBundle() {
+ return mBundle;
+ }
+
+ /**
+ * Creates an instance from a bundle.
+ *
+ * @param bundle The bundle, or null if none.
+ * @return The new instance, or null if the bundle was null.
+ */
+ public static MediaRouteProviderDescriptor fromBundle(Bundle bundle) {
+ return bundle != null ? new MediaRouteProviderDescriptor(bundle, null) : null;
+ }
+
+ /**
+ * Builder for {@link MediaRouteProviderDescriptor media route provider descriptors}.
+ */
+ public static final class Builder {
+ private final Bundle mBundle;
+ private ArrayList<MediaRouteDescriptor> mRoutes;
+
+ /**
+ * Creates an empty media route provider descriptor builder.
+ */
+ public Builder() {
+ mBundle = new Bundle();
+ }
+
+ /**
+ * Creates a media route provider descriptor builder whose initial contents are
+ * copied from an existing descriptor.
+ */
+ public Builder(MediaRouteProviderDescriptor descriptor) {
+ if (descriptor == null) {
+ throw new IllegalArgumentException("descriptor must not be null");
+ }
+
+ mBundle = new Bundle(descriptor.mBundle);
+
+ descriptor.ensureRoutes();
+ if (!descriptor.mRoutes.isEmpty()) {
+ mRoutes = new ArrayList<MediaRouteDescriptor>(descriptor.mRoutes);
+ }
+ }
+
+ /**
+ * Adds a route.
+ */
+ public Builder addRoute(MediaRouteDescriptor route) {
+ if (route == null) {
+ throw new IllegalArgumentException("route must not be null");
+ }
+
+ if (mRoutes == null) {
+ mRoutes = new ArrayList<MediaRouteDescriptor>();
+ } else if (mRoutes.contains(route)) {
+ throw new IllegalArgumentException("route descriptor already added");
+ }
+ mRoutes.add(route);
+ return this;
+ }
+
+ /**
+ * Adds a list of routes.
+ */
+ public Builder addRoutes(Collection<MediaRouteDescriptor> routes) {
+ if (routes == null) {
+ throw new IllegalArgumentException("routes must not be null");
+ }
+
+ if (!routes.isEmpty()) {
+ for (MediaRouteDescriptor route : routes) {
+ addRoute(route);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Sets the list of routes.
+ */
+ Builder setRoutes(Collection<MediaRouteDescriptor> routes) {
+ if (routes == null || routes.isEmpty()) {
+ mRoutes = null;
+ mBundle.remove(KEY_ROUTES);
+ } else {
+ mRoutes = new ArrayList<>(routes);
+ }
+ return this;
+ }
+
+ /**
+ * Builds the {@link MediaRouteProviderDescriptor media route provider descriptor}.
+ */
+ public MediaRouteProviderDescriptor build() {
+ if (mRoutes != null) {
+ final int count = mRoutes.size();
+ ArrayList<Bundle> routeBundles = new ArrayList<Bundle>(count);
+ for (int i = 0; i < count; i++) {
+ routeBundles.add(mRoutes.get(i).asBundle());
+ }
+ mBundle.putParcelableArrayList(KEY_ROUTES, routeBundles);
+ }
+ return new MediaRouteProviderDescriptor(mBundle, mRoutes);
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaRouteProviderProtocol.java b/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaRouteProviderProtocol.java
new file mode 100644
index 0000000..6be9343
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaRouteProviderProtocol.java
@@ -0,0 +1,230 @@
+/*
+ * 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.support.mediarouter.media;
+
+import android.content.Intent;
+import android.os.Messenger;
+
+/**
+ * Defines the communication protocol for media route provider services.
+ */
+abstract class MediaRouteProviderProtocol {
+ /**
+ * The {@link Intent} that must be declared as handled by the service.
+ * Put this in your manifest.
+ */
+ public static final String SERVICE_INTERFACE =
+ "android.media.MediaRouteProviderService";
+
+ /*
+ * Messages sent from the client to the service.
+ * DO NOT RENUMBER THESE!
+ */
+
+ /** (client v1)
+ * Register client.
+ * - replyTo : client messenger
+ * - arg1 : request id
+ * - arg2 : client version
+ */
+ public static final int CLIENT_MSG_REGISTER = 1;
+
+ /** (client v1)
+ * Unregister client.
+ * - replyTo : client messenger
+ * - arg1 : request id
+ */
+ public static final int CLIENT_MSG_UNREGISTER = 2;
+
+ /** (client v1)
+ * Create route controller.
+ * - replyTo : client messenger
+ * - arg1 : request id
+ * - arg2 : route controller id
+ * - CLIENT_DATA_ROUTE_ID : route id string
+ */
+ public static final int CLIENT_MSG_CREATE_ROUTE_CONTROLLER = 3;
+
+ /** (client v1)
+ * Release route controller.
+ * - replyTo : client messenger
+ * - arg1 : request id
+ * - arg2 : route controller id
+ */
+ public static final int CLIENT_MSG_RELEASE_ROUTE_CONTROLLER = 4;
+
+ /** (client v1)
+ * Select route.
+ * - replyTo : client messenger
+ * - arg1 : request id
+ * - arg2 : route controller id
+ */
+ public static final int CLIENT_MSG_SELECT_ROUTE = 5;
+
+ /** (client v1)
+ * Unselect route.
+ * - replyTo : client messenger
+ * - arg1 : request id
+ * - arg2 : route controller id
+ */
+ public static final int CLIENT_MSG_UNSELECT_ROUTE = 6;
+
+ /** (client v1)
+ * Set route volume.
+ * - replyTo : client messenger
+ * - arg1 : request id
+ * - arg2 : route controller id
+ * - CLIENT_DATA_VOLUME : volume integer
+ */
+ public static final int CLIENT_MSG_SET_ROUTE_VOLUME = 7;
+
+ /** (client v1)
+ * Update route volume.
+ * - replyTo : client messenger
+ * - arg1 : request id
+ * - arg2 : route controller id
+ * - CLIENT_DATA_VOLUME : volume delta integer
+ */
+ public static final int CLIENT_MSG_UPDATE_ROUTE_VOLUME = 8;
+
+ /** (client v1)
+ * Route control request.
+ * - replyTo : client messenger
+ * - arg1 : request id
+ * - arg2 : route controller id
+ * - obj : media control intent
+ */
+ public static final int CLIENT_MSG_ROUTE_CONTROL_REQUEST = 9;
+
+ /** (client v1)
+ * Sets the discovery request.
+ * - replyTo : client messenger
+ * - arg1 : request id
+ * - obj : discovery request bundle, or null if none
+ */
+ public static final int CLIENT_MSG_SET_DISCOVERY_REQUEST = 10;
+
+ public static final String CLIENT_DATA_ROUTE_ID = "routeId";
+ public static final String CLIENT_DATA_ROUTE_LIBRARY_GROUP = "routeGroupId";
+ public static final String CLIENT_DATA_VOLUME = "volume";
+ public static final String CLIENT_DATA_UNSELECT_REASON = "unselectReason";
+
+ /*
+ * Messages sent from the service to the client.
+ * DO NOT RENUMBER THESE!
+ */
+
+ /** (service v1)
+ * Generic failure sent in response to any unrecognized or malformed request.
+ * - arg1 : request id
+ */
+ public static final int SERVICE_MSG_GENERIC_FAILURE = 0;
+
+ /** (service v1)
+ * Generic failure sent in response to a successful message.
+ * - arg1 : request id
+ */
+ public static final int SERVICE_MSG_GENERIC_SUCCESS = 1;
+
+ /** (service v1)
+ * Registration succeeded.
+ * - arg1 : request id
+ * - arg2 : server version
+ * - obj : route provider descriptor bundle, or null
+ */
+ public static final int SERVICE_MSG_REGISTERED = 2;
+
+ /** (service v1)
+ * Route control request success result.
+ * - arg1 : request id
+ * - obj : result data bundle, or null
+ */
+ public static final int SERVICE_MSG_CONTROL_REQUEST_SUCCEEDED = 3;
+
+ /** (service v1)
+ * Route control request failure result.
+ * - arg1 : request id
+ * - obj : result data bundle, or null
+ * - SERVICE_DATA_ERROR: error message
+ */
+ public static final int SERVICE_MSG_CONTROL_REQUEST_FAILED = 4;
+
+ /** (service v1)
+ * Route provider descriptor changed. (unsolicited event)
+ * - arg1 : reserved (0)
+ * - obj : route provider descriptor bundle, or null
+ */
+ public static final int SERVICE_MSG_DESCRIPTOR_CHANGED = 5;
+
+ public static final String SERVICE_DATA_ERROR = "error";
+
+ /*
+ * Recognized client version numbers. (Reserved for future use.)
+ * DO NOT RENUMBER THESE!
+ */
+
+ /**
+ * The client version used from the beginning.
+ */
+ public static final int CLIENT_VERSION_1 = 1;
+
+ /**
+ * The client version used from support library v24.1.0.
+ */
+ public static final int CLIENT_VERSION_2 = 2;
+
+ /**
+ * The current client version.
+ */
+ public static final int CLIENT_VERSION_CURRENT = CLIENT_VERSION_2;
+
+ /*
+ * Recognized server version numbers. (Reserved for future use.)
+ * DO NOT RENUMBER THESE!
+ */
+
+ /**
+ * The service version used from the beginning.
+ */
+ public static final int SERVICE_VERSION_1 = 1;
+
+ /**
+ * The current service version.
+ */
+ public static final int SERVICE_VERSION_CURRENT = SERVICE_VERSION_1;
+
+ static final int CLIENT_VERSION_START = CLIENT_VERSION_1;
+
+ /**
+ * Returns true if the messenger object is valid.
+ * <p>
+ * The messenger constructor and unparceling code does not check whether the
+ * provided IBinder is a valid IMessenger object. As a result, it's possible
+ * for a peer to send an invalid IBinder that will result in crashes downstream.
+ * This method checks that the messenger is in a valid state.
+ * </p>
+ */
+ public static boolean isValidRemoteMessenger(Messenger messenger) {
+ try {
+ return messenger != null && messenger.getBinder() != null;
+ } catch (NullPointerException ex) {
+ // If the messenger was constructed with a binder interface other than
+ // IMessenger then the call to getBinder() will crash with an NPE.
+ return false;
+ }
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaRouteProviderService.java b/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaRouteProviderService.java
new file mode 100644
index 0000000..43cde10
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaRouteProviderService.java
@@ -0,0 +1,759 @@
+/*
+ * 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.support.mediarouter.media;
+
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol.CLIENT_DATA_ROUTE_ID;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+ .CLIENT_DATA_ROUTE_LIBRARY_GROUP;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+ .CLIENT_DATA_UNSELECT_REASON;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol.CLIENT_DATA_VOLUME;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+ .CLIENT_MSG_CREATE_ROUTE_CONTROLLER;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol.CLIENT_MSG_REGISTER;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+ .CLIENT_MSG_RELEASE_ROUTE_CONTROLLER;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+ .CLIENT_MSG_ROUTE_CONTROL_REQUEST;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol.CLIENT_MSG_SELECT_ROUTE;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+ .CLIENT_MSG_SET_DISCOVERY_REQUEST;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+ .CLIENT_MSG_SET_ROUTE_VOLUME;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol.CLIENT_MSG_UNREGISTER;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+ .CLIENT_MSG_UNSELECT_ROUTE;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+ .CLIENT_MSG_UPDATE_ROUTE_VOLUME;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol.CLIENT_VERSION_1;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol.SERVICE_DATA_ERROR;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+ .SERVICE_MSG_CONTROL_REQUEST_FAILED;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+ .SERVICE_MSG_CONTROL_REQUEST_SUCCEEDED;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+ .SERVICE_MSG_DESCRIPTOR_CHANGED;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+ .SERVICE_MSG_GENERIC_FAILURE;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+ .SERVICE_MSG_GENERIC_SUCCESS;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol.SERVICE_MSG_REGISTERED;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol.SERVICE_VERSION_CURRENT;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol.isValidRemoteMessenger;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.DeadObjectException;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IBinder.DeathRecipient;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.support.annotation.VisibleForTesting;
+import android.support.v4.util.ObjectsCompat;
+import android.util.Log;
+import android.util.SparseArray;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
+/**
+ * Base class for media route provider services.
+ * <p>
+ * A media router will bind to media route provider services when a callback is added via
+ * {@link MediaRouter#addCallback(MediaRouteSelector, MediaRouter.Callback, int)} with a discovery
+ * flag: {@link MediaRouter#CALLBACK_FLAG_REQUEST_DISCOVERY},
+ * {@link MediaRouter#CALLBACK_FLAG_FORCE_DISCOVERY}, or
+ * {@link MediaRouter#CALLBACK_FLAG_PERFORM_ACTIVE_SCAN}, and will unbind when the callback
+ * is removed via {@link MediaRouter#removeCallback(MediaRouter.Callback)}.
+ * </p><p>
+ * To implement your own media route provider service, extend this class and
+ * override the {@link #onCreateMediaRouteProvider} method to return an
+ * instance of your {@link MediaRouteProvider}.
+ * </p><p>
+ * Declare your media route provider service in your application manifest
+ * like this:
+ * </p>
+ * <pre>
+ * <service android:name=".MyMediaRouteProviderService"
+ * android:label="@string/my_media_route_provider_service">
+ * <intent-filter>
+ * <action android:name="android.media.MediaRouteProviderService" />
+ * </intent-filter>
+ * </service>
+ * </pre>
+ */
+public abstract class MediaRouteProviderService extends Service {
+ static final String TAG = "MediaRouteProviderSrv"; // max. 23 chars
+ static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private final ArrayList<ClientRecord> mClients = new ArrayList<ClientRecord>();
+ private final ReceiveHandler mReceiveHandler;
+ private final Messenger mReceiveMessenger;
+ final PrivateHandler mPrivateHandler;
+ private final ProviderCallback mProviderCallback;
+
+ MediaRouteProvider mProvider;
+ private MediaRouteDiscoveryRequest mCompositeDiscoveryRequest;
+
+ /**
+ * The {@link Intent} that must be declared as handled by the service.
+ * Put this in your manifest.
+ */
+ public static final String SERVICE_INTERFACE = MediaRouteProviderProtocol.SERVICE_INTERFACE;
+
+ /*
+ * Private messages used internally. (Yes, you can renumber these.)
+ */
+
+ static final int PRIVATE_MSG_CLIENT_DIED = 1;
+
+ /**
+ * Creates a media route provider service.
+ */
+ public MediaRouteProviderService() {
+ mReceiveHandler = new ReceiveHandler(this);
+ mReceiveMessenger = new Messenger(mReceiveHandler);
+ mPrivateHandler = new PrivateHandler();
+ mProviderCallback = new ProviderCallback();
+ }
+
+ /**
+ * Called by the system when it is time to create the media route provider.
+ *
+ * @return The media route provider offered by this service, or null if
+ * this service has decided not to offer a media route provider.
+ */
+ public abstract MediaRouteProvider onCreateMediaRouteProvider();
+
+ /**
+ * Gets the media route provider offered by this service.
+ *
+ * @return The media route provider offered by this service, or null if
+ * it has not yet been created.
+ *
+ * @see #onCreateMediaRouteProvider()
+ */
+ public MediaRouteProvider getMediaRouteProvider() {
+ return mProvider;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ if (intent.getAction().equals(SERVICE_INTERFACE)) {
+ if (mProvider == null) {
+ MediaRouteProvider provider = onCreateMediaRouteProvider();
+ if (provider != null) {
+ String providerPackage = provider.getMetadata().getPackageName();
+ if (!providerPackage.equals(getPackageName())) {
+ throw new IllegalStateException("onCreateMediaRouteProvider() returned "
+ + "a provider whose package name does not match the package "
+ + "name of the service. A media route provider service can "
+ + "only export its own media route providers. "
+ + "Provider package name: " + providerPackage
+ + ". Service package name: " + getPackageName() + ".");
+ }
+ mProvider = provider;
+ mProvider.setCallback(mProviderCallback);
+ }
+ }
+ if (mProvider != null) {
+ return mReceiveMessenger.getBinder();
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public boolean onUnbind(Intent intent) {
+ if (mProvider != null) {
+ mProvider.setCallback(null);
+ }
+ return super.onUnbind(intent);
+ }
+
+ boolean onRegisterClient(Messenger messenger, int requestId, int version) {
+ if (version >= CLIENT_VERSION_1) {
+ int index = findClient(messenger);
+ if (index < 0) {
+ ClientRecord client = new ClientRecord(messenger, version);
+ if (client.register()) {
+ mClients.add(client);
+ if (DEBUG) {
+ Log.d(TAG, client + ": Registered, version=" + version);
+ }
+ if (requestId != 0) {
+ MediaRouteProviderDescriptor descriptor = mProvider.getDescriptor();
+ sendReply(messenger, SERVICE_MSG_REGISTERED,
+ requestId, SERVICE_VERSION_CURRENT,
+ createDescriptorBundleForClientVersion(descriptor,
+ client.mVersion), null);
+ }
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ boolean onUnregisterClient(Messenger messenger, int requestId) {
+ int index = findClient(messenger);
+ if (index >= 0) {
+ ClientRecord client = mClients.remove(index);
+ if (DEBUG) {
+ Log.d(TAG, client + ": Unregistered");
+ }
+ client.dispose();
+ sendGenericSuccess(messenger, requestId);
+ return true;
+ }
+ return false;
+ }
+
+ void onBinderDied(Messenger messenger) {
+ int index = findClient(messenger);
+ if (index >= 0) {
+ ClientRecord client = mClients.remove(index);
+ if (DEBUG) {
+ Log.d(TAG, client + ": Binder died");
+ }
+ client.dispose();
+ }
+ }
+
+ boolean onCreateRouteController(Messenger messenger, int requestId,
+ int controllerId, String routeId, String routeGroupId) {
+ ClientRecord client = getClient(messenger);
+ if (client != null) {
+ if (client.createRouteController(routeId, routeGroupId, controllerId)) {
+ if (DEBUG) {
+ Log.d(TAG, client + ": Route controller created, controllerId=" + controllerId
+ + ", routeId=" + routeId + ", routeGroupId=" + routeGroupId);
+ }
+ sendGenericSuccess(messenger, requestId);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ boolean onReleaseRouteController(Messenger messenger, int requestId,
+ int controllerId) {
+ ClientRecord client = getClient(messenger);
+ if (client != null) {
+ if (client.releaseRouteController(controllerId)) {
+ if (DEBUG) {
+ Log.d(TAG, client + ": Route controller released"
+ + ", controllerId=" + controllerId);
+ }
+ sendGenericSuccess(messenger, requestId);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ boolean onSelectRoute(Messenger messenger, int requestId,
+ int controllerId) {
+ ClientRecord client = getClient(messenger);
+ if (client != null) {
+ MediaRouteProvider.RouteController controller =
+ client.getRouteController(controllerId);
+ if (controller != null) {
+ controller.onSelect();
+ if (DEBUG) {
+ Log.d(TAG, client + ": Route selected"
+ + ", controllerId=" + controllerId);
+ }
+ sendGenericSuccess(messenger, requestId);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ boolean onUnselectRoute(Messenger messenger, int requestId,
+ int controllerId, int reason) {
+ ClientRecord client = getClient(messenger);
+ if (client != null) {
+ MediaRouteProvider.RouteController controller =
+ client.getRouteController(controllerId);
+ if (controller != null) {
+ controller.onUnselect(reason);
+ if (DEBUG) {
+ Log.d(TAG, client + ": Route unselected"
+ + ", controllerId=" + controllerId);
+ }
+ sendGenericSuccess(messenger, requestId);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ boolean onSetRouteVolume(Messenger messenger, int requestId,
+ int controllerId, int volume) {
+ ClientRecord client = getClient(messenger);
+ if (client != null) {
+ MediaRouteProvider.RouteController controller =
+ client.getRouteController(controllerId);
+ if (controller != null) {
+ controller.onSetVolume(volume);
+ if (DEBUG) {
+ Log.d(TAG, client + ": Route volume changed"
+ + ", controllerId=" + controllerId + ", volume=" + volume);
+ }
+ sendGenericSuccess(messenger, requestId);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ boolean onUpdateRouteVolume(Messenger messenger, int requestId,
+ int controllerId, int delta) {
+ ClientRecord client = getClient(messenger);
+ if (client != null) {
+ MediaRouteProvider.RouteController controller =
+ client.getRouteController(controllerId);
+ if (controller != null) {
+ controller.onUpdateVolume(delta);
+ if (DEBUG) {
+ Log.d(TAG, client + ": Route volume updated"
+ + ", controllerId=" + controllerId + ", delta=" + delta);
+ }
+ sendGenericSuccess(messenger, requestId);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ boolean onRouteControlRequest(final Messenger messenger, final int requestId,
+ final int controllerId, final Intent intent) {
+ final ClientRecord client = getClient(messenger);
+ if (client != null) {
+ MediaRouteProvider.RouteController controller =
+ client.getRouteController(controllerId);
+ if (controller != null) {
+ MediaRouter.ControlRequestCallback callback = null;
+ if (requestId != 0) {
+ callback = new MediaRouter.ControlRequestCallback() {
+ @Override
+ public void onResult(Bundle data) {
+ if (DEBUG) {
+ Log.d(TAG, client + ": Route control request succeeded"
+ + ", controllerId=" + controllerId
+ + ", intent=" + intent
+ + ", data=" + data);
+ }
+ if (findClient(messenger) >= 0) {
+ sendReply(messenger, SERVICE_MSG_CONTROL_REQUEST_SUCCEEDED,
+ requestId, 0, data, null);
+ }
+ }
+
+ @Override
+ public void onError(String error, Bundle data) {
+ if (DEBUG) {
+ Log.d(TAG, client + ": Route control request failed"
+ + ", controllerId=" + controllerId
+ + ", intent=" + intent
+ + ", error=" + error + ", data=" + data);
+ }
+ if (findClient(messenger) >= 0) {
+ if (error != null) {
+ Bundle bundle = new Bundle();
+ bundle.putString(SERVICE_DATA_ERROR, error);
+ sendReply(messenger, SERVICE_MSG_CONTROL_REQUEST_FAILED,
+ requestId, 0, data, bundle);
+ } else {
+ sendReply(messenger, SERVICE_MSG_CONTROL_REQUEST_FAILED,
+ requestId, 0, data, null);
+ }
+ }
+ }
+ };
+ }
+ if (controller.onControlRequest(intent, callback)) {
+ if (DEBUG) {
+ Log.d(TAG, client + ": Route control request delivered"
+ + ", controllerId=" + controllerId + ", intent=" + intent);
+ }
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ boolean onSetDiscoveryRequest(Messenger messenger, int requestId,
+ MediaRouteDiscoveryRequest request) {
+ ClientRecord client = getClient(messenger);
+ if (client != null) {
+ boolean actuallyChanged = client.setDiscoveryRequest(request);
+ if (DEBUG) {
+ Log.d(TAG, client + ": Set discovery request, request=" + request
+ + ", actuallyChanged=" + actuallyChanged
+ + ", compositeDiscoveryRequest=" + mCompositeDiscoveryRequest);
+ }
+ sendGenericSuccess(messenger, requestId);
+ return true;
+ }
+ return false;
+ }
+
+ void sendDescriptorChanged(MediaRouteProviderDescriptor descriptor) {
+ final int count = mClients.size();
+ for (int i = 0; i < count; i++) {
+ ClientRecord client = mClients.get(i);
+ sendReply(client.mMessenger, SERVICE_MSG_DESCRIPTOR_CHANGED, 0, 0,
+ createDescriptorBundleForClientVersion(descriptor, client.mVersion), null);
+ if (DEBUG) {
+ Log.d(TAG, client + ": Sent descriptor change event, descriptor=" + descriptor);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ static Bundle createDescriptorBundleForClientVersion(MediaRouteProviderDescriptor descriptor,
+ int clientVersion) {
+ if (descriptor == null) {
+ return null;
+ }
+ MediaRouteProviderDescriptor.Builder builder =
+ new MediaRouteProviderDescriptor.Builder(descriptor);
+ builder.setRoutes(null);
+ for (MediaRouteDescriptor route : descriptor.getRoutes()) {
+ if (clientVersion >= route.getMinClientVersion()
+ && clientVersion <= route.getMaxClientVersion()) {
+ builder.addRoute(route);
+ }
+ }
+ return builder.build().asBundle();
+ }
+
+ boolean updateCompositeDiscoveryRequest() {
+ MediaRouteDiscoveryRequest composite = null;
+ MediaRouteSelector.Builder selectorBuilder = null;
+ boolean activeScan = false;
+ final int count = mClients.size();
+ for (int i = 0; i < count; i++) {
+ MediaRouteDiscoveryRequest request = mClients.get(i).mDiscoveryRequest;
+ if (request != null
+ && (!request.getSelector().isEmpty() || request.isActiveScan())) {
+ activeScan |= request.isActiveScan();
+ if (composite == null) {
+ composite = request;
+ } else {
+ if (selectorBuilder == null) {
+ selectorBuilder = new MediaRouteSelector.Builder(composite.getSelector());
+ }
+ selectorBuilder.addSelector(request.getSelector());
+ }
+ }
+ }
+ if (selectorBuilder != null) {
+ composite = new MediaRouteDiscoveryRequest(selectorBuilder.build(), activeScan);
+ }
+ if (!ObjectsCompat.equals(mCompositeDiscoveryRequest, composite)) {
+ mCompositeDiscoveryRequest = composite;
+ mProvider.setDiscoveryRequest(composite);
+ return true;
+ }
+ return false;
+ }
+
+ private ClientRecord getClient(Messenger messenger) {
+ int index = findClient(messenger);
+ return index >= 0 ? mClients.get(index) : null;
+ }
+
+ int findClient(Messenger messenger) {
+ final int count = mClients.size();
+ for (int i = 0; i < count; i++) {
+ ClientRecord client = mClients.get(i);
+ if (client.hasMessenger(messenger)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ static void sendGenericFailure(Messenger messenger, int requestId) {
+ if (requestId != 0) {
+ sendReply(messenger, SERVICE_MSG_GENERIC_FAILURE, requestId, 0, null, null);
+ }
+ }
+
+ private static void sendGenericSuccess(Messenger messenger, int requestId) {
+ if (requestId != 0) {
+ sendReply(messenger, SERVICE_MSG_GENERIC_SUCCESS, requestId, 0, null, null);
+ }
+ }
+
+ static void sendReply(Messenger messenger, int what,
+ int requestId, int arg, Object obj, Bundle data) {
+ Message msg = Message.obtain();
+ msg.what = what;
+ msg.arg1 = requestId;
+ msg.arg2 = arg;
+ msg.obj = obj;
+ msg.setData(data);
+ try {
+ messenger.send(msg);
+ } catch (DeadObjectException ex) {
+ // The client died.
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Could not send message to " + getClientId(messenger), ex);
+ }
+ }
+
+ static String getClientId(Messenger messenger) {
+ return "Client connection " + messenger.getBinder().toString();
+ }
+
+ private final class PrivateHandler extends Handler {
+ PrivateHandler() {
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case PRIVATE_MSG_CLIENT_DIED:
+ onBinderDied((Messenger)msg.obj);
+ break;
+ }
+ }
+ }
+
+ private final class ProviderCallback extends MediaRouteProvider.Callback {
+ ProviderCallback() {
+ }
+
+ @Override
+ public void onDescriptorChanged(MediaRouteProvider provider,
+ MediaRouteProviderDescriptor descriptor) {
+ sendDescriptorChanged(descriptor);
+ }
+ }
+
+ private final class ClientRecord implements DeathRecipient {
+ public final Messenger mMessenger;
+ public final int mVersion;
+ public MediaRouteDiscoveryRequest mDiscoveryRequest;
+
+ private final SparseArray<MediaRouteProvider.RouteController> mControllers =
+ new SparseArray<MediaRouteProvider.RouteController>();
+
+ public ClientRecord(Messenger messenger, int version) {
+ mMessenger = messenger;
+ mVersion = version;
+ }
+
+ public boolean register() {
+ try {
+ mMessenger.getBinder().linkToDeath(this, 0);
+ return true;
+ } catch (RemoteException ex) {
+ binderDied();
+ }
+ return false;
+ }
+
+ public void dispose() {
+ int count = mControllers.size();
+ for (int i = 0; i < count; i++) {
+ mControllers.valueAt(i).onRelease();
+ }
+ mControllers.clear();
+
+ mMessenger.getBinder().unlinkToDeath(this, 0);
+
+ setDiscoveryRequest(null);
+ }
+
+ public boolean hasMessenger(Messenger other) {
+ return mMessenger.getBinder() == other.getBinder();
+ }
+
+ public boolean createRouteController(String routeId, String routeGroupId,
+ int controllerId) {
+ if (mControllers.indexOfKey(controllerId) < 0) {
+ MediaRouteProvider.RouteController controller = routeGroupId == null
+ ? mProvider.onCreateRouteController(routeId)
+ : mProvider.onCreateRouteController(routeId, routeGroupId);
+ if (controller != null) {
+ mControllers.put(controllerId, controller);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean releaseRouteController(int controllerId) {
+ MediaRouteProvider.RouteController controller = mControllers.get(controllerId);
+ if (controller != null) {
+ mControllers.remove(controllerId);
+ controller.onRelease();
+ return true;
+ }
+ return false;
+ }
+
+ public MediaRouteProvider.RouteController getRouteController(int controllerId) {
+ return mControllers.get(controllerId);
+ }
+
+ public boolean setDiscoveryRequest(MediaRouteDiscoveryRequest request) {
+ if (!ObjectsCompat.equals(mDiscoveryRequest, request)) {
+ mDiscoveryRequest = request;
+ return updateCompositeDiscoveryRequest();
+ }
+ return false;
+ }
+
+ // Runs on a binder thread.
+ @Override
+ public void binderDied() {
+ mPrivateHandler.obtainMessage(PRIVATE_MSG_CLIENT_DIED, mMessenger).sendToTarget();
+ }
+
+ @Override
+ public String toString() {
+ return getClientId(mMessenger);
+ }
+ }
+
+ /**
+ * Handler that receives messages from clients.
+ * <p>
+ * This inner class is static and only retains a weak reference to the service
+ * to prevent the service from being leaked in case one of the clients is holding an
+ * active reference to the server's messenger.
+ * </p><p>
+ * This handler should not be used to handle any messages other than those
+ * that come from the client.
+ * </p>
+ */
+ private static final class ReceiveHandler extends Handler {
+ private final WeakReference<MediaRouteProviderService> mServiceRef;
+
+ public ReceiveHandler(MediaRouteProviderService service) {
+ mServiceRef = new WeakReference<MediaRouteProviderService>(service);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ final Messenger messenger = msg.replyTo;
+ if (isValidRemoteMessenger(messenger)) {
+ final int what = msg.what;
+ final int requestId = msg.arg1;
+ final int arg = msg.arg2;
+ final Object obj = msg.obj;
+ final Bundle data = msg.peekData();
+ if (!processMessage(what, messenger, requestId, arg, obj, data)) {
+ if (DEBUG) {
+ Log.d(TAG, getClientId(messenger) + ": Message failed, what=" + what
+ + ", requestId=" + requestId + ", arg=" + arg
+ + ", obj=" + obj + ", data=" + data);
+ }
+ sendGenericFailure(messenger, requestId);
+ }
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "Ignoring message without valid reply messenger.");
+ }
+ }
+ }
+
+ private boolean processMessage(int what,
+ Messenger messenger, int requestId, int arg, Object obj, Bundle data) {
+ MediaRouteProviderService service = mServiceRef.get();
+ if (service != null) {
+ switch (what) {
+ case CLIENT_MSG_REGISTER:
+ return service.onRegisterClient(messenger, requestId, arg);
+
+ case CLIENT_MSG_UNREGISTER:
+ return service.onUnregisterClient(messenger, requestId);
+
+ case CLIENT_MSG_CREATE_ROUTE_CONTROLLER: {
+ String routeId = data.getString(CLIENT_DATA_ROUTE_ID);
+ String routeGroupId = data.getString(CLIENT_DATA_ROUTE_LIBRARY_GROUP);
+ if (routeId != null) {
+ return service.onCreateRouteController(
+ messenger, requestId, arg, routeId, routeGroupId);
+ }
+ break;
+ }
+
+ case CLIENT_MSG_RELEASE_ROUTE_CONTROLLER:
+ return service.onReleaseRouteController(messenger, requestId, arg);
+
+ case CLIENT_MSG_SELECT_ROUTE:
+ return service.onSelectRoute(messenger, requestId, arg);
+
+ case CLIENT_MSG_UNSELECT_ROUTE:
+ int reason = data == null ?
+ MediaRouter.UNSELECT_REASON_UNKNOWN
+ : data.getInt(CLIENT_DATA_UNSELECT_REASON,
+ MediaRouter.UNSELECT_REASON_UNKNOWN);
+ return service.onUnselectRoute(messenger, requestId, arg, reason);
+
+ case CLIENT_MSG_SET_ROUTE_VOLUME: {
+ int volume = data.getInt(CLIENT_DATA_VOLUME, -1);
+ if (volume >= 0) {
+ return service.onSetRouteVolume(
+ messenger, requestId, arg, volume);
+ }
+ break;
+ }
+
+ case CLIENT_MSG_UPDATE_ROUTE_VOLUME: {
+ int delta = data.getInt(CLIENT_DATA_VOLUME, 0);
+ if (delta != 0) {
+ return service.onUpdateRouteVolume(
+ messenger, requestId, arg, delta);
+ }
+ break;
+ }
+
+ case CLIENT_MSG_ROUTE_CONTROL_REQUEST:
+ if (obj instanceof Intent) {
+ return service.onRouteControlRequest(
+ messenger, requestId, arg, (Intent)obj);
+ }
+ break;
+
+ case CLIENT_MSG_SET_DISCOVERY_REQUEST: {
+ if (obj == null || obj instanceof Bundle) {
+ MediaRouteDiscoveryRequest request =
+ MediaRouteDiscoveryRequest.fromBundle((Bundle)obj);
+ return service.onSetDiscoveryRequest(
+ messenger, requestId,
+ request != null && request.isValid() ? request : null);
+ }
+ }
+ }
+ }
+ return false;
+ }
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaRouteSelector.java b/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaRouteSelector.java
new file mode 100644
index 0000000..5669b19
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaRouteSelector.java
@@ -0,0 +1,308 @@
+/*
+ * 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.support.mediarouter.media;
+
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Describes the capabilities of routes that applications would like to discover and use.
+ * <p>
+ * This object is immutable once created using a {@link Builder} instance.
+ * </p>
+ *
+ * <h3>Example</h3>
+ * <pre>
+ * MediaRouteSelector selectorBuilder = new MediaRouteSelector.Builder()
+ * .addControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO)
+ * .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
+ * .build();
+ *
+ * MediaRouter router = MediaRouter.getInstance(context);
+ * router.addCallback(selector, callback, MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
+ * </pre>
+ */
+public final class MediaRouteSelector {
+ static final String KEY_CONTROL_CATEGORIES = "controlCategories";
+
+ private final Bundle mBundle;
+ List<String> mControlCategories;
+
+ /**
+ * An empty media route selector that will not match any routes.
+ */
+ public static final MediaRouteSelector EMPTY = new MediaRouteSelector(new Bundle(), null);
+
+ MediaRouteSelector(Bundle bundle, List<String> controlCategories) {
+ mBundle = bundle;
+ mControlCategories = controlCategories;
+ }
+
+ /**
+ * Gets the list of {@link MediaControlIntent media control categories} in the selector.
+ *
+ * @return The list of categories.
+ */
+ public List<String> getControlCategories() {
+ ensureControlCategories();
+ return mControlCategories;
+ }
+
+ void ensureControlCategories() {
+ if (mControlCategories == null) {
+ mControlCategories = mBundle.getStringArrayList(KEY_CONTROL_CATEGORIES);
+ if (mControlCategories == null || mControlCategories.isEmpty()) {
+ mControlCategories = Collections.<String>emptyList();
+ }
+ }
+ }
+
+ /**
+ * Returns true if the selector contains the specified category.
+ *
+ * @param category The category to check.
+ * @return True if the category is present.
+ */
+ public boolean hasControlCategory(String category) {
+ if (category != null) {
+ ensureControlCategories();
+ final int categoryCount = mControlCategories.size();
+ for (int i = 0; i < categoryCount; i++) {
+ if (mControlCategories.get(i).equals(category)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if the selector matches at least one of the specified control filters.
+ *
+ * @param filters The list of control filters to consider.
+ * @return True if a match is found.
+ */
+ public boolean matchesControlFilters(List<IntentFilter> filters) {
+ if (filters != null) {
+ ensureControlCategories();
+ final int categoryCount = mControlCategories.size();
+ if (categoryCount != 0) {
+ final int filterCount = filters.size();
+ for (int i = 0; i < filterCount; i++) {
+ final IntentFilter filter = filters.get(i);
+ if (filter != null) {
+ for (int j = 0; j < categoryCount; j++) {
+ if (filter.hasCategory(mControlCategories.get(j))) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if this selector contains all of the capabilities described
+ * by the specified selector.
+ *
+ * @param selector The selector to be examined.
+ * @return True if this selector contains all of the capabilities described
+ * by the specified selector.
+ */
+ public boolean contains(MediaRouteSelector selector) {
+ if (selector != null) {
+ ensureControlCategories();
+ selector.ensureControlCategories();
+ return mControlCategories.containsAll(selector.mControlCategories);
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if the selector does not specify any capabilities.
+ */
+ public boolean isEmpty() {
+ ensureControlCategories();
+ return mControlCategories.isEmpty();
+ }
+
+ /**
+ * Returns true if the selector has all of the required fields.
+ */
+ public boolean isValid() {
+ ensureControlCategories();
+ if (mControlCategories.contains(null)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof MediaRouteSelector) {
+ MediaRouteSelector other = (MediaRouteSelector)o;
+ ensureControlCategories();
+ other.ensureControlCategories();
+ return mControlCategories.equals(other.mControlCategories);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ ensureControlCategories();
+ return mControlCategories.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder();
+ result.append("MediaRouteSelector{ ");
+ result.append("controlCategories=").append(
+ Arrays.toString(getControlCategories().toArray()));
+ result.append(" }");
+ return result.toString();
+ }
+
+ /**
+ * Converts this object to a bundle for serialization.
+ *
+ * @return The contents of the object represented as a bundle.
+ */
+ public Bundle asBundle() {
+ return mBundle;
+ }
+
+ /**
+ * Creates an instance from a bundle.
+ *
+ * @param bundle The bundle, or null if none.
+ * @return The new instance, or null if the bundle was null.
+ */
+ public static MediaRouteSelector fromBundle(@Nullable Bundle bundle) {
+ return bundle != null ? new MediaRouteSelector(bundle, null) : null;
+ }
+
+ /**
+ * Builder for {@link MediaRouteSelector media route selectors}.
+ */
+ public static final class Builder {
+ private ArrayList<String> mControlCategories;
+
+ /**
+ * Creates an empty media route selector builder.
+ */
+ public Builder() {
+ }
+
+ /**
+ * Creates a media route selector descriptor builder whose initial contents are
+ * copied from an existing selector.
+ */
+ public Builder(@NonNull MediaRouteSelector selector) {
+ if (selector == null) {
+ throw new IllegalArgumentException("selector must not be null");
+ }
+
+ selector.ensureControlCategories();
+ if (!selector.mControlCategories.isEmpty()) {
+ mControlCategories = new ArrayList<String>(selector.mControlCategories);
+ }
+ }
+
+ /**
+ * Adds a {@link MediaControlIntent media control category} to the builder.
+ *
+ * @param category The category to add to the set of desired capabilities, such as
+ * {@link MediaControlIntent#CATEGORY_LIVE_AUDIO}.
+ * @return The builder instance for chaining.
+ */
+ @NonNull
+ public Builder addControlCategory(@NonNull String category) {
+ if (category == null) {
+ throw new IllegalArgumentException("category must not be null");
+ }
+
+ if (mControlCategories == null) {
+ mControlCategories = new ArrayList<String>();
+ }
+ if (!mControlCategories.contains(category)) {
+ mControlCategories.add(category);
+ }
+ return this;
+ }
+
+ /**
+ * Adds a list of {@link MediaControlIntent media control categories} to the builder.
+ *
+ * @param categories The list categories to add to the set of desired capabilities,
+ * such as {@link MediaControlIntent#CATEGORY_LIVE_AUDIO}.
+ * @return The builder instance for chaining.
+ */
+ @NonNull
+ public Builder addControlCategories(@NonNull Collection<String> categories) {
+ if (categories == null) {
+ throw new IllegalArgumentException("categories must not be null");
+ }
+
+ if (!categories.isEmpty()) {
+ for (String category : categories) {
+ addControlCategory(category);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Adds the contents of an existing media route selector to the builder.
+ *
+ * @param selector The media route selector whose contents are to be added.
+ * @return The builder instance for chaining.
+ */
+ @NonNull
+ public Builder addSelector(@NonNull MediaRouteSelector selector) {
+ if (selector == null) {
+ throw new IllegalArgumentException("selector must not be null");
+ }
+
+ addControlCategories(selector.getControlCategories());
+ return this;
+ }
+
+ /**
+ * Builds the {@link MediaRouteSelector media route selector}.
+ */
+ @NonNull
+ public MediaRouteSelector build() {
+ if (mControlCategories == null) {
+ return EMPTY;
+ }
+ Bundle bundle = new Bundle();
+ bundle.putStringArrayList(KEY_CONTROL_CATEGORIES, mControlCategories);
+ return new MediaRouteSelector(bundle, mControlCategories);
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaRouter.java b/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaRouter.java
new file mode 100644
index 0000000..db0052e
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaRouter.java
@@ -0,0 +1,2999 @@
+/*
+ * 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.support.mediarouter.media;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.support.v4.app.ActivityManagerCompat;
+import android.support.v4.hardware.display.DisplayManagerCompat;
+import android.support.v4.media.VolumeProviderCompat;
+import android.support.v4.media.session.MediaSessionCompat;
+import android.support.v4.util.Pair;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.Display;
+
+import com.android.support.mediarouter.media.MediaRouteProvider.ProviderMetadata;
+import com.android.support.mediarouter.media.MediaRouteProvider.RouteController;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * MediaRouter allows applications to control the routing of media channels
+ * and streams from the current device to external speakers and destination devices.
+ * <p>
+ * A MediaRouter instance is retrieved through {@link #getInstance}. Applications
+ * can query the media router about the currently selected route and its capabilities
+ * to determine how to send content to the route's destination. Applications can
+ * also {@link RouteInfo#sendControlRequest send control requests} to the route
+ * to ask the route's destination to perform certain remote control functions
+ * such as playing media.
+ * </p><p>
+ * See also {@link MediaRouteProvider} for information on how an application
+ * can publish new media routes to the media router.
+ * </p><p>
+ * The media router API is not thread-safe; all interactions with it must be
+ * done from the main thread of the process.
+ * </p>
+ */
+public final class MediaRouter {
+ static final String TAG = "MediaRouter";
+ static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ /**
+ * Passed to {@link android.support.v7.media.MediaRouteProvider.RouteController#onUnselect(int)}
+ * and {@link Callback#onRouteUnselected(MediaRouter, RouteInfo, int)} when the reason the route
+ * was unselected is unknown.
+ */
+ public static final int UNSELECT_REASON_UNKNOWN = 0;
+ /**
+ * Passed to {@link android.support.v7.media.MediaRouteProvider.RouteController#onUnselect(int)}
+ * and {@link Callback#onRouteUnselected(MediaRouter, RouteInfo, int)} when the user pressed
+ * the disconnect button to disconnect and keep playing.
+ * <p>
+ *
+ * @see MediaRouteDescriptor#canDisconnectAndKeepPlaying()
+ */
+ public static final int UNSELECT_REASON_DISCONNECTED = 1;
+ /**
+ * Passed to {@link android.support.v7.media.MediaRouteProvider.RouteController#onUnselect(int)}
+ * and {@link Callback#onRouteUnselected(MediaRouter, RouteInfo, int)} when the user pressed
+ * the stop casting button.
+ */
+ public static final int UNSELECT_REASON_STOPPED = 2;
+ /**
+ * Passed to {@link android.support.v7.media.MediaRouteProvider.RouteController#onUnselect(int)}
+ * and {@link Callback#onRouteUnselected(MediaRouter, RouteInfo, int)} when the user selected
+ * a different route.
+ */
+ public static final int UNSELECT_REASON_ROUTE_CHANGED = 3;
+
+ // Maintains global media router state for the process.
+ // This field is initialized in MediaRouter.getInstance() before any
+ // MediaRouter objects are instantiated so it is guaranteed to be
+ // valid whenever any instance method is invoked.
+ static GlobalMediaRouter sGlobal;
+
+ // Context-bound state of the media router.
+ final Context mContext;
+ final ArrayList<CallbackRecord> mCallbackRecords = new ArrayList<CallbackRecord>();
+
+ @IntDef(flag = true,
+ value = {
+ CALLBACK_FLAG_PERFORM_ACTIVE_SCAN,
+ CALLBACK_FLAG_REQUEST_DISCOVERY,
+ CALLBACK_FLAG_UNFILTERED_EVENTS
+ }
+ )
+ @Retention(RetentionPolicy.SOURCE)
+ private @interface CallbackFlags {}
+
+ /**
+ * Flag for {@link #addCallback}: Actively scan for routes while this callback
+ * is registered.
+ * <p>
+ * When this flag is specified, the media router will actively scan for new
+ * routes. Certain routes, such as wifi display routes, may not be discoverable
+ * except when actively scanning. This flag is typically used when the route picker
+ * dialog has been opened by the user to ensure that the route information is
+ * up to date.
+ * </p><p>
+ * Active scanning may consume a significant amount of power and may have intrusive
+ * effects on wireless connectivity. Therefore it is important that active scanning
+ * only be requested when it is actually needed to satisfy a user request to
+ * discover and select a new route.
+ * </p><p>
+ * This flag implies {@link #CALLBACK_FLAG_REQUEST_DISCOVERY} but performing
+ * active scans is much more expensive than a normal discovery request.
+ * </p>
+ *
+ * @see #CALLBACK_FLAG_REQUEST_DISCOVERY
+ */
+ public static final int CALLBACK_FLAG_PERFORM_ACTIVE_SCAN = 1 << 0;
+
+ /**
+ * Flag for {@link #addCallback}: Do not filter route events.
+ * <p>
+ * When this flag is specified, the callback will be invoked for events that affect any
+ * route even if they do not match the callback's filter.
+ * </p>
+ */
+ public static final int CALLBACK_FLAG_UNFILTERED_EVENTS = 1 << 1;
+
+ /**
+ * Flag for {@link #addCallback}: Request passive route discovery while this
+ * callback is registered, except on {@link ActivityManager#isLowRamDevice low-RAM devices}.
+ * <p>
+ * When this flag is specified, the media router will try to discover routes.
+ * Although route discovery is intended to be efficient, checking for new routes may
+ * result in some network activity and could slowly drain the battery. Therefore
+ * applications should only specify {@link #CALLBACK_FLAG_REQUEST_DISCOVERY} when
+ * they are running in the foreground and would like to provide the user with the
+ * option of connecting to new routes.
+ * </p><p>
+ * Applications should typically add a callback using this flag in the
+ * {@link android.app.Activity activity's} {@link android.app.Activity#onStart onStart}
+ * method and remove it in the {@link android.app.Activity#onStop onStop} method.
+ * The {@link android.support.v7.app.MediaRouteDiscoveryFragment} fragment may
+ * also be used for this purpose.
+ * </p><p class="note">
+ * On {@link ActivityManager#isLowRamDevice low-RAM devices} this flag
+ * will be ignored. Refer to
+ * {@link #addCallback(MediaRouteSelector, Callback, int) addCallback} for details.
+ * </p>
+ *
+ * @see android.support.v7.app.MediaRouteDiscoveryFragment
+ */
+ public static final int CALLBACK_FLAG_REQUEST_DISCOVERY = 1 << 2;
+
+ /**
+ * Flag for {@link #addCallback}: Request passive route discovery while this
+ * callback is registered, even on {@link ActivityManager#isLowRamDevice low-RAM devices}.
+ * <p class="note">
+ * This flag has a significant performance impact on low-RAM devices
+ * since it may cause many media route providers to be started simultaneously.
+ * It is much better to use {@link #CALLBACK_FLAG_REQUEST_DISCOVERY} instead to avoid
+ * performing passive discovery on these devices altogether. Refer to
+ * {@link #addCallback(MediaRouteSelector, Callback, int) addCallback} for details.
+ * </p>
+ *
+ * @see android.support.v7.app.MediaRouteDiscoveryFragment
+ */
+ public static final int CALLBACK_FLAG_FORCE_DISCOVERY = 1 << 3;
+
+ /**
+ * Flag for {@link #isRouteAvailable}: Ignore the default route.
+ * <p>
+ * This flag is used to determine whether a matching non-default route is available.
+ * This constraint may be used to decide whether to offer the route chooser dialog
+ * to the user. There is no point offering the chooser if there are no
+ * non-default choices.
+ * </p>
+ */
+ public static final int AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE = 1 << 0;
+
+ /**
+ * Flag for {@link #isRouteAvailable}: Require an actual route to be matched.
+ * <p>
+ * If this flag is not set, then {@link #isRouteAvailable} will return true
+ * if it is possible to discover a matching route even if discovery is not in
+ * progress or if no matching route has yet been found. This feature is used to
+ * save resources by removing the need to perform passive route discovery on
+ * {@link ActivityManager#isLowRamDevice low-RAM devices}.
+ * </p><p>
+ * If this flag is set, then {@link #isRouteAvailable} will only return true if
+ * a matching route has actually been discovered.
+ * </p>
+ */
+ public static final int AVAILABILITY_FLAG_REQUIRE_MATCH = 1 << 1;
+
+ private MediaRouter(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Gets an instance of the media router service associated with the context.
+ * <p>
+ * The application is responsible for holding a strong reference to the returned
+ * {@link MediaRouter} instance, such as by storing the instance in a field of
+ * the {@link android.app.Activity}, to ensure that the media router remains alive
+ * as long as the application is using its features.
+ * </p><p>
+ * In other words, the support library only holds a {@link WeakReference weak reference}
+ * to each media router instance. When there are no remaining strong references to the
+ * media router instance, all of its callbacks will be removed and route discovery
+ * will no longer be performed on its behalf.
+ * </p>
+ *
+ * @return The media router instance for the context. The application must hold
+ * a strong reference to this object as long as it is in use.
+ */
+ public static MediaRouter getInstance(@NonNull Context context) {
+ if (context == null) {
+ throw new IllegalArgumentException("context must not be null");
+ }
+ checkCallingThread();
+
+ if (sGlobal == null) {
+ sGlobal = new GlobalMediaRouter(context.getApplicationContext());
+ sGlobal.start();
+ }
+ return sGlobal.getRouter(context);
+ }
+
+ /**
+ * Gets information about the {@link MediaRouter.RouteInfo routes} currently known to
+ * this media router.
+ */
+ public List<RouteInfo> getRoutes() {
+ checkCallingThread();
+ return sGlobal.getRoutes();
+ }
+
+ /**
+ * Gets information about the {@link MediaRouter.ProviderInfo route providers}
+ * currently known to this media router.
+ */
+ public List<ProviderInfo> getProviders() {
+ checkCallingThread();
+ return sGlobal.getProviders();
+ }
+
+ /**
+ * Gets the default route for playing media content on the system.
+ * <p>
+ * The system always provides a default route.
+ * </p>
+ *
+ * @return The default route, which is guaranteed to never be null.
+ */
+ @NonNull
+ public RouteInfo getDefaultRoute() {
+ checkCallingThread();
+ return sGlobal.getDefaultRoute();
+ }
+
+ /**
+ * Gets a bluetooth route for playing media content on the system.
+ *
+ * @return A bluetooth route, if exist, otherwise null.
+ */
+ public RouteInfo getBluetoothRoute() {
+ checkCallingThread();
+ return sGlobal.getBluetoothRoute();
+ }
+
+ /**
+ * Gets the currently selected route.
+ * <p>
+ * The application should examine the route's
+ * {@link RouteInfo#getControlFilters media control intent filters} to assess the
+ * capabilities of the route before attempting to use it.
+ * </p>
+ *
+ * <h3>Example</h3>
+ * <pre>
+ * public boolean playMovie() {
+ * MediaRouter mediaRouter = MediaRouter.getInstance(context);
+ * MediaRouter.RouteInfo route = mediaRouter.getSelectedRoute();
+ *
+ * // First try using the remote playback interface, if supported.
+ * if (route.supportsControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) {
+ * // The route supports remote playback.
+ * // Try to send it the Uri of the movie to play.
+ * Intent intent = new Intent(MediaControlIntent.ACTION_PLAY);
+ * intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
+ * intent.setDataAndType("http://example.com/videos/movie.mp4", "video/mp4");
+ * if (route.supportsControlRequest(intent)) {
+ * route.sendControlRequest(intent, null);
+ * return true; // sent the request to play the movie
+ * }
+ * }
+ *
+ * // If remote playback was not possible, then play locally.
+ * if (route.supportsControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO)) {
+ * // The route supports live video streaming.
+ * // Prepare to play content locally in a window or in a presentation.
+ * return playMovieInWindow();
+ * }
+ *
+ * // Neither interface is supported, so we can't play the movie to this route.
+ * return false;
+ * }
+ * </pre>
+ *
+ * @return The selected route, which is guaranteed to never be null.
+ *
+ * @see RouteInfo#getControlFilters
+ * @see RouteInfo#supportsControlCategory
+ * @see RouteInfo#supportsControlRequest
+ */
+ @NonNull
+ public RouteInfo getSelectedRoute() {
+ checkCallingThread();
+ return sGlobal.getSelectedRoute();
+ }
+
+ /**
+ * Returns the selected route if it matches the specified selector, otherwise
+ * selects the default route and returns it. If there is one live audio route
+ * (usually Bluetooth A2DP), it will be selected instead of default route.
+ *
+ * @param selector The selector to match.
+ * @return The previously selected route if it matched the selector, otherwise the
+ * newly selected default route which is guaranteed to never be null.
+ *
+ * @see MediaRouteSelector
+ * @see RouteInfo#matchesSelector
+ */
+ @NonNull
+ public RouteInfo updateSelectedRoute(@NonNull MediaRouteSelector selector) {
+ if (selector == null) {
+ throw new IllegalArgumentException("selector must not be null");
+ }
+ checkCallingThread();
+
+ if (DEBUG) {
+ Log.d(TAG, "updateSelectedRoute: " + selector);
+ }
+ RouteInfo route = sGlobal.getSelectedRoute();
+ if (!route.isDefaultOrBluetooth() && !route.matchesSelector(selector)) {
+ route = sGlobal.chooseFallbackRoute();
+ sGlobal.selectRoute(route);
+ }
+ return route;
+ }
+
+ /**
+ * Selects the specified route.
+ *
+ * @param route The route to select.
+ */
+ public void selectRoute(@NonNull RouteInfo route) {
+ if (route == null) {
+ throw new IllegalArgumentException("route must not be null");
+ }
+ checkCallingThread();
+
+ if (DEBUG) {
+ Log.d(TAG, "selectRoute: " + route);
+ }
+ sGlobal.selectRoute(route);
+ }
+
+ /**
+ * Unselects the current round and selects the default route instead.
+ * <p>
+ * The reason given must be one of:
+ * <ul>
+ * <li>{@link MediaRouter#UNSELECT_REASON_UNKNOWN}</li>
+ * <li>{@link MediaRouter#UNSELECT_REASON_DISCONNECTED}</li>
+ * <li>{@link MediaRouter#UNSELECT_REASON_STOPPED}</li>
+ * <li>{@link MediaRouter#UNSELECT_REASON_ROUTE_CHANGED}</li>
+ * </ul>
+ *
+ * @param reason The reason for disconnecting the current route.
+ */
+ public void unselect(int reason) {
+ if (reason < MediaRouter.UNSELECT_REASON_UNKNOWN ||
+ reason > MediaRouter.UNSELECT_REASON_ROUTE_CHANGED) {
+ throw new IllegalArgumentException("Unsupported reason to unselect route");
+ }
+ checkCallingThread();
+
+ // Choose the fallback route if it's not already selected.
+ // Otherwise, select the default route.
+ RouteInfo fallbackRoute = sGlobal.chooseFallbackRoute();
+ if (sGlobal.getSelectedRoute() != fallbackRoute) {
+ sGlobal.selectRoute(fallbackRoute, reason);
+ } else {
+ sGlobal.selectRoute(sGlobal.getDefaultRoute(), reason);
+ }
+ }
+
+ /**
+ * Returns true if there is a route that matches the specified selector.
+ * <p>
+ * This method returns true if there are any available routes that match the
+ * selector regardless of whether they are enabled or disabled. If the
+ * {@link #AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE} flag is specified, then
+ * the method will only consider non-default routes.
+ * </p>
+ * <p class="note">
+ * On {@link ActivityManager#isLowRamDevice low-RAM devices} this method
+ * will return true if it is possible to discover a matching route even if
+ * discovery is not in progress or if no matching route has yet been found.
+ * Use {@link #AVAILABILITY_FLAG_REQUIRE_MATCH} to require an actual match.
+ * </p>
+ *
+ * @param selector The selector to match.
+ * @param flags Flags to control the determination of whether a route may be
+ * available. May be zero or some combination of
+ * {@link #AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE} and
+ * {@link #AVAILABILITY_FLAG_REQUIRE_MATCH}.
+ * @return True if a matching route may be available.
+ */
+ public boolean isRouteAvailable(@NonNull MediaRouteSelector selector, int flags) {
+ if (selector == null) {
+ throw new IllegalArgumentException("selector must not be null");
+ }
+ checkCallingThread();
+
+ return sGlobal.isRouteAvailable(selector, flags);
+ }
+
+ /**
+ * Registers a callback to discover routes that match the selector and to receive
+ * events when they change.
+ * <p>
+ * This is a convenience method that has the same effect as calling
+ * {@link #addCallback(MediaRouteSelector, Callback, int)} without flags.
+ * </p>
+ *
+ * @param selector A route selector that indicates the kinds of routes that the
+ * callback would like to discover.
+ * @param callback The callback to add.
+ * @see #removeCallback
+ */
+ public void addCallback(MediaRouteSelector selector, Callback callback) {
+ addCallback(selector, callback, 0);
+ }
+
+ /**
+ * Registers a callback to discover routes that match the selector and to receive
+ * events when they change.
+ * <p>
+ * The selector describes the kinds of routes that the application wants to
+ * discover. For example, if the application wants to use
+ * live audio routes then it should include the
+ * {@link MediaControlIntent#CATEGORY_LIVE_AUDIO live audio media control intent category}
+ * in its selector when it adds a callback to the media router.
+ * The selector may include any number of categories.
+ * </p><p>
+ * If the callback has already been registered, then the selector is added to
+ * the set of selectors being monitored by the callback.
+ * </p><p>
+ * By default, the callback will only be invoked for events that affect routes
+ * that match the specified selector. Event filtering may be disabled by specifying
+ * the {@link #CALLBACK_FLAG_UNFILTERED_EVENTS} flag when the callback is registered.
+ * </p><p>
+ * Applications should use the {@link #isRouteAvailable} method to determine
+ * whether is it possible to discover a route with the desired capabilities
+ * and therefore whether the media route button should be shown to the user.
+ * </p><p>
+ * The {@link #CALLBACK_FLAG_REQUEST_DISCOVERY} flag should be used while the application
+ * is in the foreground to request that passive discovery be performed if there are
+ * sufficient resources to allow continuous passive discovery.
+ * On {@link ActivityManager#isLowRamDevice low-RAM devices} this flag will be
+ * ignored to conserve resources.
+ * </p><p>
+ * The {@link #CALLBACK_FLAG_FORCE_DISCOVERY} flag should be used when
+ * passive discovery absolutely must be performed, even on low-RAM devices.
+ * This flag has a significant performance impact on low-RAM devices
+ * since it may cause many media route providers to be started simultaneously.
+ * It is much better to use {@link #CALLBACK_FLAG_REQUEST_DISCOVERY} instead to avoid
+ * performing passive discovery on these devices altogether.
+ * </p><p>
+ * The {@link #CALLBACK_FLAG_PERFORM_ACTIVE_SCAN} flag should be used when the
+ * media route chooser dialog is showing to confirm the presence of available
+ * routes that the user may connect to. This flag may use substantially more
+ * power.
+ * </p>
+ *
+ * <h3>Example</h3>
+ * <pre>
+ * public class MyActivity extends Activity {
+ * private MediaRouter mRouter;
+ * private MediaRouter.Callback mCallback;
+ * private MediaRouteSelector mSelector;
+ *
+ * protected void onCreate(Bundle savedInstanceState) {
+ * super.onCreate(savedInstanceState);
+ *
+ * mRouter = Mediarouter.getInstance(this);
+ * mCallback = new MyCallback();
+ * mSelector = new MediaRouteSelector.Builder()
+ * .addControlCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO)
+ * .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
+ * .build();
+ * }
+ *
+ * // Add the callback on start to tell the media router what kinds of routes
+ * // the application is interested in so that it can try to discover suitable ones.
+ * public void onStart() {
+ * super.onStart();
+ *
+ * mediaRouter.addCallback(mSelector, mCallback,
+ * MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
+ *
+ * MediaRouter.RouteInfo route = mediaRouter.updateSelectedRoute(mSelector);
+ * // do something with the route...
+ * }
+ *
+ * // Remove the selector on stop to tell the media router that it no longer
+ * // needs to invest effort trying to discover routes of these kinds for now.
+ * public void onStop() {
+ * super.onStop();
+ *
+ * mediaRouter.removeCallback(mCallback);
+ * }
+ *
+ * private final class MyCallback extends MediaRouter.Callback {
+ * // Implement callback methods as needed.
+ * }
+ * }
+ * </pre>
+ *
+ * @param selector A route selector that indicates the kinds of routes that the
+ * callback would like to discover.
+ * @param callback The callback to add.
+ * @param flags Flags to control the behavior of the callback.
+ * May be zero or a combination of {@link #CALLBACK_FLAG_PERFORM_ACTIVE_SCAN} and
+ * {@link #CALLBACK_FLAG_UNFILTERED_EVENTS}.
+ * @see #removeCallback
+ */
+ public void addCallback(@NonNull MediaRouteSelector selector, @NonNull Callback callback,
+ @CallbackFlags int flags) {
+ if (selector == null) {
+ throw new IllegalArgumentException("selector must not be null");
+ }
+ if (callback == null) {
+ throw new IllegalArgumentException("callback must not be null");
+ }
+ checkCallingThread();
+
+ if (DEBUG) {
+ Log.d(TAG, "addCallback: selector=" + selector
+ + ", callback=" + callback + ", flags=" + Integer.toHexString(flags));
+ }
+
+ CallbackRecord record;
+ int index = findCallbackRecord(callback);
+ if (index < 0) {
+ record = new CallbackRecord(this, callback);
+ mCallbackRecords.add(record);
+ } else {
+ record = mCallbackRecords.get(index);
+ }
+ boolean updateNeeded = false;
+ if ((flags & ~record.mFlags) != 0) {
+ record.mFlags |= flags;
+ updateNeeded = true;
+ }
+ if (!record.mSelector.contains(selector)) {
+ record.mSelector = new MediaRouteSelector.Builder(record.mSelector)
+ .addSelector(selector)
+ .build();
+ updateNeeded = true;
+ }
+ if (updateNeeded) {
+ sGlobal.updateDiscoveryRequest();
+ }
+ }
+
+ /**
+ * Removes the specified callback. It will no longer receive events about
+ * changes to media routes.
+ *
+ * @param callback The callback to remove.
+ * @see #addCallback
+ */
+ public void removeCallback(@NonNull Callback callback) {
+ if (callback == null) {
+ throw new IllegalArgumentException("callback must not be null");
+ }
+ checkCallingThread();
+
+ if (DEBUG) {
+ Log.d(TAG, "removeCallback: callback=" + callback);
+ }
+
+ int index = findCallbackRecord(callback);
+ if (index >= 0) {
+ mCallbackRecords.remove(index);
+ sGlobal.updateDiscoveryRequest();
+ }
+ }
+
+ private int findCallbackRecord(Callback callback) {
+ final int count = mCallbackRecords.size();
+ for (int i = 0; i < count; i++) {
+ if (mCallbackRecords.get(i).mCallback == callback) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Registers a media route provider within this application process.
+ * <p>
+ * The provider will be added to the list of providers that all {@link MediaRouter}
+ * instances within this process can use to discover routes.
+ * </p>
+ *
+ * @param providerInstance The media route provider instance to add.
+ *
+ * @see MediaRouteProvider
+ * @see #removeCallback
+ */
+ public void addProvider(@NonNull MediaRouteProvider providerInstance) {
+ if (providerInstance == null) {
+ throw new IllegalArgumentException("providerInstance must not be null");
+ }
+ checkCallingThread();
+
+ if (DEBUG) {
+ Log.d(TAG, "addProvider: " + providerInstance);
+ }
+ sGlobal.addProvider(providerInstance);
+ }
+
+ /**
+ * Unregisters a media route provider within this application process.
+ * <p>
+ * The provider will be removed from the list of providers that all {@link MediaRouter}
+ * instances within this process can use to discover routes.
+ * </p>
+ *
+ * @param providerInstance The media route provider instance to remove.
+ *
+ * @see MediaRouteProvider
+ * @see #addCallback
+ */
+ public void removeProvider(@NonNull MediaRouteProvider providerInstance) {
+ if (providerInstance == null) {
+ throw new IllegalArgumentException("providerInstance must not be null");
+ }
+ checkCallingThread();
+
+ if (DEBUG) {
+ Log.d(TAG, "removeProvider: " + providerInstance);
+ }
+ sGlobal.removeProvider(providerInstance);
+ }
+
+ /**
+ * Adds a remote control client to enable remote control of the volume
+ * of the selected route.
+ * <p>
+ * The remote control client must have previously been registered with
+ * the audio manager using the {@link android.media.AudioManager#registerRemoteControlClient
+ * AudioManager.registerRemoteControlClient} method.
+ * </p>
+ *
+ * @param remoteControlClient The {@link android.media.RemoteControlClient} to register.
+ */
+ public void addRemoteControlClient(@NonNull Object remoteControlClient) {
+ if (remoteControlClient == null) {
+ throw new IllegalArgumentException("remoteControlClient must not be null");
+ }
+ checkCallingThread();
+
+ if (DEBUG) {
+ Log.d(TAG, "addRemoteControlClient: " + remoteControlClient);
+ }
+ sGlobal.addRemoteControlClient(remoteControlClient);
+ }
+
+ /**
+ * Removes a remote control client.
+ *
+ * @param remoteControlClient The {@link android.media.RemoteControlClient}
+ * to unregister.
+ */
+ public void removeRemoteControlClient(@NonNull Object remoteControlClient) {
+ if (remoteControlClient == null) {
+ throw new IllegalArgumentException("remoteControlClient must not be null");
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "removeRemoteControlClient: " + remoteControlClient);
+ }
+ sGlobal.removeRemoteControlClient(remoteControlClient);
+ }
+
+ /**
+ * Sets the media session to enable remote control of the volume of the
+ * selected route. This should be used instead of
+ * {@link #addRemoteControlClient} when using media sessions. Set the
+ * session to null to clear it.
+ *
+ * @param mediaSession The {@link android.media.session.MediaSession} to
+ * use.
+ */
+ public void setMediaSession(Object mediaSession) {
+ if (DEBUG) {
+ Log.d(TAG, "addMediaSession: " + mediaSession);
+ }
+ sGlobal.setMediaSession(mediaSession);
+ }
+
+ /**
+ * Sets a compat media session to enable remote control of the volume of the
+ * selected route. This should be used instead of
+ * {@link #addRemoteControlClient} when using {@link MediaSessionCompat}.
+ * Set the session to null to clear it.
+ *
+ * @param mediaSession
+ */
+ public void setMediaSessionCompat(MediaSessionCompat mediaSession) {
+ if (DEBUG) {
+ Log.d(TAG, "addMediaSessionCompat: " + mediaSession);
+ }
+ sGlobal.setMediaSessionCompat(mediaSession);
+ }
+
+ public MediaSessionCompat.Token getMediaSessionToken() {
+ return sGlobal.getMediaSessionToken();
+ }
+
+ /**
+ * Ensures that calls into the media router are on the correct thread.
+ * It pays to be a little paranoid when global state invariants are at risk.
+ */
+ static void checkCallingThread() {
+ if (Looper.myLooper() != Looper.getMainLooper()) {
+ throw new IllegalStateException("The media router service must only be "
+ + "accessed on the application's main thread.");
+ }
+ }
+
+ static <T> boolean equal(T a, T b) {
+ return a == b || (a != null && b != null && a.equals(b));
+ }
+
+ /**
+ * Provides information about a media route.
+ * <p>
+ * Each media route has a list of {@link MediaControlIntent media control}
+ * {@link #getControlFilters intent filters} that describe the capabilities of the
+ * route and the manner in which it is used and controlled.
+ * </p>
+ */
+ public static class RouteInfo {
+ private final ProviderInfo mProvider;
+ private final String mDescriptorId;
+ private final String mUniqueId;
+ private String mName;
+ private String mDescription;
+ private Uri mIconUri;
+ private boolean mEnabled;
+ private boolean mConnecting;
+ private int mConnectionState;
+ private boolean mCanDisconnect;
+ private final ArrayList<IntentFilter> mControlFilters = new ArrayList<>();
+ private int mPlaybackType;
+ private int mPlaybackStream;
+ private int mDeviceType;
+ private int mVolumeHandling;
+ private int mVolume;
+ private int mVolumeMax;
+ private Display mPresentationDisplay;
+ private int mPresentationDisplayId = PRESENTATION_DISPLAY_ID_NONE;
+ private Bundle mExtras;
+ private IntentSender mSettingsIntent;
+ MediaRouteDescriptor mDescriptor;
+
+ @IntDef({CONNECTION_STATE_DISCONNECTED, CONNECTION_STATE_CONNECTING,
+ CONNECTION_STATE_CONNECTED})
+ @Retention(RetentionPolicy.SOURCE)
+ private @interface ConnectionState {}
+
+ /**
+ * The default connection state indicating the route is disconnected.
+ *
+ * @see #getConnectionState
+ */
+ public static final int CONNECTION_STATE_DISCONNECTED = 0;
+
+ /**
+ * A connection state indicating the route is in the process of connecting and is not yet
+ * ready for use.
+ *
+ * @see #getConnectionState
+ */
+ public static final int CONNECTION_STATE_CONNECTING = 1;
+
+ /**
+ * A connection state indicating the route is connected.
+ *
+ * @see #getConnectionState
+ */
+ public static final int CONNECTION_STATE_CONNECTED = 2;
+
+ @IntDef({PLAYBACK_TYPE_LOCAL,PLAYBACK_TYPE_REMOTE})
+ @Retention(RetentionPolicy.SOURCE)
+ private @interface PlaybackType {}
+
+ /**
+ * The default playback type, "local", indicating the presentation of the media
+ * is happening on the same device (e.g. a phone, a tablet) as where it is
+ * controlled from.
+ *
+ * @see #getPlaybackType
+ */
+ public static final int PLAYBACK_TYPE_LOCAL = 0;
+
+ /**
+ * A playback type indicating the presentation of the media is happening on
+ * a different device (i.e. the remote device) than where it is controlled from.
+ *
+ * @see #getPlaybackType
+ */
+ public static final int PLAYBACK_TYPE_REMOTE = 1;
+
+ @IntDef({DEVICE_TYPE_UNKNOWN, DEVICE_TYPE_TV, DEVICE_TYPE_SPEAKER, DEVICE_TYPE_BLUETOOTH})
+ @Retention(RetentionPolicy.SOURCE)
+ private @interface DeviceType {}
+
+ /**
+ * The default receiver device type of the route indicating the type is unknown.
+ *
+ * @see #getDeviceType
+ * @hide
+ */
+ // @RestrictTo(LIBRARY_GROUP)
+ public static final int DEVICE_TYPE_UNKNOWN = 0;
+
+ /**
+ * A receiver device type of the route indicating the presentation of the media is happening
+ * on a TV.
+ *
+ * @see #getDeviceType
+ */
+ public static final int DEVICE_TYPE_TV = 1;
+
+ /**
+ * A receiver device type of the route indicating the presentation of the media is happening
+ * on a speaker.
+ *
+ * @see #getDeviceType
+ */
+ public static final int DEVICE_TYPE_SPEAKER = 2;
+
+ /**
+ * A receiver device type of the route indicating the presentation of the media is happening
+ * on a bluetooth device such as a bluetooth speaker.
+ *
+ * @see #getDeviceType
+ * @hide
+ */
+ // @RestrictTo(LIBRARY_GROUP)
+ public static final int DEVICE_TYPE_BLUETOOTH = 3;
+
+ @IntDef({PLAYBACK_VOLUME_FIXED,PLAYBACK_VOLUME_VARIABLE})
+ @Retention(RetentionPolicy.SOURCE)
+ private @interface PlaybackVolume {}
+
+ /**
+ * Playback information indicating the playback volume is fixed, i.e. it cannot be
+ * controlled from this object. An example of fixed playback volume is a remote player,
+ * playing over HDMI where the user prefers to control the volume on the HDMI sink, rather
+ * than attenuate at the source.
+ *
+ * @see #getVolumeHandling
+ */
+ public static final int PLAYBACK_VOLUME_FIXED = 0;
+
+ /**
+ * Playback information indicating the playback volume is variable and can be controlled
+ * from this object.
+ *
+ * @see #getVolumeHandling
+ */
+ public static final int PLAYBACK_VOLUME_VARIABLE = 1;
+
+ /**
+ * The default presentation display id indicating no presentation display is associated
+ * with the route.
+ * @hide
+ */
+ // @RestrictTo(LIBRARY_GROUP)
+ public static final int PRESENTATION_DISPLAY_ID_NONE = -1;
+
+ static final int CHANGE_GENERAL = 1 << 0;
+ static final int CHANGE_VOLUME = 1 << 1;
+ static final int CHANGE_PRESENTATION_DISPLAY = 1 << 2;
+
+ // Should match to SystemMediaRouteProvider.PACKAGE_NAME.
+ static final String SYSTEM_MEDIA_ROUTE_PROVIDER_PACKAGE_NAME = "android";
+
+ RouteInfo(ProviderInfo provider, String descriptorId, String uniqueId) {
+ mProvider = provider;
+ mDescriptorId = descriptorId;
+ mUniqueId = uniqueId;
+ }
+
+ /**
+ * Gets information about the provider of this media route.
+ */
+ public ProviderInfo getProvider() {
+ return mProvider;
+ }
+
+ /**
+ * Gets the unique id of the route.
+ * <p>
+ * The route unique id functions as a stable identifier by which the route is known.
+ * For example, an application can use this id as a token to remember the
+ * selected route across restarts or to communicate its identity to a service.
+ * </p>
+ *
+ * @return The unique id of the route, never null.
+ */
+ @NonNull
+ public String getId() {
+ return mUniqueId;
+ }
+
+ /**
+ * Gets the user-visible name of the route.
+ * <p>
+ * The route name identifies the destination represented by the route.
+ * It may be a user-supplied name, an alias, or device serial number.
+ * </p>
+ *
+ * @return The user-visible name of a media route. This is the string presented
+ * to users who may select this as the active route.
+ */
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Gets the user-visible description of the route.
+ * <p>
+ * The route description describes the kind of destination represented by the route.
+ * It may be a user-supplied string, a model number or brand of device.
+ * </p>
+ *
+ * @return The description of the route, or null if none.
+ */
+ @Nullable
+ public String getDescription() {
+ return mDescription;
+ }
+
+ /**
+ * Gets the URI of the icon representing this route.
+ * <p>
+ * This icon will be used in picker UIs if available.
+ * </p>
+ *
+ * @return The URI of the icon representing this route, or null if none.
+ */
+ public Uri getIconUri() {
+ return mIconUri;
+ }
+
+ /**
+ * Returns true if this route is enabled and may be selected.
+ *
+ * @return True if this route is enabled.
+ */
+ public boolean isEnabled() {
+ return mEnabled;
+ }
+
+ /**
+ * Returns true if the route is in the process of connecting and is not
+ * yet ready for use.
+ *
+ * @return True if this route is in the process of connecting.
+ */
+ public boolean isConnecting() {
+ return mConnecting;
+ }
+
+ /**
+ * Gets the connection state of the route.
+ *
+ * @return The connection state of this route: {@link #CONNECTION_STATE_DISCONNECTED},
+ * {@link #CONNECTION_STATE_CONNECTING}, or {@link #CONNECTION_STATE_CONNECTED}.
+ */
+ @ConnectionState
+ public int getConnectionState() {
+ return mConnectionState;
+ }
+
+ /**
+ * Returns true if this route is currently selected.
+ *
+ * @return True if this route is currently selected.
+ *
+ * @see MediaRouter#getSelectedRoute
+ */
+ public boolean isSelected() {
+ checkCallingThread();
+ return sGlobal.getSelectedRoute() == this;
+ }
+
+ /**
+ * Returns true if this route is the default route.
+ *
+ * @return True if this route is the default route.
+ *
+ * @see MediaRouter#getDefaultRoute
+ */
+ public boolean isDefault() {
+ checkCallingThread();
+ return sGlobal.getDefaultRoute() == this;
+ }
+
+ /**
+ * Returns true if this route is a bluetooth route.
+ *
+ * @return True if this route is a bluetooth route.
+ *
+ * @see MediaRouter#getBluetoothRoute
+ */
+ public boolean isBluetooth() {
+ checkCallingThread();
+ return sGlobal.getBluetoothRoute() == this;
+ }
+
+ /**
+ * Returns true if this route is the default route and the device speaker.
+ *
+ * @return True if this route is the default route and the device speaker.
+ */
+ public boolean isDeviceSpeaker() {
+ int defaultAudioRouteNameResourceId = Resources.getSystem().getIdentifier(
+ "default_audio_route_name", "string", "android");
+ return isDefault()
+ && Resources.getSystem().getText(defaultAudioRouteNameResourceId).equals(mName);
+ }
+
+ /**
+ * Gets a list of {@link MediaControlIntent media control intent} filters that
+ * describe the capabilities of this route and the media control actions that
+ * it supports.
+ *
+ * @return A list of intent filters that specifies the media control intents that
+ * this route supports.
+ *
+ * @see MediaControlIntent
+ * @see #supportsControlCategory
+ * @see #supportsControlRequest
+ */
+ public List<IntentFilter> getControlFilters() {
+ return mControlFilters;
+ }
+
+ /**
+ * Returns true if the route supports at least one of the capabilities
+ * described by a media route selector.
+ *
+ * @param selector The selector that specifies the capabilities to check.
+ * @return True if the route supports at least one of the capabilities
+ * described in the media route selector.
+ */
+ public boolean matchesSelector(@NonNull MediaRouteSelector selector) {
+ if (selector == null) {
+ throw new IllegalArgumentException("selector must not be null");
+ }
+ checkCallingThread();
+ return selector.matchesControlFilters(mControlFilters);
+ }
+
+ /**
+ * Returns true if the route supports the specified
+ * {@link MediaControlIntent media control} category.
+ * <p>
+ * Media control categories describe the capabilities of this route
+ * such as whether it supports live audio streaming or remote playback.
+ * </p>
+ *
+ * @param category A {@link MediaControlIntent media control} category
+ * such as {@link MediaControlIntent#CATEGORY_LIVE_AUDIO},
+ * {@link MediaControlIntent#CATEGORY_LIVE_VIDEO},
+ * {@link MediaControlIntent#CATEGORY_REMOTE_PLAYBACK}, or a provider-defined
+ * media control category.
+ * @return True if the route supports the specified intent category.
+ *
+ * @see MediaControlIntent
+ * @see #getControlFilters
+ */
+ public boolean supportsControlCategory(@NonNull String category) {
+ if (category == null) {
+ throw new IllegalArgumentException("category must not be null");
+ }
+ checkCallingThread();
+
+ int count = mControlFilters.size();
+ for (int i = 0; i < count; i++) {
+ if (mControlFilters.get(i).hasCategory(category)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if the route supports the specified
+ * {@link MediaControlIntent media control} category and action.
+ * <p>
+ * Media control actions describe specific requests that an application
+ * can ask a route to perform.
+ * </p>
+ *
+ * @param category A {@link MediaControlIntent media control} category
+ * such as {@link MediaControlIntent#CATEGORY_LIVE_AUDIO},
+ * {@link MediaControlIntent#CATEGORY_LIVE_VIDEO},
+ * {@link MediaControlIntent#CATEGORY_REMOTE_PLAYBACK}, or a provider-defined
+ * media control category.
+ * @param action A {@link MediaControlIntent media control} action
+ * such as {@link MediaControlIntent#ACTION_PLAY}.
+ * @return True if the route supports the specified intent action.
+ *
+ * @see MediaControlIntent
+ * @see #getControlFilters
+ */
+ public boolean supportsControlAction(@NonNull String category, @NonNull String action) {
+ if (category == null) {
+ throw new IllegalArgumentException("category must not be null");
+ }
+ if (action == null) {
+ throw new IllegalArgumentException("action must not be null");
+ }
+ checkCallingThread();
+
+ int count = mControlFilters.size();
+ for (int i = 0; i < count; i++) {
+ IntentFilter filter = mControlFilters.get(i);
+ if (filter.hasCategory(category) && filter.hasAction(action)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if the route supports the specified
+ * {@link MediaControlIntent media control} request.
+ * <p>
+ * Media control requests are used to request the route to perform
+ * actions such as starting remote playback of a media item.
+ * </p>
+ *
+ * @param intent A {@link MediaControlIntent media control intent}.
+ * @return True if the route can handle the specified intent.
+ *
+ * @see MediaControlIntent
+ * @see #getControlFilters
+ */
+ public boolean supportsControlRequest(@NonNull Intent intent) {
+ if (intent == null) {
+ throw new IllegalArgumentException("intent must not be null");
+ }
+ checkCallingThread();
+
+ ContentResolver contentResolver = sGlobal.getContentResolver();
+ int count = mControlFilters.size();
+ for (int i = 0; i < count; i++) {
+ if (mControlFilters.get(i).match(contentResolver, intent, true, TAG) >= 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Sends a {@link MediaControlIntent media control} request to be performed
+ * asynchronously by the route's destination.
+ * <p>
+ * Media control requests are used to request the route to perform
+ * actions such as starting remote playback of a media item.
+ * </p><p>
+ * This function may only be called on a selected route. Control requests
+ * sent to unselected routes will fail.
+ * </p>
+ *
+ * @param intent A {@link MediaControlIntent media control intent}.
+ * @param callback A {@link ControlRequestCallback} to invoke with the result
+ * of the request, or null if no result is required.
+ *
+ * @see MediaControlIntent
+ */
+ public void sendControlRequest(@NonNull Intent intent,
+ @Nullable ControlRequestCallback callback) {
+ if (intent == null) {
+ throw new IllegalArgumentException("intent must not be null");
+ }
+ checkCallingThread();
+
+ sGlobal.sendControlRequest(this, intent, callback);
+ }
+
+ /**
+ * Gets the type of playback associated with this route.
+ *
+ * @return The type of playback associated with this route: {@link #PLAYBACK_TYPE_LOCAL}
+ * or {@link #PLAYBACK_TYPE_REMOTE}.
+ */
+ @PlaybackType
+ public int getPlaybackType() {
+ return mPlaybackType;
+ }
+
+ /**
+ * Gets the audio stream over which the playback associated with this route is performed.
+ *
+ * @return The stream over which the playback associated with this route is performed.
+ */
+ public int getPlaybackStream() {
+ return mPlaybackStream;
+ }
+
+ /**
+ * Gets the type of the receiver device associated with this route.
+ *
+ * @return The type of the receiver device associated with this route:
+ * {@link #DEVICE_TYPE_TV} or {@link #DEVICE_TYPE_SPEAKER}.
+ */
+ public int getDeviceType() {
+ return mDeviceType;
+ }
+
+
+ /**
+ * @hide
+ */
+ // @RestrictTo(LIBRARY_GROUP)
+ public boolean isDefaultOrBluetooth() {
+ if (isDefault() || mDeviceType == DEVICE_TYPE_BLUETOOTH) {
+ return true;
+ }
+ // This is a workaround for platform version 23 or below where the system route
+ // provider doesn't specify device type for bluetooth media routes.
+ return isSystemMediaRouteProvider(this)
+ && supportsControlCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO)
+ && !supportsControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO);
+ }
+
+ /**
+ * Returns {@code true} if the route is selectable.
+ */
+ boolean isSelectable() {
+ // This tests whether the route is still valid and enabled.
+ // The route descriptor field is set to null when the route is removed.
+ return mDescriptor != null && mEnabled;
+ }
+
+ private static boolean isSystemMediaRouteProvider(MediaRouter.RouteInfo route) {
+ return TextUtils.equals(route.getProviderInstance().getMetadata().getPackageName(),
+ SYSTEM_MEDIA_ROUTE_PROVIDER_PACKAGE_NAME);
+ }
+
+ /**
+ * Gets information about how volume is handled on the route.
+ *
+ * @return How volume is handled on the route: {@link #PLAYBACK_VOLUME_FIXED}
+ * or {@link #PLAYBACK_VOLUME_VARIABLE}.
+ */
+ @PlaybackVolume
+ public int getVolumeHandling() {
+ return mVolumeHandling;
+ }
+
+ /**
+ * Gets the current volume for this route. Depending on the route, this may only
+ * be valid if the route is currently selected.
+ *
+ * @return The volume at which the playback associated with this route is performed.
+ */
+ public int getVolume() {
+ return mVolume;
+ }
+
+ /**
+ * Gets the maximum volume at which the playback associated with this route is performed.
+ *
+ * @return The maximum volume at which the playback associated with
+ * this route is performed.
+ */
+ public int getVolumeMax() {
+ return mVolumeMax;
+ }
+
+ /**
+ * Gets whether this route supports disconnecting without interrupting
+ * playback.
+ *
+ * @return True if this route can disconnect without stopping playback,
+ * false otherwise.
+ */
+ public boolean canDisconnect() {
+ return mCanDisconnect;
+ }
+
+ /**
+ * Requests a volume change for this route asynchronously.
+ * <p>
+ * This function may only be called on a selected route. It will have
+ * no effect if the route is currently unselected.
+ * </p>
+ *
+ * @param volume The new volume value between 0 and {@link #getVolumeMax}.
+ */
+ public void requestSetVolume(int volume) {
+ checkCallingThread();
+ sGlobal.requestSetVolume(this, Math.min(mVolumeMax, Math.max(0, volume)));
+ }
+
+ /**
+ * Requests an incremental volume update for this route asynchronously.
+ * <p>
+ * This function may only be called on a selected route. It will have
+ * no effect if the route is currently unselected.
+ * </p>
+ *
+ * @param delta The delta to add to the current volume.
+ */
+ public void requestUpdateVolume(int delta) {
+ checkCallingThread();
+ if (delta != 0) {
+ sGlobal.requestUpdateVolume(this, delta);
+ }
+ }
+
+ /**
+ * Gets the {@link Display} that should be used by the application to show
+ * a {@link android.app.Presentation} on an external display when this route is selected.
+ * Depending on the route, this may only be valid if the route is currently
+ * selected.
+ * <p>
+ * The preferred presentation display may change independently of the route
+ * being selected or unselected. For example, the presentation display
+ * of the default system route may change when an external HDMI display is connected
+ * or disconnected even though the route itself has not changed.
+ * </p><p>
+ * This method may return null if there is no external display associated with
+ * the route or if the display is not ready to show UI yet.
+ * </p><p>
+ * The application should listen for changes to the presentation display
+ * using the {@link Callback#onRoutePresentationDisplayChanged} callback and
+ * show or dismiss its {@link android.app.Presentation} accordingly when the display
+ * becomes available or is removed.
+ * </p><p>
+ * This method only makes sense for
+ * {@link MediaControlIntent#CATEGORY_LIVE_VIDEO live video} routes.
+ * </p>
+ *
+ * @return The preferred presentation display to use when this route is
+ * selected or null if none.
+ *
+ * @see MediaControlIntent#CATEGORY_LIVE_VIDEO
+ * @see android.app.Presentation
+ */
+ @Nullable
+ public Display getPresentationDisplay() {
+ checkCallingThread();
+ if (mPresentationDisplayId >= 0 && mPresentationDisplay == null) {
+ mPresentationDisplay = sGlobal.getDisplay(mPresentationDisplayId);
+ }
+ return mPresentationDisplay;
+ }
+
+ /**
+ * Gets the route's presentation display id, or -1 if none.
+ * @hide
+ */
+ // @RestrictTo(LIBRARY_GROUP)
+ public int getPresentationDisplayId() {
+ return mPresentationDisplayId;
+ }
+
+ /**
+ * Gets a collection of extra properties about this route that were supplied
+ * by its media route provider, or null if none.
+ */
+ @Nullable
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ /**
+ * Gets an intent sender for launching a settings activity for this
+ * route.
+ */
+ @Nullable
+ public IntentSender getSettingsIntent() {
+ return mSettingsIntent;
+ }
+
+ /**
+ * Selects this media route.
+ */
+ public void select() {
+ checkCallingThread();
+ sGlobal.selectRoute(this);
+ }
+
+ @Override
+ public String toString() {
+ return "MediaRouter.RouteInfo{ uniqueId=" + mUniqueId
+ + ", name=" + mName
+ + ", description=" + mDescription
+ + ", iconUri=" + mIconUri
+ + ", enabled=" + mEnabled
+ + ", connecting=" + mConnecting
+ + ", connectionState=" + mConnectionState
+ + ", canDisconnect=" + mCanDisconnect
+ + ", playbackType=" + mPlaybackType
+ + ", playbackStream=" + mPlaybackStream
+ + ", deviceType=" + mDeviceType
+ + ", volumeHandling=" + mVolumeHandling
+ + ", volume=" + mVolume
+ + ", volumeMax=" + mVolumeMax
+ + ", presentationDisplayId=" + mPresentationDisplayId
+ + ", extras=" + mExtras
+ + ", settingsIntent=" + mSettingsIntent
+ + ", providerPackageName=" + mProvider.getPackageName()
+ + " }";
+ }
+
+ int maybeUpdateDescriptor(MediaRouteDescriptor descriptor) {
+ int changes = 0;
+ if (mDescriptor != descriptor) {
+ changes = updateDescriptor(descriptor);
+ }
+ return changes;
+ }
+
+ int updateDescriptor(MediaRouteDescriptor descriptor) {
+ int changes = 0;
+ mDescriptor = descriptor;
+ if (descriptor != null) {
+ if (!equal(mName, descriptor.getName())) {
+ mName = descriptor.getName();
+ changes |= CHANGE_GENERAL;
+ }
+ if (!equal(mDescription, descriptor.getDescription())) {
+ mDescription = descriptor.getDescription();
+ changes |= CHANGE_GENERAL;
+ }
+ if (!equal(mIconUri, descriptor.getIconUri())) {
+ mIconUri = descriptor.getIconUri();
+ changes |= CHANGE_GENERAL;
+ }
+ if (mEnabled != descriptor.isEnabled()) {
+ mEnabled = descriptor.isEnabled();
+ changes |= CHANGE_GENERAL;
+ }
+ if (mConnecting != descriptor.isConnecting()) {
+ mConnecting = descriptor.isConnecting();
+ changes |= CHANGE_GENERAL;
+ }
+ if (mConnectionState != descriptor.getConnectionState()) {
+ mConnectionState = descriptor.getConnectionState();
+ changes |= CHANGE_GENERAL;
+ }
+ if (!mControlFilters.equals(descriptor.getControlFilters())) {
+ mControlFilters.clear();
+ mControlFilters.addAll(descriptor.getControlFilters());
+ changes |= CHANGE_GENERAL;
+ }
+ if (mPlaybackType != descriptor.getPlaybackType()) {
+ mPlaybackType = descriptor.getPlaybackType();
+ changes |= CHANGE_GENERAL;
+ }
+ if (mPlaybackStream != descriptor.getPlaybackStream()) {
+ mPlaybackStream = descriptor.getPlaybackStream();
+ changes |= CHANGE_GENERAL;
+ }
+ if (mDeviceType != descriptor.getDeviceType()) {
+ mDeviceType = descriptor.getDeviceType();
+ changes |= CHANGE_GENERAL;
+ }
+ if (mVolumeHandling != descriptor.getVolumeHandling()) {
+ mVolumeHandling = descriptor.getVolumeHandling();
+ changes |= CHANGE_GENERAL | CHANGE_VOLUME;
+ }
+ if (mVolume != descriptor.getVolume()) {
+ mVolume = descriptor.getVolume();
+ changes |= CHANGE_GENERAL | CHANGE_VOLUME;
+ }
+ if (mVolumeMax != descriptor.getVolumeMax()) {
+ mVolumeMax = descriptor.getVolumeMax();
+ changes |= CHANGE_GENERAL | CHANGE_VOLUME;
+ }
+ if (mPresentationDisplayId != descriptor.getPresentationDisplayId()) {
+ mPresentationDisplayId = descriptor.getPresentationDisplayId();
+ mPresentationDisplay = null;
+ changes |= CHANGE_GENERAL | CHANGE_PRESENTATION_DISPLAY;
+ }
+ if (!equal(mExtras, descriptor.getExtras())) {
+ mExtras = descriptor.getExtras();
+ changes |= CHANGE_GENERAL;
+ }
+ if (!equal(mSettingsIntent, descriptor.getSettingsActivity())) {
+ mSettingsIntent = descriptor.getSettingsActivity();
+ changes |= CHANGE_GENERAL;
+ }
+ if (mCanDisconnect != descriptor.canDisconnectAndKeepPlaying()) {
+ mCanDisconnect = descriptor.canDisconnectAndKeepPlaying();
+ changes |= CHANGE_GENERAL | CHANGE_PRESENTATION_DISPLAY;
+ }
+ }
+ return changes;
+ }
+
+ String getDescriptorId() {
+ return mDescriptorId;
+ }
+
+ /** @hide */
+ // @RestrictTo(LIBRARY_GROUP)
+ public MediaRouteProvider getProviderInstance() {
+ return mProvider.getProviderInstance();
+ }
+ }
+
+ /**
+ * Information about a route that consists of multiple other routes in a group.
+ * @hide
+ */
+ // @RestrictTo(LIBRARY_GROUP)
+ public static class RouteGroup extends RouteInfo {
+ private List<RouteInfo> mRoutes = new ArrayList<>();
+
+ RouteGroup(ProviderInfo provider, String descriptorId, String uniqueId) {
+ super(provider, descriptorId, uniqueId);
+ }
+
+ /**
+ * @return The number of routes in this group
+ */
+ public int getRouteCount() {
+ return mRoutes.size();
+ }
+
+ /**
+ * Returns the route in this group at the specified index
+ *
+ * @param index Index to fetch
+ * @return The route at index
+ */
+ public RouteInfo getRouteAt(int index) {
+ return mRoutes.get(index);
+ }
+
+ /**
+ * Returns the routes in this group
+ *
+ * @return The list of the routes in this group
+ */
+ public List<RouteInfo> getRoutes() {
+ return mRoutes;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(super.toString());
+ sb.append('[');
+ final int count = mRoutes.size();
+ for (int i = 0; i < count; i++) {
+ if (i > 0) sb.append(", ");
+ sb.append(mRoutes.get(i));
+ }
+ sb.append(']');
+ return sb.toString();
+ }
+
+ @Override
+ int maybeUpdateDescriptor(MediaRouteDescriptor descriptor) {
+ boolean changed = false;
+ if (mDescriptor != descriptor) {
+ mDescriptor = descriptor;
+ if (descriptor != null) {
+ List<String> groupMemberIds = descriptor.getGroupMemberIds();
+ List<RouteInfo> routes = new ArrayList<>();
+ changed = groupMemberIds.size() != mRoutes.size();
+ for (String groupMemberId : groupMemberIds) {
+ String uniqueId = sGlobal.getUniqueId(getProvider(), groupMemberId);
+ RouteInfo groupMember = sGlobal.getRoute(uniqueId);
+ if (groupMember != null) {
+ routes.add(groupMember);
+ if (!changed && !mRoutes.contains(groupMember)) {
+ changed = true;
+ }
+ }
+ }
+ if (changed) {
+ mRoutes = routes;
+ }
+ }
+ }
+ return (changed ? CHANGE_GENERAL : 0) | super.updateDescriptor(descriptor);
+ }
+ }
+
+ /**
+ * Provides information about a media route provider.
+ * <p>
+ * This object may be used to determine which media route provider has
+ * published a particular route.
+ * </p>
+ */
+ public static final class ProviderInfo {
+ private final MediaRouteProvider mProviderInstance;
+ private final List<RouteInfo> mRoutes = new ArrayList<>();
+
+ private final ProviderMetadata mMetadata;
+ private MediaRouteProviderDescriptor mDescriptor;
+ private Resources mResources;
+ private boolean mResourcesNotAvailable;
+
+ ProviderInfo(MediaRouteProvider provider) {
+ mProviderInstance = provider;
+ mMetadata = provider.getMetadata();
+ }
+
+ /**
+ * Gets the provider's underlying {@link MediaRouteProvider} instance.
+ */
+ public MediaRouteProvider getProviderInstance() {
+ checkCallingThread();
+ return mProviderInstance;
+ }
+
+ /**
+ * Gets the package name of the media route provider.
+ */
+ public String getPackageName() {
+ return mMetadata.getPackageName();
+ }
+
+ /**
+ * Gets the component name of the media route provider.
+ */
+ public ComponentName getComponentName() {
+ return mMetadata.getComponentName();
+ }
+
+ /**
+ * Gets the {@link MediaRouter.RouteInfo routes} published by this route provider.
+ */
+ public List<RouteInfo> getRoutes() {
+ checkCallingThread();
+ return mRoutes;
+ }
+
+ Resources getResources() {
+ if (mResources == null && !mResourcesNotAvailable) {
+ String packageName = getPackageName();
+ Context context = sGlobal.getProviderContext(packageName);
+ if (context != null) {
+ mResources = context.getResources();
+ } else {
+ Log.w(TAG, "Unable to obtain resources for route provider package: "
+ + packageName);
+ mResourcesNotAvailable = true;
+ }
+ }
+ return mResources;
+ }
+
+ boolean updateDescriptor(MediaRouteProviderDescriptor descriptor) {
+ if (mDescriptor != descriptor) {
+ mDescriptor = descriptor;
+ return true;
+ }
+ return false;
+ }
+
+ int findRouteByDescriptorId(String id) {
+ final int count = mRoutes.size();
+ for (int i = 0; i < count; i++) {
+ if (mRoutes.get(i).mDescriptorId.equals(id)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ @Override
+ public String toString() {
+ return "MediaRouter.RouteProviderInfo{ packageName=" + getPackageName()
+ + " }";
+ }
+ }
+
+ /**
+ * Interface for receiving events about media routing changes.
+ * All methods of this interface will be called from the application's main thread.
+ * <p>
+ * A Callback will only receive events relevant to routes that the callback
+ * was registered for unless the {@link MediaRouter#CALLBACK_FLAG_UNFILTERED_EVENTS}
+ * flag was specified in {@link MediaRouter#addCallback(MediaRouteSelector, Callback, int)}.
+ * </p>
+ *
+ * @see MediaRouter#addCallback(MediaRouteSelector, Callback, int)
+ * @see MediaRouter#removeCallback(Callback)
+ */
+ public static abstract class Callback {
+ /**
+ * Called when the supplied media route becomes selected as the active route.
+ *
+ * @param router The media router reporting the event.
+ * @param route The route that has been selected.
+ */
+ public void onRouteSelected(MediaRouter router, RouteInfo route) {
+ }
+
+ /**
+ * Called when the supplied media route becomes unselected as the active route.
+ * For detailed reason, override {@link #onRouteUnselected(MediaRouter, RouteInfo, int)}
+ * instead.
+ *
+ * @param router The media router reporting the event.
+ * @param route The route that has been unselected.
+ */
+ public void onRouteUnselected(MediaRouter router, RouteInfo route) {
+ }
+
+ /**
+ * Called when the supplied media route becomes unselected as the active route.
+ * The default implementation calls {@link #onRouteUnselected}.
+ * <p>
+ * The reason provided will be one of the following:
+ * <ul>
+ * <li>{@link MediaRouter#UNSELECT_REASON_UNKNOWN}</li>
+ * <li>{@link MediaRouter#UNSELECT_REASON_DISCONNECTED}</li>
+ * <li>{@link MediaRouter#UNSELECT_REASON_STOPPED}</li>
+ * <li>{@link MediaRouter#UNSELECT_REASON_ROUTE_CHANGED}</li>
+ * </ul>
+ *
+ * @param router The media router reporting the event.
+ * @param route The route that has been unselected.
+ * @param reason The reason for unselecting the route.
+ */
+ public void onRouteUnselected(MediaRouter router, RouteInfo route, int reason) {
+ onRouteUnselected(router, route);
+ }
+
+ /**
+ * Called when a media route has been added.
+ *
+ * @param router The media router reporting the event.
+ * @param route The route that has become available for use.
+ */
+ public void onRouteAdded(MediaRouter router, RouteInfo route) {
+ }
+
+ /**
+ * Called when a media route has been removed.
+ *
+ * @param router The media router reporting the event.
+ * @param route The route that has been removed from availability.
+ */
+ public void onRouteRemoved(MediaRouter router, RouteInfo route) {
+ }
+
+ /**
+ * Called when a property of the indicated media route has changed.
+ *
+ * @param router The media router reporting the event.
+ * @param route The route that was changed.
+ */
+ public void onRouteChanged(MediaRouter router, RouteInfo route) {
+ }
+
+ /**
+ * Called when a media route's volume changes.
+ *
+ * @param router The media router reporting the event.
+ * @param route The route whose volume changed.
+ */
+ public void onRouteVolumeChanged(MediaRouter router, RouteInfo route) {
+ }
+
+ /**
+ * Called when a media route's presentation display changes.
+ * <p>
+ * This method is called whenever the route's presentation display becomes
+ * available, is removed or has changes to some of its properties (such as its size).
+ * </p>
+ *
+ * @param router The media router reporting the event.
+ * @param route The route whose presentation display changed.
+ *
+ * @see RouteInfo#getPresentationDisplay()
+ */
+ public void onRoutePresentationDisplayChanged(MediaRouter router, RouteInfo route) {
+ }
+
+ /**
+ * Called when a media route provider has been added.
+ *
+ * @param router The media router reporting the event.
+ * @param provider The provider that has become available for use.
+ */
+ public void onProviderAdded(MediaRouter router, ProviderInfo provider) {
+ }
+
+ /**
+ * Called when a media route provider has been removed.
+ *
+ * @param router The media router reporting the event.
+ * @param provider The provider that has been removed from availability.
+ */
+ public void onProviderRemoved(MediaRouter router, ProviderInfo provider) {
+ }
+
+ /**
+ * Called when a property of the indicated media route provider has changed.
+ *
+ * @param router The media router reporting the event.
+ * @param provider The provider that was changed.
+ */
+ public void onProviderChanged(MediaRouter router, ProviderInfo provider) {
+ }
+ }
+
+ /**
+ * Callback which is invoked with the result of a media control request.
+ *
+ * @see RouteInfo#sendControlRequest
+ */
+ public static abstract class ControlRequestCallback {
+ /**
+ * Called when a media control request succeeds.
+ *
+ * @param data Result data, or null if none.
+ * Contents depend on the {@link MediaControlIntent media control action}.
+ */
+ public void onResult(Bundle data) {
+ }
+
+ /**
+ * Called when a media control request fails.
+ *
+ * @param error A localized error message which may be shown to the user, or null
+ * if the cause of the error is unclear.
+ * @param data Error data, or null if none.
+ * Contents depend on the {@link MediaControlIntent media control action}.
+ */
+ public void onError(String error, Bundle data) {
+ }
+ }
+
+ private static final class CallbackRecord {
+ public final MediaRouter mRouter;
+ public final Callback mCallback;
+ public MediaRouteSelector mSelector;
+ public int mFlags;
+
+ public CallbackRecord(MediaRouter router, Callback callback) {
+ mRouter = router;
+ mCallback = callback;
+ mSelector = MediaRouteSelector.EMPTY;
+ }
+
+ public boolean filterRouteEvent(RouteInfo route) {
+ return (mFlags & CALLBACK_FLAG_UNFILTERED_EVENTS) != 0
+ || route.matchesSelector(mSelector);
+ }
+ }
+
+ /**
+ * Global state for the media router.
+ * <p>
+ * Media routes and media route providers are global to the process; their
+ * state and the bulk of the media router implementation lives here.
+ * </p>
+ */
+ private static final class GlobalMediaRouter
+ implements SystemMediaRouteProvider.SyncCallback,
+ RegisteredMediaRouteProviderWatcher.Callback {
+ final Context mApplicationContext;
+ final ArrayList<WeakReference<MediaRouter>> mRouters = new ArrayList<>();
+ private final ArrayList<RouteInfo> mRoutes = new ArrayList<>();
+ private final Map<Pair<String, String>, String> mUniqueIdMap = new HashMap<>();
+ private final ArrayList<ProviderInfo> mProviders = new ArrayList<>();
+ private final ArrayList<RemoteControlClientRecord> mRemoteControlClients =
+ new ArrayList<>();
+ final RemoteControlClientCompat.PlaybackInfo mPlaybackInfo =
+ new RemoteControlClientCompat.PlaybackInfo();
+ private final ProviderCallback mProviderCallback = new ProviderCallback();
+ final CallbackHandler mCallbackHandler = new CallbackHandler();
+ private final DisplayManagerCompat mDisplayManager;
+ final SystemMediaRouteProvider mSystemProvider;
+ private final boolean mLowRam;
+
+ private RegisteredMediaRouteProviderWatcher mRegisteredProviderWatcher;
+ private RouteInfo mDefaultRoute;
+ private RouteInfo mBluetoothRoute;
+ RouteInfo mSelectedRoute;
+ private RouteController mSelectedRouteController;
+ // A map from route descriptor ID to RouteController for the member routes in the currently
+ // selected route group.
+ private final Map<String, RouteController> mRouteControllerMap = new HashMap<>();
+ private MediaRouteDiscoveryRequest mDiscoveryRequest;
+ private MediaSessionRecord mMediaSession;
+ MediaSessionCompat mRccMediaSession;
+ private MediaSessionCompat mCompatSession;
+ private MediaSessionCompat.OnActiveChangeListener mSessionActiveListener =
+ new MediaSessionCompat.OnActiveChangeListener() {
+ @Override
+ public void onActiveChanged() {
+ if(mRccMediaSession != null) {
+ if (mRccMediaSession.isActive()) {
+ addRemoteControlClient(mRccMediaSession.getRemoteControlClient());
+ } else {
+ removeRemoteControlClient(mRccMediaSession.getRemoteControlClient());
+ }
+ }
+ }
+ };
+
+ GlobalMediaRouter(Context applicationContext) {
+ mApplicationContext = applicationContext;
+ mDisplayManager = DisplayManagerCompat.getInstance(applicationContext);
+ mLowRam = ActivityManagerCompat.isLowRamDevice(
+ (ActivityManager)applicationContext.getSystemService(
+ Context.ACTIVITY_SERVICE));
+
+ // Add the system media route provider for interoperating with
+ // the framework media router. This one is special and receives
+ // synchronization messages from the media router.
+ mSystemProvider = SystemMediaRouteProvider.obtain(applicationContext, this);
+ }
+
+ public void start() {
+ addProvider(mSystemProvider);
+
+ // Start watching for routes published by registered media route
+ // provider services.
+ mRegisteredProviderWatcher = new RegisteredMediaRouteProviderWatcher(
+ mApplicationContext, this);
+ mRegisteredProviderWatcher.start();
+ }
+
+ public MediaRouter getRouter(Context context) {
+ MediaRouter router;
+ for (int i = mRouters.size(); --i >= 0; ) {
+ router = mRouters.get(i).get();
+ if (router == null) {
+ mRouters.remove(i);
+ } else if (router.mContext == context) {
+ return router;
+ }
+ }
+ router = new MediaRouter(context);
+ mRouters.add(new WeakReference<MediaRouter>(router));
+ return router;
+ }
+
+ public ContentResolver getContentResolver() {
+ return mApplicationContext.getContentResolver();
+ }
+
+ public Context getProviderContext(String packageName) {
+ if (packageName.equals(SystemMediaRouteProvider.PACKAGE_NAME)) {
+ return mApplicationContext;
+ }
+ try {
+ return mApplicationContext.createPackageContext(
+ packageName, Context.CONTEXT_RESTRICTED);
+ } catch (NameNotFoundException ex) {
+ return null;
+ }
+ }
+
+ public Display getDisplay(int displayId) {
+ return mDisplayManager.getDisplay(displayId);
+ }
+
+ public void sendControlRequest(RouteInfo route,
+ Intent intent, ControlRequestCallback callback) {
+ if (route == mSelectedRoute && mSelectedRouteController != null) {
+ if (mSelectedRouteController.onControlRequest(intent, callback)) {
+ return;
+ }
+ }
+ if (callback != null) {
+ callback.onError(null, null);
+ }
+ }
+
+ public void requestSetVolume(RouteInfo route, int volume) {
+ if (route == mSelectedRoute && mSelectedRouteController != null) {
+ mSelectedRouteController.onSetVolume(volume);
+ } else if (!mRouteControllerMap.isEmpty()) {
+ RouteController controller = mRouteControllerMap.get(route.mDescriptorId);
+ if (controller != null) {
+ controller.onSetVolume(volume);
+ }
+ }
+ }
+
+ public void requestUpdateVolume(RouteInfo route, int delta) {
+ if (route == mSelectedRoute && mSelectedRouteController != null) {
+ mSelectedRouteController.onUpdateVolume(delta);
+ }
+ }
+
+ public RouteInfo getRoute(String uniqueId) {
+ for (RouteInfo info : mRoutes) {
+ if (info.mUniqueId.equals(uniqueId)) {
+ return info;
+ }
+ }
+ return null;
+ }
+
+ public List<RouteInfo> getRoutes() {
+ return mRoutes;
+ }
+
+ List<ProviderInfo> getProviders() {
+ return mProviders;
+ }
+
+ @NonNull RouteInfo getDefaultRoute() {
+ if (mDefaultRoute == null) {
+ // This should never happen once the media router has been fully
+ // initialized but it is good to check for the error in case there
+ // is a bug in provider initialization.
+ throw new IllegalStateException("There is no default route. "
+ + "The media router has not yet been fully initialized.");
+ }
+ return mDefaultRoute;
+ }
+
+ RouteInfo getBluetoothRoute() {
+ return mBluetoothRoute;
+ }
+
+ @NonNull RouteInfo getSelectedRoute() {
+ if (mSelectedRoute == null) {
+ // This should never happen once the media router has been fully
+ // initialized but it is good to check for the error in case there
+ // is a bug in provider initialization.
+ throw new IllegalStateException("There is no currently selected route. "
+ + "The media router has not yet been fully initialized.");
+ }
+ return mSelectedRoute;
+ }
+
+ void selectRoute(@NonNull RouteInfo route) {
+ selectRoute(route, MediaRouter.UNSELECT_REASON_ROUTE_CHANGED);
+ }
+
+ void selectRoute(@NonNull RouteInfo route, int unselectReason) {
+ if (!mRoutes.contains(route)) {
+ Log.w(TAG, "Ignoring attempt to select removed route: " + route);
+ return;
+ }
+ if (!route.mEnabled) {
+ Log.w(TAG, "Ignoring attempt to select disabled route: " + route);
+ return;
+ }
+ setSelectedRouteInternal(route, unselectReason);
+ }
+
+ public boolean isRouteAvailable(MediaRouteSelector selector, int flags) {
+ if (selector.isEmpty()) {
+ return false;
+ }
+
+ // On low-RAM devices, do not rely on actual discovery results unless asked to.
+ if ((flags & AVAILABILITY_FLAG_REQUIRE_MATCH) == 0 && mLowRam) {
+ return true;
+ }
+
+ // Check whether any existing routes match the selector.
+ final int routeCount = mRoutes.size();
+ for (int i = 0; i < routeCount; i++) {
+ RouteInfo route = mRoutes.get(i);
+ if ((flags & AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE) != 0
+ && route.isDefaultOrBluetooth()) {
+ continue;
+ }
+ if (route.matchesSelector(selector)) {
+ return true;
+ }
+ }
+
+ // It doesn't look like we can find a matching route right now.
+ return false;
+ }
+
+ public void updateDiscoveryRequest() {
+ // Combine all of the callback selectors and active scan flags.
+ boolean discover = false;
+ boolean activeScan = false;
+ MediaRouteSelector.Builder builder = new MediaRouteSelector.Builder();
+ for (int i = mRouters.size(); --i >= 0; ) {
+ MediaRouter router = mRouters.get(i).get();
+ if (router == null) {
+ mRouters.remove(i);
+ } else {
+ final int count = router.mCallbackRecords.size();
+ for (int j = 0; j < count; j++) {
+ CallbackRecord callback = router.mCallbackRecords.get(j);
+ builder.addSelector(callback.mSelector);
+ if ((callback.mFlags & CALLBACK_FLAG_PERFORM_ACTIVE_SCAN) != 0) {
+ activeScan = true;
+ discover = true; // perform active scan implies request discovery
+ }
+ if ((callback.mFlags & CALLBACK_FLAG_REQUEST_DISCOVERY) != 0) {
+ if (!mLowRam) {
+ discover = true;
+ }
+ }
+ if ((callback.mFlags & CALLBACK_FLAG_FORCE_DISCOVERY) != 0) {
+ discover = true;
+ }
+ }
+ }
+ }
+ MediaRouteSelector selector = discover ? builder.build() : MediaRouteSelector.EMPTY;
+
+ // Create a new discovery request.
+ if (mDiscoveryRequest != null
+ && mDiscoveryRequest.getSelector().equals(selector)
+ && mDiscoveryRequest.isActiveScan() == activeScan) {
+ return; // no change
+ }
+ if (selector.isEmpty() && !activeScan) {
+ // Discovery is not needed.
+ if (mDiscoveryRequest == null) {
+ return; // no change
+ }
+ mDiscoveryRequest = null;
+ } else {
+ // Discovery is needed.
+ mDiscoveryRequest = new MediaRouteDiscoveryRequest(selector, activeScan);
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Updated discovery request: " + mDiscoveryRequest);
+ }
+ if (discover && !activeScan && mLowRam) {
+ Log.i(TAG, "Forcing passive route discovery on a low-RAM device, "
+ + "system performance may be affected. Please consider using "
+ + "CALLBACK_FLAG_REQUEST_DISCOVERY instead of "
+ + "CALLBACK_FLAG_FORCE_DISCOVERY.");
+ }
+
+ // Notify providers.
+ final int providerCount = mProviders.size();
+ for (int i = 0; i < providerCount; i++) {
+ mProviders.get(i).mProviderInstance.setDiscoveryRequest(mDiscoveryRequest);
+ }
+ }
+
+ @Override
+ public void addProvider(MediaRouteProvider providerInstance) {
+ int index = findProviderInfo(providerInstance);
+ if (index < 0) {
+ // 1. Add the provider to the list.
+ ProviderInfo provider = new ProviderInfo(providerInstance);
+ mProviders.add(provider);
+ if (DEBUG) {
+ Log.d(TAG, "Provider added: " + provider);
+ }
+ mCallbackHandler.post(CallbackHandler.MSG_PROVIDER_ADDED, provider);
+ // 2. Create the provider's contents.
+ updateProviderContents(provider, providerInstance.getDescriptor());
+ // 3. Register the provider callback.
+ providerInstance.setCallback(mProviderCallback);
+ // 4. Set the discovery request.
+ providerInstance.setDiscoveryRequest(mDiscoveryRequest);
+ }
+ }
+
+ @Override
+ public void removeProvider(MediaRouteProvider providerInstance) {
+ int index = findProviderInfo(providerInstance);
+ if (index >= 0) {
+ // 1. Unregister the provider callback.
+ providerInstance.setCallback(null);
+ // 2. Clear the discovery request.
+ providerInstance.setDiscoveryRequest(null);
+ // 3. Delete the provider's contents.
+ ProviderInfo provider = mProviders.get(index);
+ updateProviderContents(provider, null);
+ // 4. Remove the provider from the list.
+ if (DEBUG) {
+ Log.d(TAG, "Provider removed: " + provider);
+ }
+ mCallbackHandler.post(CallbackHandler.MSG_PROVIDER_REMOVED, provider);
+ mProviders.remove(index);
+ }
+ }
+
+ void updateProviderDescriptor(MediaRouteProvider providerInstance,
+ MediaRouteProviderDescriptor descriptor) {
+ int index = findProviderInfo(providerInstance);
+ if (index >= 0) {
+ // Update the provider's contents.
+ ProviderInfo provider = mProviders.get(index);
+ updateProviderContents(provider, descriptor);
+ }
+ }
+
+ private int findProviderInfo(MediaRouteProvider providerInstance) {
+ final int count = mProviders.size();
+ for (int i = 0; i < count; i++) {
+ if (mProviders.get(i).mProviderInstance == providerInstance) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ private void updateProviderContents(ProviderInfo provider,
+ MediaRouteProviderDescriptor providerDescriptor) {
+ if (provider.updateDescriptor(providerDescriptor)) {
+ // Update all existing routes and reorder them to match
+ // the order of their descriptors.
+ int targetIndex = 0;
+ boolean selectedRouteDescriptorChanged = false;
+ if (providerDescriptor != null) {
+ if (providerDescriptor.isValid()) {
+ final List<MediaRouteDescriptor> routeDescriptors =
+ providerDescriptor.getRoutes();
+ final int routeCount = routeDescriptors.size();
+ // Updating route group's contents requires all member routes' information.
+ // Add the groups to the lists and update them later.
+ List<Pair<RouteInfo, MediaRouteDescriptor>> addedGroups = new ArrayList<>();
+ List<Pair<RouteInfo, MediaRouteDescriptor>> updatedGroups =
+ new ArrayList<>();
+ for (int i = 0; i < routeCount; i++) {
+ final MediaRouteDescriptor routeDescriptor = routeDescriptors.get(i);
+ final String id = routeDescriptor.getId();
+ final int sourceIndex = provider.findRouteByDescriptorId(id);
+ if (sourceIndex < 0) {
+ // 1. Add the route to the list.
+ String uniqueId = assignRouteUniqueId(provider, id);
+ boolean isGroup = routeDescriptor.getGroupMemberIds() != null;
+ RouteInfo route = isGroup ? new RouteGroup(provider, id, uniqueId) :
+ new RouteInfo(provider, id, uniqueId);
+ provider.mRoutes.add(targetIndex++, route);
+ mRoutes.add(route);
+ // 2. Create the route's contents.
+ if (isGroup) {
+ addedGroups.add(new Pair<>(route, routeDescriptor));
+ } else {
+ route.maybeUpdateDescriptor(routeDescriptor);
+ // 3. Notify clients about addition.
+ if (DEBUG) {
+ Log.d(TAG, "Route added: " + route);
+ }
+ mCallbackHandler.post(CallbackHandler.MSG_ROUTE_ADDED, route);
+ }
+
+ } else if (sourceIndex < targetIndex) {
+ Log.w(TAG, "Ignoring route descriptor with duplicate id: "
+ + routeDescriptor);
+ } else {
+ // 1. Reorder the route within the list.
+ RouteInfo route = provider.mRoutes.get(sourceIndex);
+ Collections.swap(provider.mRoutes,
+ sourceIndex, targetIndex++);
+ // 2. Update the route's contents.
+ if (route instanceof RouteGroup) {
+ updatedGroups.add(new Pair<>(route, routeDescriptor));
+ } else {
+ // 3. Notify clients about changes.
+ if (updateRouteDescriptorAndNotify(route, routeDescriptor)
+ != 0) {
+ if (route == mSelectedRoute) {
+ selectedRouteDescriptorChanged = true;
+ }
+ }
+ }
+ }
+ }
+ // Update the new and/or existing groups.
+ for (Pair<RouteInfo, MediaRouteDescriptor> pair : addedGroups) {
+ RouteInfo route = pair.first;
+ route.maybeUpdateDescriptor(pair.second);
+ if (DEBUG) {
+ Log.d(TAG, "Route added: " + route);
+ }
+ mCallbackHandler.post(CallbackHandler.MSG_ROUTE_ADDED, route);
+ }
+ for (Pair<RouteInfo, MediaRouteDescriptor> pair : updatedGroups) {
+ RouteInfo route = pair.first;
+ if (updateRouteDescriptorAndNotify(route, pair.second) != 0) {
+ if (route == mSelectedRoute) {
+ selectedRouteDescriptorChanged = true;
+ }
+ }
+ }
+ } else {
+ Log.w(TAG, "Ignoring invalid provider descriptor: " + providerDescriptor);
+ }
+ }
+
+ // Dispose all remaining routes that do not have matching descriptors.
+ for (int i = provider.mRoutes.size() - 1; i >= targetIndex; i--) {
+ // 1. Delete the route's contents.
+ RouteInfo route = provider.mRoutes.get(i);
+ route.maybeUpdateDescriptor(null);
+ // 2. Remove the route from the list.
+ mRoutes.remove(route);
+ }
+
+ // Update the selected route if needed.
+ updateSelectedRouteIfNeeded(selectedRouteDescriptorChanged);
+
+ // Now notify clients about routes that were removed.
+ // We do this after updating the selected route to ensure
+ // that the framework media router observes the new route
+ // selection before the removal since removing the currently
+ // selected route may have side-effects.
+ for (int i = provider.mRoutes.size() - 1; i >= targetIndex; i--) {
+ RouteInfo route = provider.mRoutes.remove(i);
+ if (DEBUG) {
+ Log.d(TAG, "Route removed: " + route);
+ }
+ mCallbackHandler.post(CallbackHandler.MSG_ROUTE_REMOVED, route);
+ }
+
+ // Notify provider changed.
+ if (DEBUG) {
+ Log.d(TAG, "Provider changed: " + provider);
+ }
+ mCallbackHandler.post(CallbackHandler.MSG_PROVIDER_CHANGED, provider);
+ }
+ }
+
+ private int updateRouteDescriptorAndNotify(RouteInfo route,
+ MediaRouteDescriptor routeDescriptor) {
+ int changes = route.maybeUpdateDescriptor(routeDescriptor);
+ if (changes != 0) {
+ if ((changes & RouteInfo.CHANGE_GENERAL) != 0) {
+ if (DEBUG) {
+ Log.d(TAG, "Route changed: " + route);
+ }
+ mCallbackHandler.post(
+ CallbackHandler.MSG_ROUTE_CHANGED, route);
+ }
+ if ((changes & RouteInfo.CHANGE_VOLUME) != 0) {
+ if (DEBUG) {
+ Log.d(TAG, "Route volume changed: " + route);
+ }
+ mCallbackHandler.post(
+ CallbackHandler.MSG_ROUTE_VOLUME_CHANGED, route);
+ }
+ if ((changes & RouteInfo.CHANGE_PRESENTATION_DISPLAY) != 0) {
+ if (DEBUG) {
+ Log.d(TAG, "Route presentation display changed: "
+ + route);
+ }
+ mCallbackHandler.post(CallbackHandler.
+ MSG_ROUTE_PRESENTATION_DISPLAY_CHANGED, route);
+ }
+ }
+ return changes;
+ }
+
+ private String assignRouteUniqueId(ProviderInfo provider, String routeDescriptorId) {
+ // Although route descriptor ids are unique within a provider, it's
+ // possible for there to be two providers with the same package name.
+ // Therefore we must dedupe the composite id.
+ String componentName = provider.getComponentName().flattenToShortString();
+ String uniqueId = componentName + ":" + routeDescriptorId;
+ if (findRouteByUniqueId(uniqueId) < 0) {
+ mUniqueIdMap.put(new Pair<>(componentName, routeDescriptorId), uniqueId);
+ return uniqueId;
+ }
+ Log.w(TAG, "Either " + routeDescriptorId + " isn't unique in " + componentName
+ + " or we're trying to assign a unique ID for an already added route");
+ for (int i = 2; ; i++) {
+ String newUniqueId = String.format(Locale.US, "%s_%d", uniqueId, i);
+ if (findRouteByUniqueId(newUniqueId) < 0) {
+ mUniqueIdMap.put(new Pair<>(componentName, routeDescriptorId), newUniqueId);
+ return newUniqueId;
+ }
+ }
+ }
+
+ private int findRouteByUniqueId(String uniqueId) {
+ final int count = mRoutes.size();
+ for (int i = 0; i < count; i++) {
+ if (mRoutes.get(i).mUniqueId.equals(uniqueId)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ private String getUniqueId(ProviderInfo provider, String routeDescriptorId) {
+ String componentName = provider.getComponentName().flattenToShortString();
+ return mUniqueIdMap.get(new Pair<>(componentName, routeDescriptorId));
+ }
+
+ private void updateSelectedRouteIfNeeded(boolean selectedRouteDescriptorChanged) {
+ // Update default route.
+ if (mDefaultRoute != null && !mDefaultRoute.isSelectable()) {
+ Log.i(TAG, "Clearing the default route because it "
+ + "is no longer selectable: " + mDefaultRoute);
+ mDefaultRoute = null;
+ }
+ if (mDefaultRoute == null && !mRoutes.isEmpty()) {
+ for (RouteInfo route : mRoutes) {
+ if (isSystemDefaultRoute(route) && route.isSelectable()) {
+ mDefaultRoute = route;
+ Log.i(TAG, "Found default route: " + mDefaultRoute);
+ break;
+ }
+ }
+ }
+
+ // Update bluetooth route.
+ if (mBluetoothRoute != null && !mBluetoothRoute.isSelectable()) {
+ Log.i(TAG, "Clearing the bluetooth route because it "
+ + "is no longer selectable: " + mBluetoothRoute);
+ mBluetoothRoute = null;
+ }
+ if (mBluetoothRoute == null && !mRoutes.isEmpty()) {
+ for (RouteInfo route : mRoutes) {
+ if (isSystemLiveAudioOnlyRoute(route) && route.isSelectable()) {
+ mBluetoothRoute = route;
+ Log.i(TAG, "Found bluetooth route: " + mBluetoothRoute);
+ break;
+ }
+ }
+ }
+
+ // Update selected route.
+ if (mSelectedRoute == null || !mSelectedRoute.isSelectable()) {
+ Log.i(TAG, "Unselecting the current route because it "
+ + "is no longer selectable: " + mSelectedRoute);
+ setSelectedRouteInternal(chooseFallbackRoute(),
+ MediaRouter.UNSELECT_REASON_UNKNOWN);
+ } else if (selectedRouteDescriptorChanged) {
+ // In case the selected route is a route group, select/unselect route controllers
+ // for the added/removed route members.
+ if (mSelectedRoute instanceof RouteGroup) {
+ List<RouteInfo> routes = ((RouteGroup) mSelectedRoute).getRoutes();
+ // Build a set of descriptor IDs for the new route group.
+ Set<String> idSet = new HashSet<>();
+ for (RouteInfo route : routes) {
+ idSet.add(route.mDescriptorId);
+ }
+ // Unselect route controllers for the removed routes.
+ Iterator<Map.Entry<String, RouteController>> iter =
+ mRouteControllerMap.entrySet().iterator();
+ while (iter.hasNext()) {
+ Map.Entry<String, RouteController> entry = iter.next();
+ if (!idSet.contains(entry.getKey())) {
+ RouteController controller = entry.getValue();
+ controller.onUnselect();
+ controller.onRelease();
+ iter.remove();
+ }
+ }
+ // Select route controllers for the added routes.
+ for (RouteInfo route : routes) {
+ if (!mRouteControllerMap.containsKey(route.mDescriptorId)) {
+ RouteController controller = route.getProviderInstance()
+ .onCreateRouteController(
+ route.mDescriptorId, mSelectedRoute.mDescriptorId);
+ controller.onSelect();
+ mRouteControllerMap.put(route.mDescriptorId, controller);
+ }
+ }
+ }
+ // Update the playback info because the properties of the route have changed.
+ updatePlaybackInfoFromSelectedRoute();
+ }
+ }
+
+ RouteInfo chooseFallbackRoute() {
+ // When the current route is removed or no longer selectable,
+ // we want to revert to a live audio route if there is
+ // one (usually Bluetooth A2DP). Failing that, use
+ // the default route.
+ for (RouteInfo route : mRoutes) {
+ if (route != mDefaultRoute
+ && isSystemLiveAudioOnlyRoute(route)
+ && route.isSelectable()) {
+ return route;
+ }
+ }
+ return mDefaultRoute;
+ }
+
+ private boolean isSystemLiveAudioOnlyRoute(RouteInfo route) {
+ return route.getProviderInstance() == mSystemProvider
+ && route.supportsControlCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO)
+ && !route.supportsControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO);
+ }
+
+ private boolean isSystemDefaultRoute(RouteInfo route) {
+ return route.getProviderInstance() == mSystemProvider
+ && route.mDescriptorId.equals(
+ SystemMediaRouteProvider.DEFAULT_ROUTE_ID);
+ }
+
+ private void setSelectedRouteInternal(@NonNull RouteInfo route, int unselectReason) {
+ // TODO: Remove the following logging when no longer needed.
+ if (sGlobal == null || (mBluetoothRoute != null && route.isDefault())) {
+ final StackTraceElement[] callStack = Thread.currentThread().getStackTrace();
+ StringBuilder sb = new StringBuilder();
+ // callStack[3] is the caller of this method.
+ for (int i = 3; i < callStack.length; i++) {
+ StackTraceElement caller = callStack[i];
+ sb.append(caller.getClassName())
+ .append(".")
+ .append(caller.getMethodName())
+ .append(":")
+ .append(caller.getLineNumber())
+ .append(" ");
+ }
+ if (sGlobal == null) {
+ Log.w(TAG, "setSelectedRouteInternal is called while sGlobal is null: pkgName="
+ + mApplicationContext.getPackageName() + ", callers=" + sb.toString());
+ } else {
+ Log.w(TAG, "Default route is selected while a BT route is available: pkgName="
+ + mApplicationContext.getPackageName() + ", callers=" + sb.toString());
+ }
+ }
+
+ if (mSelectedRoute != route) {
+ if (mSelectedRoute != null) {
+ if (DEBUG) {
+ Log.d(TAG, "Route unselected: " + mSelectedRoute + " reason: "
+ + unselectReason);
+ }
+ mCallbackHandler.post(CallbackHandler.MSG_ROUTE_UNSELECTED, mSelectedRoute,
+ unselectReason);
+ if (mSelectedRouteController != null) {
+ mSelectedRouteController.onUnselect(unselectReason);
+ mSelectedRouteController.onRelease();
+ mSelectedRouteController = null;
+ }
+ if (!mRouteControllerMap.isEmpty()) {
+ for (RouteController controller : mRouteControllerMap.values()) {
+ controller.onUnselect(unselectReason);
+ controller.onRelease();
+ }
+ mRouteControllerMap.clear();
+ }
+ }
+
+ mSelectedRoute = route;
+ mSelectedRouteController = route.getProviderInstance().onCreateRouteController(
+ route.mDescriptorId);
+ if (mSelectedRouteController != null) {
+ mSelectedRouteController.onSelect();
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Route selected: " + mSelectedRoute);
+ }
+ mCallbackHandler.post(CallbackHandler.MSG_ROUTE_SELECTED, mSelectedRoute);
+
+ if (mSelectedRoute instanceof RouteGroup) {
+ List<RouteInfo> routes = ((RouteGroup) mSelectedRoute).getRoutes();
+ mRouteControllerMap.clear();
+ for (RouteInfo r : routes) {
+ RouteController controller =
+ r.getProviderInstance().onCreateRouteController(
+ r.mDescriptorId, mSelectedRoute.mDescriptorId);
+ controller.onSelect();
+ mRouteControllerMap.put(r.mDescriptorId, controller);
+ }
+ }
+
+ updatePlaybackInfoFromSelectedRoute();
+ }
+ }
+
+ @Override
+ public void onSystemRouteSelectedByDescriptorId(String id) {
+ // System route is selected, do not sync the route we selected before.
+ mCallbackHandler.removeMessages(CallbackHandler.MSG_ROUTE_SELECTED);
+ int providerIndex = findProviderInfo(mSystemProvider);
+ if (providerIndex >= 0) {
+ ProviderInfo provider = mProviders.get(providerIndex);
+ int routeIndex = provider.findRouteByDescriptorId(id);
+ if (routeIndex >= 0) {
+ provider.mRoutes.get(routeIndex).select();
+ }
+ }
+ }
+
+ public void addRemoteControlClient(Object rcc) {
+ int index = findRemoteControlClientRecord(rcc);
+ if (index < 0) {
+ RemoteControlClientRecord record = new RemoteControlClientRecord(rcc);
+ mRemoteControlClients.add(record);
+ }
+ }
+
+ public void removeRemoteControlClient(Object rcc) {
+ int index = findRemoteControlClientRecord(rcc);
+ if (index >= 0) {
+ RemoteControlClientRecord record = mRemoteControlClients.remove(index);
+ record.disconnect();
+ }
+ }
+
+ public void setMediaSession(Object session) {
+ setMediaSessionRecord(session != null ? new MediaSessionRecord(session) : null);
+ }
+
+ public void setMediaSessionCompat(final MediaSessionCompat session) {
+ mCompatSession = session;
+ if (android.os.Build.VERSION.SDK_INT >= 21) {
+ setMediaSessionRecord(session != null ? new MediaSessionRecord(session) : null);
+ } else if (android.os.Build.VERSION.SDK_INT >= 14) {
+ if (mRccMediaSession != null) {
+ removeRemoteControlClient(mRccMediaSession.getRemoteControlClient());
+ mRccMediaSession.removeOnActiveChangeListener(mSessionActiveListener);
+ }
+ mRccMediaSession = session;
+ if (session != null) {
+ session.addOnActiveChangeListener(mSessionActiveListener);
+ if (session.isActive()) {
+ addRemoteControlClient(session.getRemoteControlClient());
+ }
+ }
+ }
+ }
+
+ private void setMediaSessionRecord(MediaSessionRecord mediaSessionRecord) {
+ if (mMediaSession != null) {
+ mMediaSession.clearVolumeHandling();
+ }
+ mMediaSession = mediaSessionRecord;
+ if (mediaSessionRecord != null) {
+ updatePlaybackInfoFromSelectedRoute();
+ }
+ }
+
+ public MediaSessionCompat.Token getMediaSessionToken() {
+ if (mMediaSession != null) {
+ return mMediaSession.getToken();
+ } else if (mCompatSession != null) {
+ return mCompatSession.getSessionToken();
+ }
+ return null;
+ }
+
+ private int findRemoteControlClientRecord(Object rcc) {
+ final int count = mRemoteControlClients.size();
+ for (int i = 0; i < count; i++) {
+ RemoteControlClientRecord record = mRemoteControlClients.get(i);
+ if (record.getRemoteControlClient() == rcc) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ private void updatePlaybackInfoFromSelectedRoute() {
+ if (mSelectedRoute != null) {
+ mPlaybackInfo.volume = mSelectedRoute.getVolume();
+ mPlaybackInfo.volumeMax = mSelectedRoute.getVolumeMax();
+ mPlaybackInfo.volumeHandling = mSelectedRoute.getVolumeHandling();
+ mPlaybackInfo.playbackStream = mSelectedRoute.getPlaybackStream();
+ mPlaybackInfo.playbackType = mSelectedRoute.getPlaybackType();
+
+ final int count = mRemoteControlClients.size();
+ for (int i = 0; i < count; i++) {
+ RemoteControlClientRecord record = mRemoteControlClients.get(i);
+ record.updatePlaybackInfo();
+ }
+ if (mMediaSession != null) {
+ if (mSelectedRoute == getDefaultRoute()
+ || mSelectedRoute == getBluetoothRoute()) {
+ // Local route
+ mMediaSession.clearVolumeHandling();
+ } else {
+ @VolumeProviderCompat.ControlType int controlType =
+ VolumeProviderCompat.VOLUME_CONTROL_FIXED;
+ if (mPlaybackInfo.volumeHandling
+ == MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE) {
+ controlType = VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE;
+ }
+ mMediaSession.configureVolume(controlType, mPlaybackInfo.volumeMax,
+ mPlaybackInfo.volume);
+ }
+ }
+ } else {
+ if (mMediaSession != null) {
+ mMediaSession.clearVolumeHandling();
+ }
+ }
+ }
+
+ private final class ProviderCallback extends MediaRouteProvider.Callback {
+ ProviderCallback() {
+ }
+
+ @Override
+ public void onDescriptorChanged(MediaRouteProvider provider,
+ MediaRouteProviderDescriptor descriptor) {
+ updateProviderDescriptor(provider, descriptor);
+ }
+ }
+
+ private final class MediaSessionRecord {
+ private final MediaSessionCompat mMsCompat;
+
+ private @VolumeProviderCompat.ControlType int mControlType;
+ private int mMaxVolume;
+ private VolumeProviderCompat mVpCompat;
+
+ public MediaSessionRecord(Object mediaSession) {
+ mMsCompat = MediaSessionCompat.fromMediaSession(mApplicationContext, mediaSession);
+ }
+
+ public MediaSessionRecord(MediaSessionCompat mediaSessionCompat) {
+ mMsCompat = mediaSessionCompat;
+ }
+
+ public void configureVolume(@VolumeProviderCompat.ControlType int controlType,
+ int max, int current) {
+ if (mVpCompat != null && controlType == mControlType && max == mMaxVolume) {
+ // If we haven't changed control type or max just set the
+ // new current volume
+ mVpCompat.setCurrentVolume(current);
+ } else {
+ // Otherwise create a new provider and update
+ mVpCompat = new VolumeProviderCompat(controlType, max, current) {
+ @Override
+ public void onSetVolumeTo(final int volume) {
+ mCallbackHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (mSelectedRoute != null) {
+ mSelectedRoute.requestSetVolume(volume);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onAdjustVolume(final int direction) {
+ mCallbackHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (mSelectedRoute != null) {
+ mSelectedRoute.requestUpdateVolume(direction);
+ }
+ }
+ });
+ }
+ };
+ mMsCompat.setPlaybackToRemote(mVpCompat);
+ }
+ }
+
+ public void clearVolumeHandling() {
+ mMsCompat.setPlaybackToLocal(mPlaybackInfo.playbackStream);
+ mVpCompat = null;
+ }
+
+ public MediaSessionCompat.Token getToken() {
+ return mMsCompat.getSessionToken();
+ }
+ }
+
+ private final class RemoteControlClientRecord
+ implements RemoteControlClientCompat.VolumeCallback {
+ private final RemoteControlClientCompat mRccCompat;
+ private boolean mDisconnected;
+
+ public RemoteControlClientRecord(Object rcc) {
+ mRccCompat = RemoteControlClientCompat.obtain(mApplicationContext, rcc);
+ mRccCompat.setVolumeCallback(this);
+ updatePlaybackInfo();
+ }
+
+ public Object getRemoteControlClient() {
+ return mRccCompat.getRemoteControlClient();
+ }
+
+ public void disconnect() {
+ mDisconnected = true;
+ mRccCompat.setVolumeCallback(null);
+ }
+
+ public void updatePlaybackInfo() {
+ mRccCompat.setPlaybackInfo(mPlaybackInfo);
+ }
+
+ @Override
+ public void onVolumeSetRequest(int volume) {
+ if (!mDisconnected && mSelectedRoute != null) {
+ mSelectedRoute.requestSetVolume(volume);
+ }
+ }
+
+ @Override
+ public void onVolumeUpdateRequest(int direction) {
+ if (!mDisconnected && mSelectedRoute != null) {
+ mSelectedRoute.requestUpdateVolume(direction);
+ }
+ }
+ }
+
+ private final class CallbackHandler extends Handler {
+ private final ArrayList<CallbackRecord> mTempCallbackRecords =
+ new ArrayList<CallbackRecord>();
+
+ private static final int MSG_TYPE_MASK = 0xff00;
+ private static final int MSG_TYPE_ROUTE = 0x0100;
+ private static final int MSG_TYPE_PROVIDER = 0x0200;
+
+ public static final int MSG_ROUTE_ADDED = MSG_TYPE_ROUTE | 1;
+ public static final int MSG_ROUTE_REMOVED = MSG_TYPE_ROUTE | 2;
+ public static final int MSG_ROUTE_CHANGED = MSG_TYPE_ROUTE | 3;
+ public static final int MSG_ROUTE_VOLUME_CHANGED = MSG_TYPE_ROUTE | 4;
+ public static final int MSG_ROUTE_PRESENTATION_DISPLAY_CHANGED = MSG_TYPE_ROUTE | 5;
+ public static final int MSG_ROUTE_SELECTED = MSG_TYPE_ROUTE | 6;
+ public static final int MSG_ROUTE_UNSELECTED = MSG_TYPE_ROUTE | 7;
+
+ public static final int MSG_PROVIDER_ADDED = MSG_TYPE_PROVIDER | 1;
+ public static final int MSG_PROVIDER_REMOVED = MSG_TYPE_PROVIDER | 2;
+ public static final int MSG_PROVIDER_CHANGED = MSG_TYPE_PROVIDER | 3;
+
+ CallbackHandler() {
+ }
+
+ public void post(int msg, Object obj) {
+ obtainMessage(msg, obj).sendToTarget();
+ }
+
+ public void post(int msg, Object obj, int arg) {
+ Message message = obtainMessage(msg, obj);
+ message.arg1 = arg;
+ message.sendToTarget();
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ final int what = msg.what;
+ final Object obj = msg.obj;
+ final int arg = msg.arg1;
+
+ if (what == MSG_ROUTE_CHANGED
+ && getSelectedRoute().getId().equals(((RouteInfo) obj).getId())) {
+ updateSelectedRouteIfNeeded(true);
+ }
+
+ // Synchronize state with the system media router.
+ syncWithSystemProvider(what, obj);
+
+ // Invoke all registered callbacks.
+ // Build a list of callbacks before invoking them in case callbacks
+ // are added or removed during dispatch.
+ try {
+ for (int i = mRouters.size(); --i >= 0; ) {
+ MediaRouter router = mRouters.get(i).get();
+ if (router == null) {
+ mRouters.remove(i);
+ } else {
+ mTempCallbackRecords.addAll(router.mCallbackRecords);
+ }
+ }
+
+ final int callbackCount = mTempCallbackRecords.size();
+ for (int i = 0; i < callbackCount; i++) {
+ invokeCallback(mTempCallbackRecords.get(i), what, obj, arg);
+ }
+ } finally {
+ mTempCallbackRecords.clear();
+ }
+ }
+
+ private void syncWithSystemProvider(int what, Object obj) {
+ switch (what) {
+ case MSG_ROUTE_ADDED:
+ mSystemProvider.onSyncRouteAdded((RouteInfo) obj);
+ break;
+ case MSG_ROUTE_REMOVED:
+ mSystemProvider.onSyncRouteRemoved((RouteInfo) obj);
+ break;
+ case MSG_ROUTE_CHANGED:
+ mSystemProvider.onSyncRouteChanged((RouteInfo) obj);
+ break;
+ case MSG_ROUTE_SELECTED:
+ mSystemProvider.onSyncRouteSelected((RouteInfo) obj);
+ break;
+ }
+ }
+
+ private void invokeCallback(CallbackRecord record, int what, Object obj, int arg) {
+ final MediaRouter router = record.mRouter;
+ final MediaRouter.Callback callback = record.mCallback;
+ switch (what & MSG_TYPE_MASK) {
+ case MSG_TYPE_ROUTE: {
+ final RouteInfo route = (RouteInfo)obj;
+ if (!record.filterRouteEvent(route)) {
+ break;
+ }
+ switch (what) {
+ case MSG_ROUTE_ADDED:
+ callback.onRouteAdded(router, route);
+ break;
+ case MSG_ROUTE_REMOVED:
+ callback.onRouteRemoved(router, route);
+ break;
+ case MSG_ROUTE_CHANGED:
+ callback.onRouteChanged(router, route);
+ break;
+ case MSG_ROUTE_VOLUME_CHANGED:
+ callback.onRouteVolumeChanged(router, route);
+ break;
+ case MSG_ROUTE_PRESENTATION_DISPLAY_CHANGED:
+ callback.onRoutePresentationDisplayChanged(router, route);
+ break;
+ case MSG_ROUTE_SELECTED:
+ callback.onRouteSelected(router, route);
+ break;
+ case MSG_ROUTE_UNSELECTED:
+ callback.onRouteUnselected(router, route, arg);
+ break;
+ }
+ break;
+ }
+ case MSG_TYPE_PROVIDER: {
+ final ProviderInfo provider = (ProviderInfo)obj;
+ switch (what) {
+ case MSG_PROVIDER_ADDED:
+ callback.onProviderAdded(router, provider);
+ break;
+ case MSG_PROVIDER_REMOVED:
+ callback.onProviderRemoved(router, provider);
+ break;
+ case MSG_PROVIDER_CHANGED:
+ callback.onProviderChanged(router, provider);
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaSessionStatus.java b/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaSessionStatus.java
new file mode 100644
index 0000000..3206596
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/media/MediaSessionStatus.java
@@ -0,0 +1,244 @@
+/*
+ * 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.support.mediarouter.media;
+
+import android.app.PendingIntent;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.support.v4.util.TimeUtils;
+
+/**
+ * Describes the playback status of a media session.
+ * <p>
+ * This class is part of the remote playback protocol described by the
+ * {@link MediaControlIntent MediaControlIntent} class.
+ * </p><p>
+ * When a media session is created, it is initially in the
+ * {@link #SESSION_STATE_ACTIVE active} state. When the media session ends
+ * normally, it transitions to the {@link #SESSION_STATE_ENDED ended} state.
+ * If the media session is invalidated due to another session forcibly taking
+ * control of the route, then it transitions to the
+ * {@link #SESSION_STATE_INVALIDATED invalidated} state.
+ * Refer to the documentation of each state for an explanation of its meaning.
+ * </p><p>
+ * To monitor session status, the application should supply a {@link PendingIntent} to use as the
+ * {@link MediaControlIntent#EXTRA_SESSION_STATUS_UPDATE_RECEIVER session status update receiver}
+ * for a given {@link MediaControlIntent#ACTION_START_SESSION session start request}.
+ * </p><p>
+ * This object is immutable once created using a {@link Builder} instance.
+ * </p>
+ */
+public final class MediaSessionStatus {
+ static final String KEY_TIMESTAMP = "timestamp";
+ static final String KEY_SESSION_STATE = "sessionState";
+ static final String KEY_QUEUE_PAUSED = "queuePaused";
+ static final String KEY_EXTRAS = "extras";
+
+ final Bundle mBundle;
+
+ /**
+ * Session state: Active.
+ * <p>
+ * Indicates that the media session is active and in control of the route.
+ * </p>
+ */
+ public static final int SESSION_STATE_ACTIVE = 0;
+
+ /**
+ * Session state: Ended.
+ * <p>
+ * Indicates that the media session was ended normally using the
+ * {@link MediaControlIntent#ACTION_END_SESSION end session} action.
+ * </p><p>
+ * A terminated media session cannot be used anymore. To play more media, the
+ * application must start a new session.
+ * </p>
+ */
+ public static final int SESSION_STATE_ENDED = 1;
+
+ /**
+ * Session state: Invalidated.
+ * <p>
+ * Indicates that the media session was invalidated involuntarily due to
+ * another session taking control of the route.
+ * </p><p>
+ * An invalidated media session cannot be used anymore. To play more media, the
+ * application must start a new session.
+ * </p>
+ */
+ public static final int SESSION_STATE_INVALIDATED = 2;
+
+ MediaSessionStatus(Bundle bundle) {
+ mBundle = bundle;
+ }
+
+ /**
+ * Gets the timestamp associated with the status information in
+ * milliseconds since boot in the {@link SystemClock#elapsedRealtime} time base.
+ *
+ * @return The status timestamp in the {@link SystemClock#elapsedRealtime()} time base.
+ */
+ public long getTimestamp() {
+ return mBundle.getLong(KEY_TIMESTAMP);
+ }
+
+ /**
+ * Gets the session state.
+ *
+ * @return The session state. One of {@link #SESSION_STATE_ACTIVE},
+ * {@link #SESSION_STATE_ENDED}, or {@link #SESSION_STATE_INVALIDATED}.
+ */
+ public int getSessionState() {
+ return mBundle.getInt(KEY_SESSION_STATE, SESSION_STATE_INVALIDATED);
+ }
+
+ /**
+ * Returns true if the session's queue is paused.
+ *
+ * @return True if the session's queue is paused.
+ */
+ public boolean isQueuePaused() {
+ return mBundle.getBoolean(KEY_QUEUE_PAUSED);
+ }
+
+ /**
+ * Gets a bundle of extras for this status object.
+ * The extras will be ignored by the media router but they may be used
+ * by applications.
+ */
+ public Bundle getExtras() {
+ return mBundle.getBundle(KEY_EXTRAS);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder();
+ result.append("MediaSessionStatus{ ");
+ result.append("timestamp=");
+ TimeUtils.formatDuration(SystemClock.elapsedRealtime() - getTimestamp(), result);
+ result.append(" ms ago");
+ result.append(", sessionState=").append(sessionStateToString(getSessionState()));
+ result.append(", queuePaused=").append(isQueuePaused());
+ result.append(", extras=").append(getExtras());
+ result.append(" }");
+ return result.toString();
+ }
+
+ private static String sessionStateToString(int sessionState) {
+ switch (sessionState) {
+ case SESSION_STATE_ACTIVE:
+ return "active";
+ case SESSION_STATE_ENDED:
+ return "ended";
+ case SESSION_STATE_INVALIDATED:
+ return "invalidated";
+ }
+ return Integer.toString(sessionState);
+ }
+
+ /**
+ * Converts this object to a bundle for serialization.
+ *
+ * @return The contents of the object represented as a bundle.
+ */
+ public Bundle asBundle() {
+ return mBundle;
+ }
+
+ /**
+ * Creates an instance from a bundle.
+ *
+ * @param bundle The bundle, or null if none.
+ * @return The new instance, or null if the bundle was null.
+ */
+ public static MediaSessionStatus fromBundle(Bundle bundle) {
+ return bundle != null ? new MediaSessionStatus(bundle) : null;
+ }
+
+ /**
+ * Builder for {@link MediaSessionStatus media session status objects}.
+ */
+ public static final class Builder {
+ private final Bundle mBundle;
+
+ /**
+ * Creates a media session status builder using the current time as the
+ * reference timestamp.
+ *
+ * @param sessionState The session state.
+ */
+ public Builder(int sessionState) {
+ mBundle = new Bundle();
+ setTimestamp(SystemClock.elapsedRealtime());
+ setSessionState(sessionState);
+ }
+
+ /**
+ * Creates a media session status builder whose initial contents are
+ * copied from an existing status.
+ */
+ public Builder(MediaSessionStatus status) {
+ if (status == null) {
+ throw new IllegalArgumentException("status must not be null");
+ }
+
+ mBundle = new Bundle(status.mBundle);
+ }
+
+ /**
+ * Sets the timestamp associated with the status information in
+ * milliseconds since boot in the {@link SystemClock#elapsedRealtime} time base.
+ */
+ public Builder setTimestamp(long elapsedRealtimeTimestamp) {
+ mBundle.putLong(KEY_TIMESTAMP, elapsedRealtimeTimestamp);
+ return this;
+ }
+
+ /**
+ * Sets the session state.
+ */
+ public Builder setSessionState(int sessionState) {
+ mBundle.putInt(KEY_SESSION_STATE, sessionState);
+ return this;
+ }
+
+ /**
+ * Sets whether the queue is paused.
+ */
+ public Builder setQueuePaused(boolean queuePaused) {
+ mBundle.putBoolean(KEY_QUEUE_PAUSED, queuePaused);
+ return this;
+ }
+
+ /**
+ * Sets a bundle of extras for this status object.
+ * The extras will be ignored by the media router but they may be used
+ * by applications.
+ */
+ public Builder setExtras(Bundle extras) {
+ mBundle.putBundle(KEY_EXTRAS, extras);
+ return this;
+ }
+
+ /**
+ * Builds the {@link MediaSessionStatus media session status object}.
+ */
+ public MediaSessionStatus build() {
+ return new MediaSessionStatus(mBundle);
+ }
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/media/RegisteredMediaRouteProvider.java b/packages/MediaComponents/src/com/android/support/mediarouter/media/RegisteredMediaRouteProvider.java
new file mode 100644
index 0000000..98e4e28
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/media/RegisteredMediaRouteProvider.java
@@ -0,0 +1,741 @@
+/*
+ * 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.support.mediarouter.media;
+
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol.CLIENT_DATA_ROUTE_ID;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+ .CLIENT_DATA_ROUTE_LIBRARY_GROUP;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+ .CLIENT_DATA_UNSELECT_REASON;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol.CLIENT_DATA_VOLUME;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+ .CLIENT_MSG_CREATE_ROUTE_CONTROLLER;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol.CLIENT_MSG_REGISTER;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+ .CLIENT_MSG_RELEASE_ROUTE_CONTROLLER;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+ .CLIENT_MSG_ROUTE_CONTROL_REQUEST;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol.CLIENT_MSG_SELECT_ROUTE;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+ .CLIENT_MSG_SET_DISCOVERY_REQUEST;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+ .CLIENT_MSG_SET_ROUTE_VOLUME;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol.CLIENT_MSG_UNREGISTER;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+ .CLIENT_MSG_UNSELECT_ROUTE;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+ .CLIENT_MSG_UPDATE_ROUTE_VOLUME;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol.CLIENT_VERSION_CURRENT;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol.SERVICE_DATA_ERROR;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+ .SERVICE_MSG_CONTROL_REQUEST_FAILED;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+ .SERVICE_MSG_CONTROL_REQUEST_SUCCEEDED;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+ .SERVICE_MSG_DESCRIPTOR_CHANGED;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+ .SERVICE_MSG_GENERIC_FAILURE;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
+ .SERVICE_MSG_GENERIC_SUCCESS;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol.SERVICE_MSG_REGISTERED;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol.SERVICE_VERSION_1;
+import static com.android.support.mediarouter.media.MediaRouteProviderProtocol.isValidRemoteMessenger;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.DeadObjectException;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IBinder.DeathRecipient;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.support.mediarouter.media.MediaRouter.ControlRequestCallback;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Maintains a connection to a particular media route provider service.
+ */
+final class RegisteredMediaRouteProvider extends MediaRouteProvider
+ implements ServiceConnection {
+ static final String TAG = "MediaRouteProviderProxy"; // max. 23 chars
+ static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private final ComponentName mComponentName;
+ final PrivateHandler mPrivateHandler;
+ private final ArrayList<Controller> mControllers = new ArrayList<Controller>();
+
+ private boolean mStarted;
+ private boolean mBound;
+ private Connection mActiveConnection;
+ private boolean mConnectionReady;
+
+ public RegisteredMediaRouteProvider(Context context, ComponentName componentName) {
+ super(context, new ProviderMetadata(componentName));
+
+ mComponentName = componentName;
+ mPrivateHandler = new PrivateHandler();
+ }
+
+ @Override
+ public RouteController onCreateRouteController(@NonNull String routeId) {
+ if (routeId == null) {
+ throw new IllegalArgumentException("routeId cannot be null");
+ }
+ return createRouteController(routeId, null);
+ }
+
+ @Override
+ public RouteController onCreateRouteController(
+ @NonNull String routeId, @NonNull String routeGroupId) {
+ if (routeId == null) {
+ throw new IllegalArgumentException("routeId cannot be null");
+ }
+ if (routeGroupId == null) {
+ throw new IllegalArgumentException("routeGroupId cannot be null");
+ }
+ return createRouteController(routeId, routeGroupId);
+ }
+
+ @Override
+ public void onDiscoveryRequestChanged(MediaRouteDiscoveryRequest request) {
+ if (mConnectionReady) {
+ mActiveConnection.setDiscoveryRequest(request);
+ }
+ updateBinding();
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ if (DEBUG) {
+ Log.d(TAG, this + ": Connected");
+ }
+
+ if (mBound) {
+ disconnect();
+
+ Messenger messenger = (service != null ? new Messenger(service) : null);
+ if (isValidRemoteMessenger(messenger)) {
+ Connection connection = new Connection(messenger);
+ if (connection.register()) {
+ mActiveConnection = connection;
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, this + ": Registration failed");
+ }
+ }
+ } else {
+ Log.e(TAG, this + ": Service returned invalid messenger binder");
+ }
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ if (DEBUG) {
+ Log.d(TAG, this + ": Service disconnected");
+ }
+ disconnect();
+ }
+
+ @Override
+ public String toString() {
+ return "Service connection " + mComponentName.flattenToShortString();
+ }
+
+ public boolean hasComponentName(String packageName, String className) {
+ return mComponentName.getPackageName().equals(packageName)
+ && mComponentName.getClassName().equals(className);
+ }
+
+ public void start() {
+ if (!mStarted) {
+ if (DEBUG) {
+ Log.d(TAG, this + ": Starting");
+ }
+
+ mStarted = true;
+ updateBinding();
+ }
+ }
+
+ public void stop() {
+ if (mStarted) {
+ if (DEBUG) {
+ Log.d(TAG, this + ": Stopping");
+ }
+
+ mStarted = false;
+ updateBinding();
+ }
+ }
+
+ public void rebindIfDisconnected() {
+ if (mActiveConnection == null && shouldBind()) {
+ unbind();
+ bind();
+ }
+ }
+
+ private void updateBinding() {
+ if (shouldBind()) {
+ bind();
+ } else {
+ unbind();
+ }
+ }
+
+ private boolean shouldBind() {
+ if (mStarted) {
+ // Bind whenever there is a discovery request.
+ if (getDiscoveryRequest() != null) {
+ return true;
+ }
+
+ // Bind whenever the application has an active route controller.
+ // This means that one of this provider's routes is selected.
+ if (!mControllers.isEmpty()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void bind() {
+ if (!mBound) {
+ if (DEBUG) {
+ Log.d(TAG, this + ": Binding");
+ }
+
+ Intent service = new Intent(MediaRouteProviderProtocol.SERVICE_INTERFACE);
+ service.setComponent(mComponentName);
+ try {
+ mBound = getContext().bindService(service, this, Context.BIND_AUTO_CREATE);
+ if (!mBound && DEBUG) {
+ Log.d(TAG, this + ": Bind failed");
+ }
+ } catch (SecurityException ex) {
+ if (DEBUG) {
+ Log.d(TAG, this + ": Bind failed", ex);
+ }
+ }
+ }
+ }
+
+ private void unbind() {
+ if (mBound) {
+ if (DEBUG) {
+ Log.d(TAG, this + ": Unbinding");
+ }
+
+ mBound = false;
+ disconnect();
+ getContext().unbindService(this);
+ }
+ }
+
+ private RouteController createRouteController(String routeId, String routeGroupId) {
+ MediaRouteProviderDescriptor descriptor = getDescriptor();
+ if (descriptor != null) {
+ List<MediaRouteDescriptor> routes = descriptor.getRoutes();
+ final int count = routes.size();
+ for (int i = 0; i < count; i++) {
+ final MediaRouteDescriptor route = routes.get(i);
+ if (route.getId().equals(routeId)) {
+ Controller controller = new Controller(routeId, routeGroupId);
+ mControllers.add(controller);
+ if (mConnectionReady) {
+ controller.attachConnection(mActiveConnection);
+ }
+ updateBinding();
+ return controller;
+ }
+ }
+ }
+ return null;
+ }
+
+ void onConnectionReady(Connection connection) {
+ if (mActiveConnection == connection) {
+ mConnectionReady = true;
+ attachControllersToConnection();
+
+ MediaRouteDiscoveryRequest request = getDiscoveryRequest();
+ if (request != null) {
+ mActiveConnection.setDiscoveryRequest(request);
+ }
+ }
+ }
+
+ void onConnectionDied(Connection connection) {
+ if (mActiveConnection == connection) {
+ if (DEBUG) {
+ Log.d(TAG, this + ": Service connection died");
+ }
+ disconnect();
+ }
+ }
+
+ void onConnectionError(Connection connection, String error) {
+ if (mActiveConnection == connection) {
+ if (DEBUG) {
+ Log.d(TAG, this + ": Service connection error - " + error);
+ }
+ unbind();
+ }
+ }
+
+ void onConnectionDescriptorChanged(Connection connection,
+ MediaRouteProviderDescriptor descriptor) {
+ if (mActiveConnection == connection) {
+ if (DEBUG) {
+ Log.d(TAG, this + ": Descriptor changed, descriptor=" + descriptor);
+ }
+ setDescriptor(descriptor);
+ }
+ }
+
+ private void disconnect() {
+ if (mActiveConnection != null) {
+ setDescriptor(null);
+ mConnectionReady = false;
+ detachControllersFromConnection();
+ mActiveConnection.dispose();
+ mActiveConnection = null;
+ }
+ }
+
+ void onControllerReleased(Controller controller) {
+ mControllers.remove(controller);
+ controller.detachConnection();
+ updateBinding();
+ }
+
+ private void attachControllersToConnection() {
+ int count = mControllers.size();
+ for (int i = 0; i < count; i++) {
+ mControllers.get(i).attachConnection(mActiveConnection);
+ }
+ }
+
+ private void detachControllersFromConnection() {
+ int count = mControllers.size();
+ for (int i = 0; i < count; i++) {
+ mControllers.get(i).detachConnection();
+ }
+ }
+
+ private final class Controller extends RouteController {
+ private final String mRouteId;
+ private final String mRouteGroupId;
+
+ private boolean mSelected;
+ private int mPendingSetVolume = -1;
+ private int mPendingUpdateVolumeDelta;
+
+ private Connection mConnection;
+ private int mControllerId;
+
+ public Controller(String routeId, String routeGroupId) {
+ mRouteId = routeId;
+ mRouteGroupId = routeGroupId;
+ }
+
+ public void attachConnection(Connection connection) {
+ mConnection = connection;
+ mControllerId = connection.createRouteController(mRouteId, mRouteGroupId);
+ if (mSelected) {
+ connection.selectRoute(mControllerId);
+ if (mPendingSetVolume >= 0) {
+ connection.setVolume(mControllerId, mPendingSetVolume);
+ mPendingSetVolume = -1;
+ }
+ if (mPendingUpdateVolumeDelta != 0) {
+ connection.updateVolume(mControllerId, mPendingUpdateVolumeDelta);
+ mPendingUpdateVolumeDelta = 0;
+ }
+ }
+ }
+
+ public void detachConnection() {
+ if (mConnection != null) {
+ mConnection.releaseRouteController(mControllerId);
+ mConnection = null;
+ mControllerId = 0;
+ }
+ }
+
+ @Override
+ public void onRelease() {
+ onControllerReleased(this);
+ }
+
+ @Override
+ public void onSelect() {
+ mSelected = true;
+ if (mConnection != null) {
+ mConnection.selectRoute(mControllerId);
+ }
+ }
+
+ @Override
+ public void onUnselect() {
+ onUnselect(MediaRouter.UNSELECT_REASON_UNKNOWN);
+ }
+
+ @Override
+ public void onUnselect(int reason) {
+ mSelected = false;
+ if (mConnection != null) {
+ mConnection.unselectRoute(mControllerId, reason);
+ }
+ }
+
+ @Override
+ public void onSetVolume(int volume) {
+ if (mConnection != null) {
+ mConnection.setVolume(mControllerId, volume);
+ } else {
+ mPendingSetVolume = volume;
+ mPendingUpdateVolumeDelta = 0;
+ }
+ }
+
+ @Override
+ public void onUpdateVolume(int delta) {
+ if (mConnection != null) {
+ mConnection.updateVolume(mControllerId, delta);
+ } else {
+ mPendingUpdateVolumeDelta += delta;
+ }
+ }
+
+ @Override
+ public boolean onControlRequest(Intent intent, ControlRequestCallback callback) {
+ if (mConnection != null) {
+ return mConnection.sendControlRequest(mControllerId, intent, callback);
+ }
+ return false;
+ }
+ }
+
+ private final class Connection implements DeathRecipient {
+ private final Messenger mServiceMessenger;
+ private final ReceiveHandler mReceiveHandler;
+ private final Messenger mReceiveMessenger;
+
+ private int mNextRequestId = 1;
+ private int mNextControllerId = 1;
+ private int mServiceVersion; // non-zero when registration complete
+
+ private int mPendingRegisterRequestId;
+ private final SparseArray<ControlRequestCallback> mPendingCallbacks =
+ new SparseArray<ControlRequestCallback>();
+
+ public Connection(Messenger serviceMessenger) {
+ mServiceMessenger = serviceMessenger;
+ mReceiveHandler = new ReceiveHandler(this);
+ mReceiveMessenger = new Messenger(mReceiveHandler);
+ }
+
+ public boolean register() {
+ mPendingRegisterRequestId = mNextRequestId++;
+ if (!sendRequest(CLIENT_MSG_REGISTER,
+ mPendingRegisterRequestId,
+ CLIENT_VERSION_CURRENT, null, null)) {
+ return false;
+ }
+
+ try {
+ mServiceMessenger.getBinder().linkToDeath(this, 0);
+ return true;
+ } catch (RemoteException ex) {
+ binderDied();
+ }
+ return false;
+ }
+
+ public void dispose() {
+ sendRequest(CLIENT_MSG_UNREGISTER, 0, 0, null, null);
+ mReceiveHandler.dispose();
+ mServiceMessenger.getBinder().unlinkToDeath(this, 0);
+
+ mPrivateHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ failPendingCallbacks();
+ }
+ });
+ }
+
+ void failPendingCallbacks() {
+ int count = 0;
+ for (int i = 0; i < mPendingCallbacks.size(); i++) {
+ mPendingCallbacks.valueAt(i).onError(null, null);
+ }
+ mPendingCallbacks.clear();
+ }
+
+ public boolean onGenericFailure(int requestId) {
+ if (requestId == mPendingRegisterRequestId) {
+ mPendingRegisterRequestId = 0;
+ onConnectionError(this, "Registration failed");
+ }
+ ControlRequestCallback callback = mPendingCallbacks.get(requestId);
+ if (callback != null) {
+ mPendingCallbacks.remove(requestId);
+ callback.onError(null, null);
+ }
+ return true;
+ }
+
+ public boolean onGenericSuccess(int requestId) {
+ return true;
+ }
+
+ public boolean onRegistered(int requestId, int serviceVersion,
+ Bundle descriptorBundle) {
+ if (mServiceVersion == 0
+ && requestId == mPendingRegisterRequestId
+ && serviceVersion >= SERVICE_VERSION_1) {
+ mPendingRegisterRequestId = 0;
+ mServiceVersion = serviceVersion;
+ onConnectionDescriptorChanged(this,
+ MediaRouteProviderDescriptor.fromBundle(descriptorBundle));
+ onConnectionReady(this);
+ return true;
+ }
+ return false;
+ }
+
+ public boolean onDescriptorChanged(Bundle descriptorBundle) {
+ if (mServiceVersion != 0) {
+ onConnectionDescriptorChanged(this,
+ MediaRouteProviderDescriptor.fromBundle(descriptorBundle));
+ return true;
+ }
+ return false;
+ }
+
+ public boolean onControlRequestSucceeded(int requestId, Bundle data) {
+ ControlRequestCallback callback = mPendingCallbacks.get(requestId);
+ if (callback != null) {
+ mPendingCallbacks.remove(requestId);
+ callback.onResult(data);
+ return true;
+ }
+ return false;
+ }
+
+ public boolean onControlRequestFailed(int requestId, String error, Bundle data) {
+ ControlRequestCallback callback = mPendingCallbacks.get(requestId);
+ if (callback != null) {
+ mPendingCallbacks.remove(requestId);
+ callback.onError(error, data);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void binderDied() {
+ mPrivateHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ onConnectionDied(Connection.this);
+ }
+ });
+ }
+
+ public int createRouteController(String routeId, String routeGroupId) {
+ int controllerId = mNextControllerId++;
+ Bundle data = new Bundle();
+ data.putString(CLIENT_DATA_ROUTE_ID, routeId);
+ data.putString(CLIENT_DATA_ROUTE_LIBRARY_GROUP, routeGroupId);
+ sendRequest(CLIENT_MSG_CREATE_ROUTE_CONTROLLER,
+ mNextRequestId++, controllerId, null, data);
+ return controllerId;
+ }
+
+ public void releaseRouteController(int controllerId) {
+ sendRequest(CLIENT_MSG_RELEASE_ROUTE_CONTROLLER,
+ mNextRequestId++, controllerId, null, null);
+ }
+
+ public void selectRoute(int controllerId) {
+ sendRequest(CLIENT_MSG_SELECT_ROUTE,
+ mNextRequestId++, controllerId, null, null);
+ }
+
+ public void unselectRoute(int controllerId, int reason) {
+ Bundle extras = new Bundle();
+ extras.putInt(CLIENT_DATA_UNSELECT_REASON, reason);
+ sendRequest(CLIENT_MSG_UNSELECT_ROUTE,
+ mNextRequestId++, controllerId, null, extras);
+ }
+
+ public void setVolume(int controllerId, int volume) {
+ Bundle data = new Bundle();
+ data.putInt(CLIENT_DATA_VOLUME, volume);
+ sendRequest(CLIENT_MSG_SET_ROUTE_VOLUME,
+ mNextRequestId++, controllerId, null, data);
+ }
+
+ public void updateVolume(int controllerId, int delta) {
+ Bundle data = new Bundle();
+ data.putInt(CLIENT_DATA_VOLUME, delta);
+ sendRequest(CLIENT_MSG_UPDATE_ROUTE_VOLUME,
+ mNextRequestId++, controllerId, null, data);
+ }
+
+ public boolean sendControlRequest(int controllerId, Intent intent,
+ ControlRequestCallback callback) {
+ int requestId = mNextRequestId++;
+ if (sendRequest(CLIENT_MSG_ROUTE_CONTROL_REQUEST,
+ requestId, controllerId, intent, null)) {
+ if (callback != null) {
+ mPendingCallbacks.put(requestId, callback);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ public void setDiscoveryRequest(MediaRouteDiscoveryRequest request) {
+ sendRequest(CLIENT_MSG_SET_DISCOVERY_REQUEST,
+ mNextRequestId++, 0, request != null ? request.asBundle() : null, null);
+ }
+
+ private boolean sendRequest(int what, int requestId, int arg, Object obj, Bundle data) {
+ Message msg = Message.obtain();
+ msg.what = what;
+ msg.arg1 = requestId;
+ msg.arg2 = arg;
+ msg.obj = obj;
+ msg.setData(data);
+ msg.replyTo = mReceiveMessenger;
+ try {
+ mServiceMessenger.send(msg);
+ return true;
+ } catch (DeadObjectException ex) {
+ // The service died.
+ } catch (RemoteException ex) {
+ if (what != CLIENT_MSG_UNREGISTER) {
+ Log.e(TAG, "Could not send message to service.", ex);
+ }
+ }
+ return false;
+ }
+ }
+
+ private static final class PrivateHandler extends Handler {
+ PrivateHandler() {
+ }
+ }
+
+ /**
+ * Handler that receives messages from the server.
+ * <p>
+ * This inner class is static and only retains a weak reference to the connection
+ * to prevent the client from being leaked in case the service is holding an
+ * active reference to the client's messenger.
+ * </p><p>
+ * This handler should not be used to handle any messages other than those
+ * that come from the service.
+ * </p>
+ */
+ private static final class ReceiveHandler extends Handler {
+ private final WeakReference<Connection> mConnectionRef;
+
+ public ReceiveHandler(Connection connection) {
+ mConnectionRef = new WeakReference<Connection>(connection);
+ }
+
+ public void dispose() {
+ mConnectionRef.clear();
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ Connection connection = mConnectionRef.get();
+ if (connection != null) {
+ final int what = msg.what;
+ final int requestId = msg.arg1;
+ final int arg = msg.arg2;
+ final Object obj = msg.obj;
+ final Bundle data = msg.peekData();
+ if (!processMessage(connection, what, requestId, arg, obj, data)) {
+ if (DEBUG) {
+ Log.d(TAG, "Unhandled message from server: " + msg);
+ }
+ }
+ }
+ }
+
+ private boolean processMessage(Connection connection,
+ int what, int requestId, int arg, Object obj, Bundle data) {
+ switch (what) {
+ case SERVICE_MSG_GENERIC_FAILURE:
+ connection.onGenericFailure(requestId);
+ return true;
+
+ case SERVICE_MSG_GENERIC_SUCCESS:
+ connection.onGenericSuccess(requestId);
+ return true;
+
+ case SERVICE_MSG_REGISTERED:
+ if (obj == null || obj instanceof Bundle) {
+ return connection.onRegistered(requestId, arg, (Bundle)obj);
+ }
+ break;
+
+ case SERVICE_MSG_DESCRIPTOR_CHANGED:
+ if (obj == null || obj instanceof Bundle) {
+ return connection.onDescriptorChanged((Bundle)obj);
+ }
+ break;
+
+ case SERVICE_MSG_CONTROL_REQUEST_SUCCEEDED:
+ if (obj == null || obj instanceof Bundle) {
+ return connection.onControlRequestSucceeded(
+ requestId, (Bundle)obj);
+ }
+ break;
+
+ case SERVICE_MSG_CONTROL_REQUEST_FAILED:
+ if (obj == null || obj instanceof Bundle) {
+ String error = (data == null ? null :
+ data.getString(SERVICE_DATA_ERROR));
+ return connection.onControlRequestFailed(
+ requestId, error, (Bundle)obj);
+ }
+ break;
+ }
+ return false;
+ }
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/media/RegisteredMediaRouteProviderWatcher.java b/packages/MediaComponents/src/com/android/support/mediarouter/media/RegisteredMediaRouteProviderWatcher.java
new file mode 100644
index 0000000..ba1f647
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/media/RegisteredMediaRouteProviderWatcher.java
@@ -0,0 +1,157 @@
+/*
+ * 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.support.mediarouter.media;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.Handler;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+/**
+ * Watches for media route provider services to be installed.
+ * Adds a provider to the media router for each registered service.
+ *
+ * @see RegisteredMediaRouteProvider
+ */
+final class RegisteredMediaRouteProviderWatcher {
+ private final Context mContext;
+ private final Callback mCallback;
+ private final Handler mHandler;
+ private final PackageManager mPackageManager;
+
+ private final ArrayList<RegisteredMediaRouteProvider> mProviders =
+ new ArrayList<RegisteredMediaRouteProvider>();
+ private boolean mRunning;
+
+ public RegisteredMediaRouteProviderWatcher(Context context, Callback callback) {
+ mContext = context;
+ mCallback = callback;
+ mHandler = new Handler();
+ mPackageManager = context.getPackageManager();
+ }
+
+ public void start() {
+ if (!mRunning) {
+ mRunning = true;
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+ filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
+ filter.addDataScheme("package");
+ mContext.registerReceiver(mScanPackagesReceiver, filter, null, mHandler);
+
+ // Scan packages.
+ // Also has the side-effect of restarting providers if needed.
+ mHandler.post(mScanPackagesRunnable);
+ }
+ }
+
+ public void stop() {
+ if (mRunning) {
+ mRunning = false;
+
+ mContext.unregisterReceiver(mScanPackagesReceiver);
+ mHandler.removeCallbacks(mScanPackagesRunnable);
+
+ // Stop all providers.
+ for (int i = mProviders.size() - 1; i >= 0; i--) {
+ mProviders.get(i).stop();
+ }
+ }
+ }
+
+ void scanPackages() {
+ if (!mRunning) {
+ return;
+ }
+
+ // Add providers for all new services.
+ // Reorder the list so that providers left at the end will be the ones to remove.
+ int targetIndex = 0;
+ Intent intent = new Intent(MediaRouteProviderService.SERVICE_INTERFACE);
+ for (ResolveInfo resolveInfo : mPackageManager.queryIntentServices(intent, 0)) {
+ ServiceInfo serviceInfo = resolveInfo.serviceInfo;
+ if (serviceInfo != null) {
+ int sourceIndex = findProvider(serviceInfo.packageName, serviceInfo.name);
+ if (sourceIndex < 0) {
+ RegisteredMediaRouteProvider provider =
+ new RegisteredMediaRouteProvider(mContext,
+ new ComponentName(serviceInfo.packageName, serviceInfo.name));
+ provider.start();
+ mProviders.add(targetIndex++, provider);
+ mCallback.addProvider(provider);
+ } else if (sourceIndex >= targetIndex) {
+ RegisteredMediaRouteProvider provider = mProviders.get(sourceIndex);
+ provider.start(); // restart the provider if needed
+ provider.rebindIfDisconnected();
+ Collections.swap(mProviders, sourceIndex, targetIndex++);
+ }
+ }
+ }
+
+ // Remove providers for missing services.
+ if (targetIndex < mProviders.size()) {
+ for (int i = mProviders.size() - 1; i >= targetIndex; i--) {
+ RegisteredMediaRouteProvider provider = mProviders.get(i);
+ mCallback.removeProvider(provider);
+ mProviders.remove(provider);
+ provider.stop();
+ }
+ }
+ }
+
+ private int findProvider(String packageName, String className) {
+ int count = mProviders.size();
+ for (int i = 0; i < count; i++) {
+ RegisteredMediaRouteProvider provider = mProviders.get(i);
+ if (provider.hasComponentName(packageName, className)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ private final BroadcastReceiver mScanPackagesReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ scanPackages();
+ }
+ };
+
+ private final Runnable mScanPackagesRunnable = new Runnable() {
+ @Override
+ public void run() {
+ scanPackages();
+ }
+ };
+
+ public interface Callback {
+ void addProvider(MediaRouteProvider provider);
+ void removeProvider(MediaRouteProvider provider);
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/media/RemoteControlClientCompat.java b/packages/MediaComponents/src/com/android/support/mediarouter/media/RemoteControlClientCompat.java
new file mode 100644
index 0000000..826449b
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/media/RemoteControlClientCompat.java
@@ -0,0 +1,190 @@
+/*
+ * 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.support.mediarouter.media;
+
+import android.content.Context;
+import android.media.AudioManager;
+import android.os.Build;
+import android.support.annotation.RequiresApi;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Provides access to features of the remote control client.
+ *
+ * Hidden for now but we might want to make this available to applications
+ * in the future.
+ */
+abstract class RemoteControlClientCompat {
+ protected final Context mContext;
+ protected final Object mRcc;
+ protected VolumeCallback mVolumeCallback;
+
+ protected RemoteControlClientCompat(Context context, Object rcc) {
+ mContext = context;
+ mRcc = rcc;
+ }
+
+ public static RemoteControlClientCompat obtain(Context context, Object rcc) {
+ if (Build.VERSION.SDK_INT >= 16) {
+ return new JellybeanImpl(context, rcc);
+ }
+ return new LegacyImpl(context, rcc);
+ }
+
+ public Object getRemoteControlClient() {
+ return mRcc;
+ }
+
+ /**
+ * Sets the current playback information.
+ * Must be called at least once to attach to the remote control client.
+ *
+ * @param info The playback information. Must not be null.
+ */
+ public void setPlaybackInfo(PlaybackInfo info) {
+ }
+
+ /**
+ * Sets a callback to receive volume change requests from the remote control client.
+ *
+ * @param callback The volume callback to use or null if none.
+ */
+ public void setVolumeCallback(VolumeCallback callback) {
+ mVolumeCallback = callback;
+ }
+
+ /**
+ * Specifies information about the playback.
+ */
+ public static final class PlaybackInfo {
+ public int volume;
+ public int volumeMax;
+ public int volumeHandling = MediaRouter.RouteInfo.PLAYBACK_VOLUME_FIXED;
+ public int playbackStream = AudioManager.STREAM_MUSIC;
+ public int playbackType = MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE;
+ }
+
+ /**
+ * Called when volume updates are requested by the remote control client.
+ */
+ public interface VolumeCallback {
+ /**
+ * Called when the volume should be increased or decreased.
+ *
+ * @param direction An integer indicating whether the volume is to be increased
+ * (positive value) or decreased (negative value).
+ * For bundled changes, the absolute value indicates the number of changes
+ * in the same direction, e.g. +3 corresponds to three "volume up" changes.
+ */
+ public void onVolumeUpdateRequest(int direction);
+
+ /**
+ * Called when the volume for the route should be set to the given value.
+ *
+ * @param volume An integer indicating the new volume value that should be used,
+ * always between 0 and the value set by {@link PlaybackInfo#volumeMax}.
+ */
+ public void onVolumeSetRequest(int volume);
+ }
+
+ /**
+ * Legacy implementation for platform versions prior to Jellybean.
+ * Does nothing.
+ */
+ static class LegacyImpl extends RemoteControlClientCompat {
+ public LegacyImpl(Context context, Object rcc) {
+ super(context, rcc);
+ }
+ }
+
+ /**
+ * Implementation for Jellybean.
+ *
+ * The basic idea of this implementation is to attach the RCC to a UserRouteInfo
+ * in order to hook up stream metadata and volume callbacks because there is no
+ * other API available to do so in this platform version. The UserRouteInfo itself
+ * is not attached to the MediaRouter so it is transparent to the user.
+ */
+ // @@RequiresApi(16)
+ static class JellybeanImpl extends RemoteControlClientCompat {
+ private final Object mRouterObj;
+ private final Object mUserRouteCategoryObj;
+ private final Object mUserRouteObj;
+ private boolean mRegistered;
+
+ public JellybeanImpl(Context context, Object rcc) {
+ super(context, rcc);
+
+ mRouterObj = MediaRouterJellybean.getMediaRouter(context);
+ mUserRouteCategoryObj = MediaRouterJellybean.createRouteCategory(
+ mRouterObj, "", false);
+ mUserRouteObj = MediaRouterJellybean.createUserRoute(
+ mRouterObj, mUserRouteCategoryObj);
+ }
+
+ @Override
+ public void setPlaybackInfo(PlaybackInfo info) {
+ MediaRouterJellybean.UserRouteInfo.setVolume(
+ mUserRouteObj, info.volume);
+ MediaRouterJellybean.UserRouteInfo.setVolumeMax(
+ mUserRouteObj, info.volumeMax);
+ MediaRouterJellybean.UserRouteInfo.setVolumeHandling(
+ mUserRouteObj, info.volumeHandling);
+ MediaRouterJellybean.UserRouteInfo.setPlaybackStream(
+ mUserRouteObj, info.playbackStream);
+ MediaRouterJellybean.UserRouteInfo.setPlaybackType(
+ mUserRouteObj, info.playbackType);
+
+ if (!mRegistered) {
+ mRegistered = true;
+ MediaRouterJellybean.UserRouteInfo.setVolumeCallback(mUserRouteObj,
+ MediaRouterJellybean.createVolumeCallback(
+ new VolumeCallbackWrapper(this)));
+ MediaRouterJellybean.UserRouteInfo.setRemoteControlClient(mUserRouteObj, mRcc);
+ }
+ }
+
+ private static final class VolumeCallbackWrapper
+ implements MediaRouterJellybean.VolumeCallback {
+ // Unfortunately, the framework never unregisters its volume observer from
+ // the audio service so the UserRouteInfo object may leak along with
+ // any callbacks that we attach to it. Use a weak reference to prevent
+ // the volume callback from holding strong references to anything important.
+ private final WeakReference<JellybeanImpl> mImplWeak;
+
+ public VolumeCallbackWrapper(JellybeanImpl impl) {
+ mImplWeak = new WeakReference<JellybeanImpl>(impl);
+ }
+
+ @Override
+ public void onVolumeUpdateRequest(Object routeObj, int direction) {
+ JellybeanImpl impl = mImplWeak.get();
+ if (impl != null && impl.mVolumeCallback != null) {
+ impl.mVolumeCallback.onVolumeUpdateRequest(direction);
+ }
+ }
+
+ @Override
+ public void onVolumeSetRequest(Object routeObj, int volume) {
+ JellybeanImpl impl = mImplWeak.get();
+ if (impl != null && impl.mVolumeCallback != null) {
+ impl.mVolumeCallback.onVolumeSetRequest(volume);
+ }
+ }
+ }
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/media/RemotePlaybackClient.java b/packages/MediaComponents/src/com/android/support/mediarouter/media/RemotePlaybackClient.java
new file mode 100644
index 0000000..f6e1497
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/media/RemotePlaybackClient.java
@@ -0,0 +1,1044 @@
+/*
+ * 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.support.mediarouter.media;
+
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v4.util.ObjectsCompat;
+import android.util.Log;
+
+/**
+ * A helper class for playing media on remote routes using the remote playback protocol
+ * defined by {@link MediaControlIntent}.
+ * <p>
+ * The client maintains session state and offers a simplified interface for issuing
+ * remote playback media control intents to a single route.
+ * </p>
+ */
+public class RemotePlaybackClient {
+ static final String TAG = "RemotePlaybackClient";
+ static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private final Context mContext;
+ private final MediaRouter.RouteInfo mRoute;
+ private final ActionReceiver mActionReceiver;
+ private final PendingIntent mItemStatusPendingIntent;
+ private final PendingIntent mSessionStatusPendingIntent;
+ private final PendingIntent mMessagePendingIntent;
+
+ private boolean mRouteSupportsRemotePlayback;
+ private boolean mRouteSupportsQueuing;
+ private boolean mRouteSupportsSessionManagement;
+ private boolean mRouteSupportsMessaging;
+
+ String mSessionId;
+ StatusCallback mStatusCallback;
+ OnMessageReceivedListener mOnMessageReceivedListener;
+
+ /**
+ * Creates a remote playback client for a route.
+ *
+ * @param route The media route.
+ */
+ public RemotePlaybackClient(Context context, MediaRouter.RouteInfo route) {
+ if (context == null) {
+ throw new IllegalArgumentException("context must not be null");
+ }
+ if (route == null) {
+ throw new IllegalArgumentException("route must not be null");
+ }
+
+ mContext = context;
+ mRoute = route;
+
+ IntentFilter actionFilter = new IntentFilter();
+ actionFilter.addAction(ActionReceiver.ACTION_ITEM_STATUS_CHANGED);
+ actionFilter.addAction(ActionReceiver.ACTION_SESSION_STATUS_CHANGED);
+ actionFilter.addAction(ActionReceiver.ACTION_MESSAGE_RECEIVED);
+ mActionReceiver = new ActionReceiver();
+ context.registerReceiver(mActionReceiver, actionFilter);
+
+ Intent itemStatusIntent = new Intent(ActionReceiver.ACTION_ITEM_STATUS_CHANGED);
+ itemStatusIntent.setPackage(context.getPackageName());
+ mItemStatusPendingIntent = PendingIntent.getBroadcast(
+ context, 0, itemStatusIntent, 0);
+
+ Intent sessionStatusIntent = new Intent(ActionReceiver.ACTION_SESSION_STATUS_CHANGED);
+ sessionStatusIntent.setPackage(context.getPackageName());
+ mSessionStatusPendingIntent = PendingIntent.getBroadcast(
+ context, 0, sessionStatusIntent, 0);
+
+ Intent messageIntent = new Intent(ActionReceiver.ACTION_MESSAGE_RECEIVED);
+ messageIntent.setPackage(context.getPackageName());
+ mMessagePendingIntent = PendingIntent.getBroadcast(
+ context, 0, messageIntent, 0);
+ detectFeatures();
+ }
+
+ /**
+ * Releases resources owned by the client.
+ */
+ public void release() {
+ mContext.unregisterReceiver(mActionReceiver);
+ }
+
+ /**
+ * Returns true if the route supports remote playback.
+ * <p>
+ * If the route does not support remote playback, then none of the functionality
+ * offered by the client will be available.
+ * </p><p>
+ * This method returns true if the route supports all of the following
+ * actions: {@link MediaControlIntent#ACTION_PLAY play},
+ * {@link MediaControlIntent#ACTION_SEEK seek},
+ * {@link MediaControlIntent#ACTION_GET_STATUS get status},
+ * {@link MediaControlIntent#ACTION_PAUSE pause},
+ * {@link MediaControlIntent#ACTION_RESUME resume},
+ * {@link MediaControlIntent#ACTION_STOP stop}.
+ * </p>
+ *
+ * @return True if remote playback is supported.
+ */
+ public boolean isRemotePlaybackSupported() {
+ return mRouteSupportsRemotePlayback;
+ }
+
+ /**
+ * Returns true if the route supports queuing features.
+ * <p>
+ * If the route does not support queuing, then at most one media item can be played
+ * at a time and the {@link #enqueue} method will not be available.
+ * </p><p>
+ * This method returns true if the route supports all of the basic remote playback
+ * actions and all of the following actions:
+ * {@link MediaControlIntent#ACTION_ENQUEUE enqueue},
+ * {@link MediaControlIntent#ACTION_REMOVE remove}.
+ * </p>
+ *
+ * @return True if queuing is supported. Implies {@link #isRemotePlaybackSupported}
+ * is also true.
+ *
+ * @see #isRemotePlaybackSupported
+ */
+ public boolean isQueuingSupported() {
+ return mRouteSupportsQueuing;
+ }
+
+ /**
+ * Returns true if the route supports session management features.
+ * <p>
+ * If the route does not support session management, then the session will
+ * not be created until the first media item is played.
+ * </p><p>
+ * This method returns true if the route supports all of the basic remote playback
+ * actions and all of the following actions:
+ * {@link MediaControlIntent#ACTION_START_SESSION start session},
+ * {@link MediaControlIntent#ACTION_GET_SESSION_STATUS get session status},
+ * {@link MediaControlIntent#ACTION_END_SESSION end session}.
+ * </p>
+ *
+ * @return True if session management is supported.
+ * Implies {@link #isRemotePlaybackSupported} is also true.
+ *
+ * @see #isRemotePlaybackSupported
+ */
+ public boolean isSessionManagementSupported() {
+ return mRouteSupportsSessionManagement;
+ }
+
+ /**
+ * Returns true if the route supports messages.
+ * <p>
+ * This method returns true if the route supports all of the basic remote playback
+ * actions and all of the following actions:
+ * {@link MediaControlIntent#ACTION_START_SESSION start session},
+ * {@link MediaControlIntent#ACTION_SEND_MESSAGE send message},
+ * {@link MediaControlIntent#ACTION_END_SESSION end session}.
+ * </p>
+ *
+ * @return True if session management is supported.
+ * Implies {@link #isRemotePlaybackSupported} is also true.
+ *
+ * @see #isRemotePlaybackSupported
+ */
+ public boolean isMessagingSupported() {
+ return mRouteSupportsMessaging;
+ }
+
+ /**
+ * Gets the current session id if there is one.
+ *
+ * @return The current session id, or null if none.
+ */
+ public String getSessionId() {
+ return mSessionId;
+ }
+
+ /**
+ * Sets the current session id.
+ * <p>
+ * It is usually not necessary to set the session id explicitly since
+ * it is created as a side-effect of other requests such as
+ * {@link #play}, {@link #enqueue}, and {@link #startSession}.
+ * </p>
+ *
+ * @param sessionId The new session id, or null if none.
+ */
+ public void setSessionId(String sessionId) {
+ if (!ObjectsCompat.equals(mSessionId, sessionId)) {
+ if (DEBUG) {
+ Log.d(TAG, "Session id is now: " + sessionId);
+ }
+ mSessionId = sessionId;
+ if (mStatusCallback != null) {
+ mStatusCallback.onSessionChanged(sessionId);
+ }
+ }
+ }
+
+ /**
+ * Returns true if the client currently has a session.
+ * <p>
+ * Equivalent to checking whether {@link #getSessionId} returns a non-null result.
+ * </p>
+ *
+ * @return True if there is a current session.
+ */
+ public boolean hasSession() {
+ return mSessionId != null;
+ }
+
+ /**
+ * Sets a callback that should receive status updates when the state of
+ * media sessions or media items created by this instance of the remote
+ * playback client changes.
+ * <p>
+ * The callback should be set before the session is created or any play
+ * commands are issued.
+ * </p>
+ *
+ * @param callback The callback to set. May be null to remove the previous callback.
+ */
+ public void setStatusCallback(StatusCallback callback) {
+ mStatusCallback = callback;
+ }
+
+ /**
+ * Sets a callback that should receive messages when a message is sent from
+ * media sessions created by this instance of the remote playback client changes.
+ * <p>
+ * The callback should be set before the session is created.
+ * </p>
+ *
+ * @param listener The callback to set. May be null to remove the previous callback.
+ */
+ public void setOnMessageReceivedListener(OnMessageReceivedListener listener) {
+ mOnMessageReceivedListener = listener;
+ }
+
+ /**
+ * Sends a request to play a media item.
+ * <p>
+ * Clears the queue and starts playing the new item immediately. If the queue
+ * was previously paused, then it is resumed as a side-effect of this request.
+ * </p><p>
+ * The request is issued in the current session. If no session is available, then
+ * one is created implicitly.
+ * </p><p>
+ * Please refer to {@link MediaControlIntent#ACTION_PLAY ACTION_PLAY} for
+ * more information about the semantics of this request.
+ * </p>
+ *
+ * @param contentUri The content Uri to play.
+ * @param mimeType The mime type of the content, or null if unknown.
+ * @param positionMillis The initial content position for the item in milliseconds,
+ * or <code>0</code> to start at the beginning.
+ * @param metadata The media item metadata bundle, or null if none.
+ * @param extras A bundle of extra arguments to be added to the
+ * {@link MediaControlIntent#ACTION_PLAY} intent, or null if none.
+ * @param callback A callback to invoke when the request has been
+ * processed, or null if none.
+ *
+ * @throws UnsupportedOperationException if the route does not support remote playback.
+ *
+ * @see MediaControlIntent#ACTION_PLAY
+ * @see #isRemotePlaybackSupported
+ */
+ public void play(Uri contentUri, String mimeType, Bundle metadata,
+ long positionMillis, Bundle extras, ItemActionCallback callback) {
+ playOrEnqueue(contentUri, mimeType, metadata, positionMillis,
+ extras, callback, MediaControlIntent.ACTION_PLAY);
+ }
+
+ /**
+ * Sends a request to enqueue a media item.
+ * <p>
+ * Enqueues a new item to play. If the queue was previously paused, then will
+ * remain paused.
+ * </p><p>
+ * The request is issued in the current session. If no session is available, then
+ * one is created implicitly.
+ * </p><p>
+ * Please refer to {@link MediaControlIntent#ACTION_ENQUEUE ACTION_ENQUEUE} for
+ * more information about the semantics of this request.
+ * </p>
+ *
+ * @param contentUri The content Uri to enqueue.
+ * @param mimeType The mime type of the content, or null if unknown.
+ * @param positionMillis The initial content position for the item in milliseconds,
+ * or <code>0</code> to start at the beginning.
+ * @param metadata The media item metadata bundle, or null if none.
+ * @param extras A bundle of extra arguments to be added to the
+ * {@link MediaControlIntent#ACTION_ENQUEUE} intent, or null if none.
+ * @param callback A callback to invoke when the request has been
+ * processed, or null if none.
+ *
+ * @throws UnsupportedOperationException if the route does not support queuing.
+ *
+ * @see MediaControlIntent#ACTION_ENQUEUE
+ * @see #isRemotePlaybackSupported
+ * @see #isQueuingSupported
+ */
+ public void enqueue(Uri contentUri, String mimeType, Bundle metadata,
+ long positionMillis, Bundle extras, ItemActionCallback callback) {
+ playOrEnqueue(contentUri, mimeType, metadata, positionMillis,
+ extras, callback, MediaControlIntent.ACTION_ENQUEUE);
+ }
+
+ private void playOrEnqueue(Uri contentUri, String mimeType, Bundle metadata,
+ long positionMillis, Bundle extras,
+ final ItemActionCallback callback, String action) {
+ if (contentUri == null) {
+ throw new IllegalArgumentException("contentUri must not be null");
+ }
+ throwIfRemotePlaybackNotSupported();
+ if (action.equals(MediaControlIntent.ACTION_ENQUEUE)) {
+ throwIfQueuingNotSupported();
+ }
+
+ Intent intent = new Intent(action);
+ intent.setDataAndType(contentUri, mimeType);
+ intent.putExtra(MediaControlIntent.EXTRA_ITEM_STATUS_UPDATE_RECEIVER,
+ mItemStatusPendingIntent);
+ if (metadata != null) {
+ intent.putExtra(MediaControlIntent.EXTRA_ITEM_METADATA, metadata);
+ }
+ if (positionMillis != 0) {
+ intent.putExtra(MediaControlIntent.EXTRA_ITEM_CONTENT_POSITION, positionMillis);
+ }
+ performItemAction(intent, mSessionId, null, extras, callback);
+ }
+
+ /**
+ * Sends a request to seek to a new position in a media item.
+ * <p>
+ * Seeks to a new position. If the queue was previously paused then it
+ * remains paused but the item's new position is still remembered.
+ * </p><p>
+ * The request is issued in the current session.
+ * </p><p>
+ * Please refer to {@link MediaControlIntent#ACTION_SEEK ACTION_SEEK} for
+ * more information about the semantics of this request.
+ * </p>
+ *
+ * @param itemId The item id.
+ * @param positionMillis The new content position for the item in milliseconds,
+ * or <code>0</code> to start at the beginning.
+ * @param extras A bundle of extra arguments to be added to the
+ * {@link MediaControlIntent#ACTION_SEEK} intent, or null if none.
+ * @param callback A callback to invoke when the request has been
+ * processed, or null if none.
+ *
+ * @throws IllegalStateException if there is no current session.
+ *
+ * @see MediaControlIntent#ACTION_SEEK
+ * @see #isRemotePlaybackSupported
+ */
+ public void seek(String itemId, long positionMillis, Bundle extras,
+ ItemActionCallback callback) {
+ if (itemId == null) {
+ throw new IllegalArgumentException("itemId must not be null");
+ }
+ throwIfNoCurrentSession();
+
+ Intent intent = new Intent(MediaControlIntent.ACTION_SEEK);
+ intent.putExtra(MediaControlIntent.EXTRA_ITEM_CONTENT_POSITION, positionMillis);
+ performItemAction(intent, mSessionId, itemId, extras, callback);
+ }
+
+ /**
+ * Sends a request to get the status of a media item.
+ * <p>
+ * The request is issued in the current session.
+ * </p><p>
+ * Please refer to {@link MediaControlIntent#ACTION_GET_STATUS ACTION_GET_STATUS} for
+ * more information about the semantics of this request.
+ * </p>
+ *
+ * @param itemId The item id.
+ * @param extras A bundle of extra arguments to be added to the
+ * {@link MediaControlIntent#ACTION_GET_STATUS} intent, or null if none.
+ * @param callback A callback to invoke when the request has been
+ * processed, or null if none.
+ *
+ * @throws IllegalStateException if there is no current session.
+ *
+ * @see MediaControlIntent#ACTION_GET_STATUS
+ * @see #isRemotePlaybackSupported
+ */
+ public void getStatus(String itemId, Bundle extras, ItemActionCallback callback) {
+ if (itemId == null) {
+ throw new IllegalArgumentException("itemId must not be null");
+ }
+ throwIfNoCurrentSession();
+
+ Intent intent = new Intent(MediaControlIntent.ACTION_GET_STATUS);
+ performItemAction(intent, mSessionId, itemId, extras, callback);
+ }
+
+ /**
+ * Sends a request to remove a media item from the queue.
+ * <p>
+ * The request is issued in the current session.
+ * </p><p>
+ * Please refer to {@link MediaControlIntent#ACTION_REMOVE ACTION_REMOVE} for
+ * more information about the semantics of this request.
+ * </p>
+ *
+ * @param itemId The item id.
+ * @param extras A bundle of extra arguments to be added to the
+ * {@link MediaControlIntent#ACTION_REMOVE} intent, or null if none.
+ * @param callback A callback to invoke when the request has been
+ * processed, or null if none.
+ *
+ * @throws IllegalStateException if there is no current session.
+ * @throws UnsupportedOperationException if the route does not support queuing.
+ *
+ * @see MediaControlIntent#ACTION_REMOVE
+ * @see #isRemotePlaybackSupported
+ * @see #isQueuingSupported
+ */
+ public void remove(String itemId, Bundle extras, ItemActionCallback callback) {
+ if (itemId == null) {
+ throw new IllegalArgumentException("itemId must not be null");
+ }
+ throwIfQueuingNotSupported();
+ throwIfNoCurrentSession();
+
+ Intent intent = new Intent(MediaControlIntent.ACTION_REMOVE);
+ performItemAction(intent, mSessionId, itemId, extras, callback);
+ }
+
+ /**
+ * Sends a request to pause media playback.
+ * <p>
+ * The request is issued in the current session. If playback is already paused
+ * then the request has no effect.
+ * </p><p>
+ * Please refer to {@link MediaControlIntent#ACTION_PAUSE ACTION_PAUSE} for
+ * more information about the semantics of this request.
+ * </p>
+ *
+ * @param extras A bundle of extra arguments to be added to the
+ * {@link MediaControlIntent#ACTION_PAUSE} intent, or null if none.
+ * @param callback A callback to invoke when the request has been
+ * processed, or null if none.
+ *
+ * @throws IllegalStateException if there is no current session.
+ *
+ * @see MediaControlIntent#ACTION_PAUSE
+ * @see #isRemotePlaybackSupported
+ */
+ public void pause(Bundle extras, SessionActionCallback callback) {
+ throwIfNoCurrentSession();
+
+ Intent intent = new Intent(MediaControlIntent.ACTION_PAUSE);
+ performSessionAction(intent, mSessionId, extras, callback);
+ }
+
+ /**
+ * Sends a request to resume (unpause) media playback.
+ * <p>
+ * The request is issued in the current session. If playback is not paused
+ * then the request has no effect.
+ * </p><p>
+ * Please refer to {@link MediaControlIntent#ACTION_RESUME ACTION_RESUME} for
+ * more information about the semantics of this request.
+ * </p>
+ *
+ * @param extras A bundle of extra arguments to be added to the
+ * {@link MediaControlIntent#ACTION_RESUME} intent, or null if none.
+ * @param callback A callback to invoke when the request has been
+ * processed, or null if none.
+ *
+ * @throws IllegalStateException if there is no current session.
+ *
+ * @see MediaControlIntent#ACTION_RESUME
+ * @see #isRemotePlaybackSupported
+ */
+ public void resume(Bundle extras, SessionActionCallback callback) {
+ throwIfNoCurrentSession();
+
+ Intent intent = new Intent(MediaControlIntent.ACTION_RESUME);
+ performSessionAction(intent, mSessionId, extras, callback);
+ }
+
+ /**
+ * Sends a request to stop media playback and clear the media playback queue.
+ * <p>
+ * The request is issued in the current session. If the queue is already
+ * empty then the request has no effect.
+ * </p><p>
+ * Please refer to {@link MediaControlIntent#ACTION_STOP ACTION_STOP} for
+ * more information about the semantics of this request.
+ * </p>
+ *
+ * @param extras A bundle of extra arguments to be added to the
+ * {@link MediaControlIntent#ACTION_STOP} intent, or null if none.
+ * @param callback A callback to invoke when the request has been
+ * processed, or null if none.
+ *
+ * @throws IllegalStateException if there is no current session.
+ *
+ * @see MediaControlIntent#ACTION_STOP
+ * @see #isRemotePlaybackSupported
+ */
+ public void stop(Bundle extras, SessionActionCallback callback) {
+ throwIfNoCurrentSession();
+
+ Intent intent = new Intent(MediaControlIntent.ACTION_STOP);
+ performSessionAction(intent, mSessionId, extras, callback);
+ }
+
+ /**
+ * Sends a request to start a new media playback session.
+ * <p>
+ * The application must wait for the callback to indicate that this request
+ * is complete before issuing other requests that affect the session. If this
+ * request is successful then the previous session will be invalidated.
+ * </p><p>
+ * Please refer to {@link MediaControlIntent#ACTION_START_SESSION ACTION_START_SESSION}
+ * for more information about the semantics of this request.
+ * </p>
+ *
+ * @param extras A bundle of extra arguments to be added to the
+ * {@link MediaControlIntent#ACTION_START_SESSION} intent, or null if none.
+ * @param callback A callback to invoke when the request has been
+ * processed, or null if none.
+ *
+ * @throws UnsupportedOperationException if the route does not support session management.
+ *
+ * @see MediaControlIntent#ACTION_START_SESSION
+ * @see #isRemotePlaybackSupported
+ * @see #isSessionManagementSupported
+ */
+ public void startSession(Bundle extras, SessionActionCallback callback) {
+ throwIfSessionManagementNotSupported();
+
+ Intent intent = new Intent(MediaControlIntent.ACTION_START_SESSION);
+ intent.putExtra(MediaControlIntent.EXTRA_SESSION_STATUS_UPDATE_RECEIVER,
+ mSessionStatusPendingIntent);
+ if (mRouteSupportsMessaging) {
+ intent.putExtra(MediaControlIntent.EXTRA_MESSAGE_RECEIVER, mMessagePendingIntent);
+ }
+ performSessionAction(intent, null, extras, callback);
+ }
+
+ /**
+ * Sends a message.
+ * <p>
+ * The request is issued in the current session.
+ * </p><p>
+ * Please refer to {@link MediaControlIntent#ACTION_SEND_MESSAGE} for
+ * more information about the semantics of this request.
+ * </p>
+ *
+ * @param message A bundle message denoting {@link MediaControlIntent#EXTRA_MESSAGE}.
+ * @param callback A callback to invoke when the request has been processed, or null if none.
+ *
+ * @throws IllegalStateException if there is no current session.
+ * @throws UnsupportedOperationException if the route does not support messages.
+ *
+ * @see MediaControlIntent#ACTION_SEND_MESSAGE
+ * @see #isMessagingSupported
+ */
+ public void sendMessage(Bundle message, SessionActionCallback callback) {
+ throwIfNoCurrentSession();
+ throwIfMessageNotSupported();
+
+ Intent intent = new Intent(MediaControlIntent.ACTION_SEND_MESSAGE);
+ performSessionAction(intent, mSessionId, message, callback);
+ }
+
+ /**
+ * Sends a request to get the status of the media playback session.
+ * <p>
+ * The request is issued in the current session.
+ * </p><p>
+ * Please refer to {@link MediaControlIntent#ACTION_GET_SESSION_STATUS
+ * ACTION_GET_SESSION_STATUS} for more information about the semantics of this request.
+ * </p>
+ *
+ * @param extras A bundle of extra arguments to be added to the
+ * {@link MediaControlIntent#ACTION_GET_SESSION_STATUS} intent, or null if none.
+ * @param callback A callback to invoke when the request has been
+ * processed, or null if none.
+ *
+ * @throws IllegalStateException if there is no current session.
+ * @throws UnsupportedOperationException if the route does not support session management.
+ *
+ * @see MediaControlIntent#ACTION_GET_SESSION_STATUS
+ * @see #isRemotePlaybackSupported
+ * @see #isSessionManagementSupported
+ */
+ public void getSessionStatus(Bundle extras, SessionActionCallback callback) {
+ throwIfSessionManagementNotSupported();
+ throwIfNoCurrentSession();
+
+ Intent intent = new Intent(MediaControlIntent.ACTION_GET_SESSION_STATUS);
+ performSessionAction(intent, mSessionId, extras, callback);
+ }
+
+ /**
+ * Sends a request to end the media playback session.
+ * <p>
+ * The request is issued in the current session. If this request is successful,
+ * the {@link #getSessionId session id property} will be set to null after
+ * the callback is invoked.
+ * </p><p>
+ * Please refer to {@link MediaControlIntent#ACTION_END_SESSION ACTION_END_SESSION}
+ * for more information about the semantics of this request.
+ * </p>
+ *
+ * @param extras A bundle of extra arguments to be added to the
+ * {@link MediaControlIntent#ACTION_END_SESSION} intent, or null if none.
+ * @param callback A callback to invoke when the request has been
+ * processed, or null if none.
+ *
+ * @throws IllegalStateException if there is no current session.
+ * @throws UnsupportedOperationException if the route does not support session management.
+ *
+ * @see MediaControlIntent#ACTION_END_SESSION
+ * @see #isRemotePlaybackSupported
+ * @see #isSessionManagementSupported
+ */
+ public void endSession(Bundle extras, SessionActionCallback callback) {
+ throwIfSessionManagementNotSupported();
+ throwIfNoCurrentSession();
+
+ Intent intent = new Intent(MediaControlIntent.ACTION_END_SESSION);
+ performSessionAction(intent, mSessionId, extras, callback);
+ }
+
+ private void performItemAction(final Intent intent,
+ final String sessionId, final String itemId,
+ Bundle extras, final ItemActionCallback callback) {
+ intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
+ if (sessionId != null) {
+ intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, sessionId);
+ }
+ if (itemId != null) {
+ intent.putExtra(MediaControlIntent.EXTRA_ITEM_ID, itemId);
+ }
+ if (extras != null) {
+ intent.putExtras(extras);
+ }
+ logRequest(intent);
+ mRoute.sendControlRequest(intent, new MediaRouter.ControlRequestCallback() {
+ @Override
+ public void onResult(Bundle data) {
+ if (data != null) {
+ String sessionIdResult = inferMissingResult(sessionId,
+ data.getString(MediaControlIntent.EXTRA_SESSION_ID));
+ MediaSessionStatus sessionStatus = MediaSessionStatus.fromBundle(
+ data.getBundle(MediaControlIntent.EXTRA_SESSION_STATUS));
+ String itemIdResult = inferMissingResult(itemId,
+ data.getString(MediaControlIntent.EXTRA_ITEM_ID));
+ MediaItemStatus itemStatus = MediaItemStatus.fromBundle(
+ data.getBundle(MediaControlIntent.EXTRA_ITEM_STATUS));
+ adoptSession(sessionIdResult);
+ if (sessionIdResult != null && itemIdResult != null && itemStatus != null) {
+ if (DEBUG) {
+ Log.d(TAG, "Received result from " + intent.getAction()
+ + ": data=" + bundleToString(data)
+ + ", sessionId=" + sessionIdResult
+ + ", sessionStatus=" + sessionStatus
+ + ", itemId=" + itemIdResult
+ + ", itemStatus=" + itemStatus);
+ }
+ callback.onResult(data, sessionIdResult, sessionStatus,
+ itemIdResult, itemStatus);
+ return;
+ }
+ }
+ handleInvalidResult(intent, callback, data);
+ }
+
+ @Override
+ public void onError(String error, Bundle data) {
+ handleError(intent, callback, error, data);
+ }
+ });
+ }
+
+ private void performSessionAction(final Intent intent, final String sessionId,
+ Bundle extras, final SessionActionCallback callback) {
+ intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
+ if (sessionId != null) {
+ intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, sessionId);
+ }
+ if (extras != null) {
+ intent.putExtras(extras);
+ }
+ logRequest(intent);
+ mRoute.sendControlRequest(intent, new MediaRouter.ControlRequestCallback() {
+ @Override
+ public void onResult(Bundle data) {
+ if (data != null) {
+ String sessionIdResult = inferMissingResult(sessionId,
+ data.getString(MediaControlIntent.EXTRA_SESSION_ID));
+ MediaSessionStatus sessionStatus = MediaSessionStatus.fromBundle(
+ data.getBundle(MediaControlIntent.EXTRA_SESSION_STATUS));
+ adoptSession(sessionIdResult);
+ if (sessionIdResult != null) {
+ if (DEBUG) {
+ Log.d(TAG, "Received result from " + intent.getAction()
+ + ": data=" + bundleToString(data)
+ + ", sessionId=" + sessionIdResult
+ + ", sessionStatus=" + sessionStatus);
+ }
+ try {
+ callback.onResult(data, sessionIdResult, sessionStatus);
+ } finally {
+ if (intent.getAction().equals(MediaControlIntent.ACTION_END_SESSION)
+ && sessionIdResult.equals(mSessionId)) {
+ setSessionId(null);
+ }
+ }
+ return;
+ }
+ }
+ handleInvalidResult(intent, callback, data);
+ }
+
+ @Override
+ public void onError(String error, Bundle data) {
+ handleError(intent, callback, error, data);
+ }
+ });
+ }
+
+ void adoptSession(String sessionId) {
+ if (sessionId != null) {
+ setSessionId(sessionId);
+ }
+ }
+
+ void handleInvalidResult(Intent intent, ActionCallback callback,
+ Bundle data) {
+ Log.w(TAG, "Received invalid result data from " + intent.getAction()
+ + ": data=" + bundleToString(data));
+ callback.onError(null, MediaControlIntent.ERROR_UNKNOWN, data);
+ }
+
+ void handleError(Intent intent, ActionCallback callback,
+ String error, Bundle data) {
+ final int code;
+ if (data != null) {
+ code = data.getInt(MediaControlIntent.EXTRA_ERROR_CODE,
+ MediaControlIntent.ERROR_UNKNOWN);
+ } else {
+ code = MediaControlIntent.ERROR_UNKNOWN;
+ }
+ if (DEBUG) {
+ Log.w(TAG, "Received error from " + intent.getAction()
+ + ": error=" + error
+ + ", code=" + code
+ + ", data=" + bundleToString(data));
+ }
+ callback.onError(error, code, data);
+ }
+
+ private void detectFeatures() {
+ mRouteSupportsRemotePlayback = routeSupportsAction(MediaControlIntent.ACTION_PLAY)
+ && routeSupportsAction(MediaControlIntent.ACTION_SEEK)
+ && routeSupportsAction(MediaControlIntent.ACTION_GET_STATUS)
+ && routeSupportsAction(MediaControlIntent.ACTION_PAUSE)
+ && routeSupportsAction(MediaControlIntent.ACTION_RESUME)
+ && routeSupportsAction(MediaControlIntent.ACTION_STOP);
+ mRouteSupportsQueuing = mRouteSupportsRemotePlayback
+ && routeSupportsAction(MediaControlIntent.ACTION_ENQUEUE)
+ && routeSupportsAction(MediaControlIntent.ACTION_REMOVE);
+ mRouteSupportsSessionManagement = mRouteSupportsRemotePlayback
+ && routeSupportsAction(MediaControlIntent.ACTION_START_SESSION)
+ && routeSupportsAction(MediaControlIntent.ACTION_GET_SESSION_STATUS)
+ && routeSupportsAction(MediaControlIntent.ACTION_END_SESSION);
+ mRouteSupportsMessaging = doesRouteSupportMessaging();
+ }
+
+ private boolean routeSupportsAction(String action) {
+ return mRoute.supportsControlAction(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK, action);
+ }
+
+ private boolean doesRouteSupportMessaging() {
+ for (IntentFilter filter : mRoute.getControlFilters()) {
+ if (filter.hasAction(MediaControlIntent.ACTION_SEND_MESSAGE)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void throwIfRemotePlaybackNotSupported() {
+ if (!mRouteSupportsRemotePlayback) {
+ throw new UnsupportedOperationException("The route does not support remote playback.");
+ }
+ }
+
+ private void throwIfQueuingNotSupported() {
+ if (!mRouteSupportsQueuing) {
+ throw new UnsupportedOperationException("The route does not support queuing.");
+ }
+ }
+
+ private void throwIfSessionManagementNotSupported() {
+ if (!mRouteSupportsSessionManagement) {
+ throw new UnsupportedOperationException("The route does not support "
+ + "session management.");
+ }
+ }
+
+ private void throwIfMessageNotSupported() {
+ if (!mRouteSupportsMessaging) {
+ throw new UnsupportedOperationException("The route does not support message.");
+ }
+ }
+
+ private void throwIfNoCurrentSession() {
+ if (mSessionId == null) {
+ throw new IllegalStateException("There is no current session.");
+ }
+ }
+
+ static String inferMissingResult(String request, String result) {
+ if (result == null) {
+ // Result is missing.
+ return request;
+ }
+ if (request == null || request.equals(result)) {
+ // Request didn't specify a value or result matches request.
+ return result;
+ }
+ // Result conflicts with request.
+ return null;
+ }
+
+ private static void logRequest(Intent intent) {
+ if (DEBUG) {
+ Log.d(TAG, "Sending request: " + intent);
+ }
+ }
+
+ static String bundleToString(Bundle bundle) {
+ if (bundle != null) {
+ bundle.size(); // force bundle to be unparcelled
+ return bundle.toString();
+ }
+ return "null";
+ }
+
+ private final class ActionReceiver extends BroadcastReceiver {
+ public static final String ACTION_ITEM_STATUS_CHANGED =
+ "android.support.v7.media.actions.ACTION_ITEM_STATUS_CHANGED";
+ public static final String ACTION_SESSION_STATUS_CHANGED =
+ "android.support.v7.media.actions.ACTION_SESSION_STATUS_CHANGED";
+ public static final String ACTION_MESSAGE_RECEIVED =
+ "android.support.v7.media.actions.ACTION_MESSAGE_RECEIVED";
+
+ ActionReceiver() {
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String sessionId = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
+ if (sessionId == null || !sessionId.equals(mSessionId)) {
+ Log.w(TAG, "Discarding spurious status callback "
+ + "with missing or invalid session id: sessionId=" + sessionId);
+ return;
+ }
+
+ MediaSessionStatus sessionStatus = MediaSessionStatus.fromBundle(
+ intent.getBundleExtra(MediaControlIntent.EXTRA_SESSION_STATUS));
+ String action = intent.getAction();
+ if (action.equals(ACTION_ITEM_STATUS_CHANGED)) {
+ String itemId = intent.getStringExtra(MediaControlIntent.EXTRA_ITEM_ID);
+ if (itemId == null) {
+ Log.w(TAG, "Discarding spurious status callback with missing item id.");
+ return;
+ }
+
+ MediaItemStatus itemStatus = MediaItemStatus.fromBundle(
+ intent.getBundleExtra(MediaControlIntent.EXTRA_ITEM_STATUS));
+ if (itemStatus == null) {
+ Log.w(TAG, "Discarding spurious status callback with missing item status.");
+ return;
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "Received item status callback: sessionId=" + sessionId
+ + ", sessionStatus=" + sessionStatus
+ + ", itemId=" + itemId
+ + ", itemStatus=" + itemStatus);
+ }
+
+ if (mStatusCallback != null) {
+ mStatusCallback.onItemStatusChanged(intent.getExtras(),
+ sessionId, sessionStatus, itemId, itemStatus);
+ }
+ } else if (action.equals(ACTION_SESSION_STATUS_CHANGED)) {
+ if (sessionStatus == null) {
+ Log.w(TAG, "Discarding spurious media status callback with "
+ +"missing session status.");
+ return;
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "Received session status callback: sessionId=" + sessionId
+ + ", sessionStatus=" + sessionStatus);
+ }
+
+ if (mStatusCallback != null) {
+ mStatusCallback.onSessionStatusChanged(intent.getExtras(),
+ sessionId, sessionStatus);
+ }
+ } else if (action.equals(ACTION_MESSAGE_RECEIVED)) {
+ if (DEBUG) {
+ Log.d(TAG, "Received message callback: sessionId=" + sessionId);
+ }
+
+ if (mOnMessageReceivedListener != null) {
+ mOnMessageReceivedListener.onMessageReceived(sessionId,
+ intent.getBundleExtra(MediaControlIntent.EXTRA_MESSAGE));
+ }
+ }
+ }
+ }
+
+ /**
+ * A callback that will receive media status updates.
+ */
+ public static abstract class StatusCallback {
+ /**
+ * Called when the status of a media item changes.
+ *
+ * @param data The result data bundle.
+ * @param sessionId The session id.
+ * @param sessionStatus The session status, or null if unknown.
+ * @param itemId The item id.
+ * @param itemStatus The item status.
+ */
+ public void onItemStatusChanged(Bundle data,
+ String sessionId, MediaSessionStatus sessionStatus,
+ String itemId, MediaItemStatus itemStatus) {
+ }
+
+ /**
+ * Called when the status of a media session changes.
+ *
+ * @param data The result data bundle.
+ * @param sessionId The session id.
+ * @param sessionStatus The session status, or null if unknown.
+ */
+ public void onSessionStatusChanged(Bundle data,
+ String sessionId, MediaSessionStatus sessionStatus) {
+ }
+
+ /**
+ * Called when the session of the remote playback client changes.
+ *
+ * @param sessionId The new session id.
+ */
+ public void onSessionChanged(String sessionId) {
+ }
+ }
+
+ /**
+ * Base callback type for remote playback requests.
+ */
+ public static abstract class ActionCallback {
+ /**
+ * Called when a media control request fails.
+ *
+ * @param error A localized error message which may be shown to the user, or null
+ * if the cause of the error is unclear.
+ * @param code The error code, or {@link MediaControlIntent#ERROR_UNKNOWN} if unknown.
+ * @param data The error data bundle, or null if none.
+ */
+ public void onError(String error, int code, Bundle data) {
+ }
+ }
+
+ /**
+ * Callback for remote playback requests that operate on items.
+ */
+ public static abstract class ItemActionCallback extends ActionCallback {
+ /**
+ * Called when the request succeeds.
+ *
+ * @param data The result data bundle.
+ * @param sessionId The session id.
+ * @param sessionStatus The session status, or null if unknown.
+ * @param itemId The item id.
+ * @param itemStatus The item status.
+ */
+ public void onResult(Bundle data, String sessionId, MediaSessionStatus sessionStatus,
+ String itemId, MediaItemStatus itemStatus) {
+ }
+ }
+
+ /**
+ * Callback for remote playback requests that operate on sessions.
+ */
+ public static abstract class SessionActionCallback extends ActionCallback {
+ /**
+ * Called when the request succeeds.
+ *
+ * @param data The result data bundle.
+ * @param sessionId The session id.
+ * @param sessionStatus The session status, or null if unknown.
+ */
+ public void onResult(Bundle data, String sessionId, MediaSessionStatus sessionStatus) {
+ }
+ }
+
+ /**
+ * A callback that will receive messages from media sessions.
+ */
+ public interface OnMessageReceivedListener {
+ /**
+ * Called when a message received.
+ *
+ * @param sessionId The session id.
+ * @param message A bundle message denoting {@link MediaControlIntent#EXTRA_MESSAGE}.
+ */
+ void onMessageReceived(String sessionId, Bundle message);
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/media/SystemMediaRouteProvider.java b/packages/MediaComponents/src/com/android/support/mediarouter/media/SystemMediaRouteProvider.java
new file mode 100644
index 0000000..33d92b4
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/media/SystemMediaRouteProvider.java
@@ -0,0 +1,883 @@
+/*
+ * 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.support.mediarouter.media;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.media.AudioManager;
+import android.os.Build;
+import android.support.annotation.RequiresApi;
+import android.view.Display;
+
+import com.android.media.update.ApiHelper;
+import com.android.media.update.R;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Provides routes for built-in system destinations such as the local display
+ * and speaker. On Jellybean and newer platform releases, queries the framework
+ * MediaRouter for framework-provided routes and registers non-framework-provided
+ * routes as user routes.
+ */
+abstract class SystemMediaRouteProvider extends MediaRouteProvider {
+ private static final String TAG = "SystemMediaRouteProvider";
+
+ public static final String PACKAGE_NAME = "android";
+ public static final String DEFAULT_ROUTE_ID = "DEFAULT_ROUTE";
+
+ protected SystemMediaRouteProvider(Context context) {
+ super(context, new ProviderMetadata(new ComponentName(PACKAGE_NAME,
+ SystemMediaRouteProvider.class.getName())));
+ }
+
+ public static SystemMediaRouteProvider obtain(Context context, SyncCallback syncCallback) {
+ if (Build.VERSION.SDK_INT >= 24) {
+ return new Api24Impl(context, syncCallback);
+ }
+ if (Build.VERSION.SDK_INT >= 18) {
+ return new JellybeanMr2Impl(context, syncCallback);
+ }
+ if (Build.VERSION.SDK_INT >= 17) {
+ return new JellybeanMr1Impl(context, syncCallback);
+ }
+ if (Build.VERSION.SDK_INT >= 16) {
+ return new JellybeanImpl(context, syncCallback);
+ }
+ return new LegacyImpl(context);
+ }
+
+ /**
+ * Called by the media router when a route is added to synchronize state with
+ * the framework media router.
+ */
+ public void onSyncRouteAdded(MediaRouter.RouteInfo route) {
+ }
+
+ /**
+ * Called by the media router when a route is removed to synchronize state with
+ * the framework media router.
+ */
+ public void onSyncRouteRemoved(MediaRouter.RouteInfo route) {
+ }
+
+ /**
+ * Called by the media router when a route is changed to synchronize state with
+ * the framework media router.
+ */
+ public void onSyncRouteChanged(MediaRouter.RouteInfo route) {
+ }
+
+ /**
+ * Called by the media router when a route is selected to synchronize state with
+ * the framework media router.
+ */
+ public void onSyncRouteSelected(MediaRouter.RouteInfo route) {
+ }
+
+ /**
+ * Callbacks into the media router to synchronize state with the framework media router.
+ */
+ public interface SyncCallback {
+ void onSystemRouteSelectedByDescriptorId(String id);
+ }
+
+ protected Object getDefaultRoute() {
+ return null;
+ }
+
+ protected Object getSystemRoute(MediaRouter.RouteInfo route) {
+ return null;
+ }
+
+ /**
+ * Legacy implementation for platform versions prior to Jellybean.
+ */
+ static class LegacyImpl extends SystemMediaRouteProvider {
+ static final int PLAYBACK_STREAM = AudioManager.STREAM_MUSIC;
+
+ private static final ArrayList<IntentFilter> CONTROL_FILTERS;
+ static {
+ IntentFilter f = new IntentFilter();
+ f.addCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO);
+ f.addCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO);
+
+ CONTROL_FILTERS = new ArrayList<IntentFilter>();
+ CONTROL_FILTERS.add(f);
+ }
+
+ final AudioManager mAudioManager;
+ private final VolumeChangeReceiver mVolumeChangeReceiver;
+ int mLastReportedVolume = -1;
+
+ public LegacyImpl(Context context) {
+ super(context);
+ mAudioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);
+ mVolumeChangeReceiver = new VolumeChangeReceiver();
+
+ context.registerReceiver(mVolumeChangeReceiver,
+ new IntentFilter(VolumeChangeReceiver.VOLUME_CHANGED_ACTION));
+ publishRoutes();
+ }
+
+ void publishRoutes() {
+ Resources r = getContext().getResources();
+ int maxVolume = mAudioManager.getStreamMaxVolume(PLAYBACK_STREAM);
+ mLastReportedVolume = mAudioManager.getStreamVolume(PLAYBACK_STREAM);
+ MediaRouteDescriptor defaultRoute = new MediaRouteDescriptor.Builder(
+ DEFAULT_ROUTE_ID, r.getString(R.string.mr_system_route_name))
+ .addControlFilters(CONTROL_FILTERS)
+ .setPlaybackStream(PLAYBACK_STREAM)
+ .setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_LOCAL)
+ .setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE)
+ .setVolumeMax(maxVolume)
+ .setVolume(mLastReportedVolume)
+ .build();
+
+ MediaRouteProviderDescriptor providerDescriptor =
+ new MediaRouteProviderDescriptor.Builder()
+ .addRoute(defaultRoute)
+ .build();
+ setDescriptor(providerDescriptor);
+ }
+
+ @Override
+ public RouteController onCreateRouteController(String routeId) {
+ if (routeId.equals(DEFAULT_ROUTE_ID)) {
+ return new DefaultRouteController();
+ }
+ return null;
+ }
+
+ final class DefaultRouteController extends RouteController {
+ @Override
+ public void onSetVolume(int volume) {
+ mAudioManager.setStreamVolume(PLAYBACK_STREAM, volume, 0);
+ publishRoutes();
+ }
+
+ @Override
+ public void onUpdateVolume(int delta) {
+ int volume = mAudioManager.getStreamVolume(PLAYBACK_STREAM);
+ int maxVolume = mAudioManager.getStreamMaxVolume(PLAYBACK_STREAM);
+ int newVolume = Math.min(maxVolume, Math.max(0, volume + delta));
+ if (newVolume != volume) {
+ mAudioManager.setStreamVolume(PLAYBACK_STREAM, volume, 0);
+ }
+ publishRoutes();
+ }
+ }
+
+ final class VolumeChangeReceiver extends BroadcastReceiver {
+ // These constants come from AudioManager.
+ public static final String VOLUME_CHANGED_ACTION =
+ "android.media.VOLUME_CHANGED_ACTION";
+ public static final String EXTRA_VOLUME_STREAM_TYPE =
+ "android.media.EXTRA_VOLUME_STREAM_TYPE";
+ public static final String EXTRA_VOLUME_STREAM_VALUE =
+ "android.media.EXTRA_VOLUME_STREAM_VALUE";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(VOLUME_CHANGED_ACTION)) {
+ final int streamType = intent.getIntExtra(EXTRA_VOLUME_STREAM_TYPE, -1);
+ if (streamType == PLAYBACK_STREAM) {
+ final int volume = intent.getIntExtra(EXTRA_VOLUME_STREAM_VALUE, -1);
+ if (volume >= 0 && volume != mLastReportedVolume) {
+ publishRoutes();
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Jellybean implementation.
+ */
+ // @@RequiresApi(16)
+ static class JellybeanImpl extends SystemMediaRouteProvider
+ implements MediaRouterJellybean.Callback, MediaRouterJellybean.VolumeCallback {
+ private static final ArrayList<IntentFilter> LIVE_AUDIO_CONTROL_FILTERS;
+ static {
+ IntentFilter f = new IntentFilter();
+ f.addCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO);
+
+ LIVE_AUDIO_CONTROL_FILTERS = new ArrayList<IntentFilter>();
+ LIVE_AUDIO_CONTROL_FILTERS.add(f);
+ }
+
+ private static final ArrayList<IntentFilter> LIVE_VIDEO_CONTROL_FILTERS;
+ static {
+ IntentFilter f = new IntentFilter();
+ f.addCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO);
+
+ LIVE_VIDEO_CONTROL_FILTERS = new ArrayList<IntentFilter>();
+ LIVE_VIDEO_CONTROL_FILTERS.add(f);
+ }
+
+ private final SyncCallback mSyncCallback;
+
+ protected final Object mRouterObj;
+ protected final Object mCallbackObj;
+ protected final Object mVolumeCallbackObj;
+ protected final Object mUserRouteCategoryObj;
+ protected int mRouteTypes;
+ protected boolean mActiveScan;
+ protected boolean mCallbackRegistered;
+
+ // Maintains an association from framework routes to support library routes.
+ // Note that we cannot use the tag field for this because an application may
+ // have published its own user routes to the framework media router and already
+ // used the tag for its own purposes.
+ protected final ArrayList<SystemRouteRecord> mSystemRouteRecords =
+ new ArrayList<SystemRouteRecord>();
+
+ // Maintains an association from support library routes to framework routes.
+ protected final ArrayList<UserRouteRecord> mUserRouteRecords =
+ new ArrayList<UserRouteRecord>();
+
+ private MediaRouterJellybean.SelectRouteWorkaround mSelectRouteWorkaround;
+ private MediaRouterJellybean.GetDefaultRouteWorkaround mGetDefaultRouteWorkaround;
+
+ public JellybeanImpl(Context context, SyncCallback syncCallback) {
+ super(context);
+ mSyncCallback = syncCallback;
+ mRouterObj = MediaRouterJellybean.getMediaRouter(context);
+ mCallbackObj = createCallbackObj();
+ mVolumeCallbackObj = createVolumeCallbackObj();
+
+ Resources r = ApiHelper.getLibResources();
+ mUserRouteCategoryObj = MediaRouterJellybean.createRouteCategory(
+ mRouterObj, r.getString(R.string.mr_user_route_category_name), false);
+
+ updateSystemRoutes();
+ }
+
+ @Override
+ public RouteController onCreateRouteController(String routeId) {
+ int index = findSystemRouteRecordByDescriptorId(routeId);
+ if (index >= 0) {
+ SystemRouteRecord record = mSystemRouteRecords.get(index);
+ return new SystemRouteController(record.mRouteObj);
+ }
+ return null;
+ }
+
+ @Override
+ public void onDiscoveryRequestChanged(MediaRouteDiscoveryRequest request) {
+ int newRouteTypes = 0;
+ boolean newActiveScan = false;
+ if (request != null) {
+ final MediaRouteSelector selector = request.getSelector();
+ final List<String> categories = selector.getControlCategories();
+ final int count = categories.size();
+ for (int i = 0; i < count; i++) {
+ String category = categories.get(i);
+ if (category.equals(MediaControlIntent.CATEGORY_LIVE_AUDIO)) {
+ newRouteTypes |= MediaRouterJellybean.ROUTE_TYPE_LIVE_AUDIO;
+ } else if (category.equals(MediaControlIntent.CATEGORY_LIVE_VIDEO)) {
+ newRouteTypes |= MediaRouterJellybean.ROUTE_TYPE_LIVE_VIDEO;
+ } else {
+ newRouteTypes |= MediaRouterJellybean.ROUTE_TYPE_USER;
+ }
+ }
+ newActiveScan = request.isActiveScan();
+ }
+
+ if (mRouteTypes != newRouteTypes || mActiveScan != newActiveScan) {
+ mRouteTypes = newRouteTypes;
+ mActiveScan = newActiveScan;
+ updateSystemRoutes();
+ }
+ }
+
+ @Override
+ public void onRouteAdded(Object routeObj) {
+ if (addSystemRouteNoPublish(routeObj)) {
+ publishRoutes();
+ }
+ }
+
+ private void updateSystemRoutes() {
+ updateCallback();
+ boolean changed = false;
+ for (Object routeObj : MediaRouterJellybean.getRoutes(mRouterObj)) {
+ changed |= addSystemRouteNoPublish(routeObj);
+ }
+ if (changed) {
+ publishRoutes();
+ }
+ }
+
+ private boolean addSystemRouteNoPublish(Object routeObj) {
+ if (getUserRouteRecord(routeObj) == null
+ && findSystemRouteRecord(routeObj) < 0) {
+ String id = assignRouteId(routeObj);
+ SystemRouteRecord record = new SystemRouteRecord(routeObj, id);
+ updateSystemRouteDescriptor(record);
+ mSystemRouteRecords.add(record);
+ return true;
+ }
+ return false;
+ }
+
+ private String assignRouteId(Object routeObj) {
+ // TODO: The framework media router should supply a unique route id that
+ // we can use here. For now we use a hash of the route name and take care
+ // to dedupe it.
+ boolean isDefault = (getDefaultRoute() == routeObj);
+ String id = isDefault ? DEFAULT_ROUTE_ID :
+ String.format(Locale.US, "ROUTE_%08x", getRouteName(routeObj).hashCode());
+ if (findSystemRouteRecordByDescriptorId(id) < 0) {
+ return id;
+ }
+ for (int i = 2; ; i++) {
+ String newId = String.format(Locale.US, "%s_%d", id, i);
+ if (findSystemRouteRecordByDescriptorId(newId) < 0) {
+ return newId;
+ }
+ }
+ }
+
+ @Override
+ public void onRouteRemoved(Object routeObj) {
+ if (getUserRouteRecord(routeObj) == null) {
+ int index = findSystemRouteRecord(routeObj);
+ if (index >= 0) {
+ mSystemRouteRecords.remove(index);
+ publishRoutes();
+ }
+ }
+ }
+
+ @Override
+ public void onRouteChanged(Object routeObj) {
+ if (getUserRouteRecord(routeObj) == null) {
+ int index = findSystemRouteRecord(routeObj);
+ if (index >= 0) {
+ SystemRouteRecord record = mSystemRouteRecords.get(index);
+ updateSystemRouteDescriptor(record);
+ publishRoutes();
+ }
+ }
+ }
+
+ @Override
+ public void onRouteVolumeChanged(Object routeObj) {
+ if (getUserRouteRecord(routeObj) == null) {
+ int index = findSystemRouteRecord(routeObj);
+ if (index >= 0) {
+ SystemRouteRecord record = mSystemRouteRecords.get(index);
+ int newVolume = MediaRouterJellybean.RouteInfo.getVolume(routeObj);
+ if (newVolume != record.mRouteDescriptor.getVolume()) {
+ record.mRouteDescriptor =
+ new MediaRouteDescriptor.Builder(record.mRouteDescriptor)
+ .setVolume(newVolume)
+ .build();
+ publishRoutes();
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onRouteSelected(int type, Object routeObj) {
+ if (routeObj != MediaRouterJellybean.getSelectedRoute(mRouterObj,
+ MediaRouterJellybean.ALL_ROUTE_TYPES)) {
+ // The currently selected route has already changed so this callback
+ // is stale. Drop it to prevent getting into sync loops.
+ return;
+ }
+
+ UserRouteRecord userRouteRecord = getUserRouteRecord(routeObj);
+ if (userRouteRecord != null) {
+ userRouteRecord.mRoute.select();
+ } else {
+ // Select the route if it already exists in the compat media router.
+ // If not, we will select it instead when the route is added.
+ int index = findSystemRouteRecord(routeObj);
+ if (index >= 0) {
+ SystemRouteRecord record = mSystemRouteRecords.get(index);
+ mSyncCallback.onSystemRouteSelectedByDescriptorId(record.mRouteDescriptorId);
+ }
+ }
+ }
+
+ @Override
+ public void onRouteUnselected(int type, Object routeObj) {
+ // Nothing to do when a route is unselected.
+ // We only need to handle when a route is selected.
+ }
+
+ @Override
+ public void onRouteGrouped(Object routeObj, Object groupObj, int index) {
+ // Route grouping is deprecated and no longer supported.
+ }
+
+ @Override
+ public void onRouteUngrouped(Object routeObj, Object groupObj) {
+ // Route grouping is deprecated and no longer supported.
+ }
+
+ @Override
+ public void onVolumeSetRequest(Object routeObj, int volume) {
+ UserRouteRecord record = getUserRouteRecord(routeObj);
+ if (record != null) {
+ record.mRoute.requestSetVolume(volume);
+ }
+ }
+
+ @Override
+ public void onVolumeUpdateRequest(Object routeObj, int direction) {
+ UserRouteRecord record = getUserRouteRecord(routeObj);
+ if (record != null) {
+ record.mRoute.requestUpdateVolume(direction);
+ }
+ }
+
+ @Override
+ public void onSyncRouteAdded(MediaRouter.RouteInfo route) {
+ if (route.getProviderInstance() != this) {
+ Object routeObj = MediaRouterJellybean.createUserRoute(
+ mRouterObj, mUserRouteCategoryObj);
+ UserRouteRecord record = new UserRouteRecord(route, routeObj);
+ MediaRouterJellybean.RouteInfo.setTag(routeObj, record);
+ MediaRouterJellybean.UserRouteInfo.setVolumeCallback(routeObj, mVolumeCallbackObj);
+ updateUserRouteProperties(record);
+ mUserRouteRecords.add(record);
+ MediaRouterJellybean.addUserRoute(mRouterObj, routeObj);
+ } else {
+ // If the newly added route is the counterpart of the currently selected
+ // route in the framework media router then ensure it is selected in
+ // the compat media router.
+ Object routeObj = MediaRouterJellybean.getSelectedRoute(
+ mRouterObj, MediaRouterJellybean.ALL_ROUTE_TYPES);
+ int index = findSystemRouteRecord(routeObj);
+ if (index >= 0) {
+ SystemRouteRecord record = mSystemRouteRecords.get(index);
+ if (record.mRouteDescriptorId.equals(route.getDescriptorId())) {
+ route.select();
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onSyncRouteRemoved(MediaRouter.RouteInfo route) {
+ if (route.getProviderInstance() != this) {
+ int index = findUserRouteRecord(route);
+ if (index >= 0) {
+ UserRouteRecord record = mUserRouteRecords.remove(index);
+ MediaRouterJellybean.RouteInfo.setTag(record.mRouteObj, null);
+ MediaRouterJellybean.UserRouteInfo.setVolumeCallback(record.mRouteObj, null);
+ MediaRouterJellybean.removeUserRoute(mRouterObj, record.mRouteObj);
+ }
+ }
+ }
+
+ @Override
+ public void onSyncRouteChanged(MediaRouter.RouteInfo route) {
+ if (route.getProviderInstance() != this) {
+ int index = findUserRouteRecord(route);
+ if (index >= 0) {
+ UserRouteRecord record = mUserRouteRecords.get(index);
+ updateUserRouteProperties(record);
+ }
+ }
+ }
+
+ @Override
+ public void onSyncRouteSelected(MediaRouter.RouteInfo route) {
+ if (!route.isSelected()) {
+ // The currently selected route has already changed so this callback
+ // is stale. Drop it to prevent getting into sync loops.
+ return;
+ }
+
+ if (route.getProviderInstance() != this) {
+ int index = findUserRouteRecord(route);
+ if (index >= 0) {
+ UserRouteRecord record = mUserRouteRecords.get(index);
+ selectRoute(record.mRouteObj);
+ }
+ } else {
+ int index = findSystemRouteRecordByDescriptorId(route.getDescriptorId());
+ if (index >= 0) {
+ SystemRouteRecord record = mSystemRouteRecords.get(index);
+ selectRoute(record.mRouteObj);
+ }
+ }
+ }
+
+ protected void publishRoutes() {
+ MediaRouteProviderDescriptor.Builder builder =
+ new MediaRouteProviderDescriptor.Builder();
+ int count = mSystemRouteRecords.size();
+ for (int i = 0; i < count; i++) {
+ builder.addRoute(mSystemRouteRecords.get(i).mRouteDescriptor);
+ }
+
+ setDescriptor(builder.build());
+ }
+
+ protected int findSystemRouteRecord(Object routeObj) {
+ final int count = mSystemRouteRecords.size();
+ for (int i = 0; i < count; i++) {
+ if (mSystemRouteRecords.get(i).mRouteObj == routeObj) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ protected int findSystemRouteRecordByDescriptorId(String id) {
+ final int count = mSystemRouteRecords.size();
+ for (int i = 0; i < count; i++) {
+ if (mSystemRouteRecords.get(i).mRouteDescriptorId.equals(id)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ protected int findUserRouteRecord(MediaRouter.RouteInfo route) {
+ final int count = mUserRouteRecords.size();
+ for (int i = 0; i < count; i++) {
+ if (mUserRouteRecords.get(i).mRoute == route) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ protected UserRouteRecord getUserRouteRecord(Object routeObj) {
+ Object tag = MediaRouterJellybean.RouteInfo.getTag(routeObj);
+ return tag instanceof UserRouteRecord ? (UserRouteRecord)tag : null;
+ }
+
+ protected void updateSystemRouteDescriptor(SystemRouteRecord record) {
+ // We must always recreate the route descriptor when making any changes
+ // because they are intended to be immutable once published.
+ MediaRouteDescriptor.Builder builder = new MediaRouteDescriptor.Builder(
+ record.mRouteDescriptorId, getRouteName(record.mRouteObj));
+ onBuildSystemRouteDescriptor(record, builder);
+ record.mRouteDescriptor = builder.build();
+ }
+
+ protected String getRouteName(Object routeObj) {
+ // Routes should not have null names but it may happen for badly configured
+ // user routes. We tolerate this by using an empty name string here but
+ // such unnamed routes will be discarded by the media router upstream
+ // (with a log message so we can track down the problem).
+ CharSequence name = MediaRouterJellybean.RouteInfo.getName(routeObj, getContext());
+ return name != null ? name.toString() : "";
+ }
+
+ protected void onBuildSystemRouteDescriptor(SystemRouteRecord record,
+ MediaRouteDescriptor.Builder builder) {
+ int supportedTypes = MediaRouterJellybean.RouteInfo.getSupportedTypes(
+ record.mRouteObj);
+ if ((supportedTypes & MediaRouterJellybean.ROUTE_TYPE_LIVE_AUDIO) != 0) {
+ builder.addControlFilters(LIVE_AUDIO_CONTROL_FILTERS);
+ }
+ if ((supportedTypes & MediaRouterJellybean.ROUTE_TYPE_LIVE_VIDEO) != 0) {
+ builder.addControlFilters(LIVE_VIDEO_CONTROL_FILTERS);
+ }
+
+ builder.setPlaybackType(
+ MediaRouterJellybean.RouteInfo.getPlaybackType(record.mRouteObj));
+ builder.setPlaybackStream(
+ MediaRouterJellybean.RouteInfo.getPlaybackStream(record.mRouteObj));
+ builder.setVolume(
+ MediaRouterJellybean.RouteInfo.getVolume(record.mRouteObj));
+ builder.setVolumeMax(
+ MediaRouterJellybean.RouteInfo.getVolumeMax(record.mRouteObj));
+ builder.setVolumeHandling(
+ MediaRouterJellybean.RouteInfo.getVolumeHandling(record.mRouteObj));
+ }
+
+ protected void updateUserRouteProperties(UserRouteRecord record) {
+ MediaRouterJellybean.UserRouteInfo.setName(
+ record.mRouteObj, record.mRoute.getName());
+ MediaRouterJellybean.UserRouteInfo.setPlaybackType(
+ record.mRouteObj, record.mRoute.getPlaybackType());
+ MediaRouterJellybean.UserRouteInfo.setPlaybackStream(
+ record.mRouteObj, record.mRoute.getPlaybackStream());
+ MediaRouterJellybean.UserRouteInfo.setVolume(
+ record.mRouteObj, record.mRoute.getVolume());
+ MediaRouterJellybean.UserRouteInfo.setVolumeMax(
+ record.mRouteObj, record.mRoute.getVolumeMax());
+ MediaRouterJellybean.UserRouteInfo.setVolumeHandling(
+ record.mRouteObj, record.mRoute.getVolumeHandling());
+ }
+
+ protected void updateCallback() {
+ if (mCallbackRegistered) {
+ mCallbackRegistered = false;
+ MediaRouterJellybean.removeCallback(mRouterObj, mCallbackObj);
+ }
+
+ if (mRouteTypes != 0) {
+ mCallbackRegistered = true;
+ MediaRouterJellybean.addCallback(mRouterObj, mRouteTypes, mCallbackObj);
+ }
+ }
+
+ protected Object createCallbackObj() {
+ return MediaRouterJellybean.createCallback(this);
+ }
+
+ protected Object createVolumeCallbackObj() {
+ return MediaRouterJellybean.createVolumeCallback(this);
+ }
+
+ protected void selectRoute(Object routeObj) {
+ if (mSelectRouteWorkaround == null) {
+ mSelectRouteWorkaround = new MediaRouterJellybean.SelectRouteWorkaround();
+ }
+ mSelectRouteWorkaround.selectRoute(mRouterObj,
+ MediaRouterJellybean.ALL_ROUTE_TYPES, routeObj);
+ }
+
+ @Override
+ protected Object getDefaultRoute() {
+ if (mGetDefaultRouteWorkaround == null) {
+ mGetDefaultRouteWorkaround = new MediaRouterJellybean.GetDefaultRouteWorkaround();
+ }
+ return mGetDefaultRouteWorkaround.getDefaultRoute(mRouterObj);
+ }
+
+ @Override
+ protected Object getSystemRoute(MediaRouter.RouteInfo route) {
+ if (route == null) {
+ return null;
+ }
+ int index = findSystemRouteRecordByDescriptorId(route.getDescriptorId());
+ if (index >= 0) {
+ return mSystemRouteRecords.get(index).mRouteObj;
+ }
+ return null;
+ }
+
+ /**
+ * Represents a route that is provided by the framework media router
+ * and published by this route provider to the support library media router.
+ */
+ protected static final class SystemRouteRecord {
+ public final Object mRouteObj;
+ public final String mRouteDescriptorId;
+ public MediaRouteDescriptor mRouteDescriptor; // assigned immediately after creation
+
+ public SystemRouteRecord(Object routeObj, String id) {
+ mRouteObj = routeObj;
+ mRouteDescriptorId = id;
+ }
+ }
+
+ /**
+ * Represents a route that is provided by the support library media router
+ * and published by this route provider to the framework media router.
+ */
+ protected static final class UserRouteRecord {
+ public final MediaRouter.RouteInfo mRoute;
+ public final Object mRouteObj;
+
+ public UserRouteRecord(MediaRouter.RouteInfo route, Object routeObj) {
+ mRoute = route;
+ mRouteObj = routeObj;
+ }
+ }
+
+ protected static final class SystemRouteController extends RouteController {
+ private final Object mRouteObj;
+
+ public SystemRouteController(Object routeObj) {
+ mRouteObj = routeObj;
+ }
+
+ @Override
+ public void onSetVolume(int volume) {
+ MediaRouterJellybean.RouteInfo.requestSetVolume(mRouteObj, volume);
+ }
+
+ @Override
+ public void onUpdateVolume(int delta) {
+ MediaRouterJellybean.RouteInfo.requestUpdateVolume(mRouteObj, delta);
+ }
+ }
+ }
+
+ /**
+ * Jellybean MR1 implementation.
+ */
+ // @@RequiresApi(17)
+ private static class JellybeanMr1Impl extends JellybeanImpl
+ implements MediaRouterJellybeanMr1.Callback {
+ private MediaRouterJellybeanMr1.ActiveScanWorkaround mActiveScanWorkaround;
+ private MediaRouterJellybeanMr1.IsConnectingWorkaround mIsConnectingWorkaround;
+
+ public JellybeanMr1Impl(Context context, SyncCallback syncCallback) {
+ super(context, syncCallback);
+ }
+
+ @Override
+ public void onRoutePresentationDisplayChanged(Object routeObj) {
+ int index = findSystemRouteRecord(routeObj);
+ if (index >= 0) {
+ SystemRouteRecord record = mSystemRouteRecords.get(index);
+ Display newPresentationDisplay =
+ MediaRouterJellybeanMr1.RouteInfo.getPresentationDisplay(routeObj);
+ int newPresentationDisplayId = (newPresentationDisplay != null
+ ? newPresentationDisplay.getDisplayId() : -1);
+ if (newPresentationDisplayId
+ != record.mRouteDescriptor.getPresentationDisplayId()) {
+ record.mRouteDescriptor =
+ new MediaRouteDescriptor.Builder(record.mRouteDescriptor)
+ .setPresentationDisplayId(newPresentationDisplayId)
+ .build();
+ publishRoutes();
+ }
+ }
+ }
+
+ @Override
+ protected void onBuildSystemRouteDescriptor(SystemRouteRecord record,
+ MediaRouteDescriptor.Builder builder) {
+ super.onBuildSystemRouteDescriptor(record, builder);
+
+ if (!MediaRouterJellybeanMr1.RouteInfo.isEnabled(record.mRouteObj)) {
+ builder.setEnabled(false);
+ }
+
+ if (isConnecting(record)) {
+ builder.setConnecting(true);
+ }
+
+ Display presentationDisplay =
+ MediaRouterJellybeanMr1.RouteInfo.getPresentationDisplay(record.mRouteObj);
+ if (presentationDisplay != null) {
+ builder.setPresentationDisplayId(presentationDisplay.getDisplayId());
+ }
+ }
+
+ @Override
+ protected void updateCallback() {
+ super.updateCallback();
+
+ if (mActiveScanWorkaround == null) {
+ mActiveScanWorkaround = new MediaRouterJellybeanMr1.ActiveScanWorkaround(
+ getContext(), getHandler());
+ }
+ mActiveScanWorkaround.setActiveScanRouteTypes(mActiveScan ? mRouteTypes : 0);
+ }
+
+ @Override
+ protected Object createCallbackObj() {
+ return MediaRouterJellybeanMr1.createCallback(this);
+ }
+
+ protected boolean isConnecting(SystemRouteRecord record) {
+ if (mIsConnectingWorkaround == null) {
+ mIsConnectingWorkaround = new MediaRouterJellybeanMr1.IsConnectingWorkaround();
+ }
+ return mIsConnectingWorkaround.isConnecting(record.mRouteObj);
+ }
+ }
+
+ /**
+ * Jellybean MR2 implementation.
+ */
+ // @@RequiresApi(18)
+ private static class JellybeanMr2Impl extends JellybeanMr1Impl {
+ public JellybeanMr2Impl(Context context, SyncCallback syncCallback) {
+ super(context, syncCallback);
+ }
+
+ @Override
+ protected void onBuildSystemRouteDescriptor(SystemRouteRecord record,
+ MediaRouteDescriptor.Builder builder) {
+ super.onBuildSystemRouteDescriptor(record, builder);
+
+ CharSequence description =
+ MediaRouterJellybeanMr2.RouteInfo.getDescription(record.mRouteObj);
+ if (description != null) {
+ builder.setDescription(description.toString());
+ }
+ }
+
+ @Override
+ protected void selectRoute(Object routeObj) {
+ MediaRouterJellybean.selectRoute(mRouterObj,
+ MediaRouterJellybean.ALL_ROUTE_TYPES, routeObj);
+ }
+
+ @Override
+ protected Object getDefaultRoute() {
+ return MediaRouterJellybeanMr2.getDefaultRoute(mRouterObj);
+ }
+
+ @Override
+ protected void updateUserRouteProperties(UserRouteRecord record) {
+ super.updateUserRouteProperties(record);
+
+ MediaRouterJellybeanMr2.UserRouteInfo.setDescription(
+ record.mRouteObj, record.mRoute.getDescription());
+ }
+
+ @Override
+ protected void updateCallback() {
+ if (mCallbackRegistered) {
+ MediaRouterJellybean.removeCallback(mRouterObj, mCallbackObj);
+ }
+
+ mCallbackRegistered = true;
+ MediaRouterJellybeanMr2.addCallback(mRouterObj, mRouteTypes, mCallbackObj,
+ MediaRouter.CALLBACK_FLAG_UNFILTERED_EVENTS
+ | (mActiveScan ? MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN : 0));
+ }
+
+ @Override
+ protected boolean isConnecting(SystemRouteRecord record) {
+ return MediaRouterJellybeanMr2.RouteInfo.isConnecting(record.mRouteObj);
+ }
+ }
+
+ /**
+ * Api24 implementation.
+ */
+ // @@RequiresApi(24)
+ private static class Api24Impl extends JellybeanMr2Impl {
+ public Api24Impl(Context context, SyncCallback syncCallback) {
+ super(context, syncCallback);
+ }
+
+ @Override
+ protected void onBuildSystemRouteDescriptor(SystemRouteRecord record,
+ MediaRouteDescriptor.Builder builder) {
+ super.onBuildSystemRouteDescriptor(record, builder);
+
+ builder.setDeviceType(MediaRouterApi24.RouteInfo.getDeviceType(record.mRouteObj));
+ }
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/media/package.html b/packages/MediaComponents/src/com/android/support/mediarouter/media/package.html
new file mode 100644
index 0000000..be2aaf2
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/media/package.html
@@ -0,0 +1,9 @@
+<html>
+
+<body>
+
+<p>Contains APIs that control the routing of media channels and streams from the current device
+ to external speakers and destination devices.</p>
+
+</body>
+</html>
diff --git a/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java b/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
new file mode 100644
index 0000000..bc370d8
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
@@ -0,0 +1,904 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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.widget;
+
+import android.content.res.Resources;
+import android.media.MediaMetadata;
+import android.media.session.MediaController;
+import android.media.session.PlaybackState;
+import android.media.update.MediaControlView2Provider;
+import android.media.update.ViewProvider;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityManager;
+import android.widget.ImageButton;
+import android.widget.MediaControlView2;
+import android.widget.ProgressBar;
+import android.widget.SeekBar;
+import android.widget.SeekBar.OnSeekBarChangeListener;
+import android.widget.TextView;
+
+import com.android.media.update.ApiHelper;
+import com.android.media.update.R;
+
+import java.util.Formatter;
+import java.util.Locale;
+
+public class MediaControlView2Impl implements MediaControlView2Provider {
+ private static final String TAG = "MediaControlView2";
+
+ private final MediaControlView2 mInstance;
+ private final ViewProvider mSuperProvider;
+
+ static final String COMMAND_SHOW_SUBTITLE = "showSubtitle";
+ static final String COMMAND_HIDE_SUBTITLE = "hideSubtitle";
+ static final String COMMAND_SET_FULLSCREEN = "setFullscreen";
+
+ static final String ARGUMENT_KEY_FULLSCREEN = "fullScreen";
+
+ static final String KEY_STATE_CONTAINS_SUBTITLE = "StateContainsSubtitle";
+ static final String EVENT_UPDATE_SUBTITLE_STATUS = "UpdateSubtitleStatus";
+
+ private static final int MAX_PROGRESS = 1000;
+ private static final int DEFAULT_PROGRESS_UPDATE_TIME_MS = 1000;
+ private static final int DEFAULT_TIMEOUT_MS = 2000;
+
+ private static final int REWIND_TIME_MS = 10000;
+ private static final int FORWARD_TIME_MS = 30000;
+
+ private final AccessibilityManager mAccessibilityManager;
+
+ private MediaController mController;
+ private MediaController.TransportControls mControls;
+ private PlaybackState mPlaybackState;
+ private MediaMetadata mMetadata;
+ private ProgressBar mProgress;
+ private TextView mEndTime, mCurrentTime;
+ private TextView mTitleView;
+ private int mDuration;
+ private int mPrevState;
+ private long mPlaybackActions;
+ private boolean mShowing;
+ private boolean mDragging;
+ private boolean mIsFullScreen;
+ private boolean mOverflowExpanded;
+ private boolean mIsStopped;
+ private boolean mSubtitleIsEnabled;
+ private boolean mContainsSubtitle;
+ private boolean mSeekAvailable;
+ private View.OnClickListener mNextListener, mPrevListener;
+ private ImageButton mPlayPauseButton;
+ private ImageButton mFfwdButton;
+ private ImageButton mRewButton;
+ private ImageButton mNextButton;
+ private ImageButton mPrevButton;
+
+ private ViewGroup mBasicControls;
+ private ImageButton mSubtitleButton;
+ private ImageButton mFullScreenButton;
+ private ImageButton mOverflowButtonRight;
+
+ private ViewGroup mExtraControls;
+ private ImageButton mOverflowButtonLeft;
+ private ImageButton mMuteButton;
+ private ImageButton mAspectRationButton;
+ private ImageButton mSettingsButton;
+
+ private CharSequence mPlayDescription;
+ private CharSequence mPauseDescription;
+ private CharSequence mReplayDescription;
+
+ private StringBuilder mFormatBuilder;
+ private Formatter mFormatter;
+
+ public MediaControlView2Impl(
+ MediaControlView2 instance, ViewProvider superProvider) {
+ mInstance = instance;
+ mSuperProvider = superProvider;
+ mAccessibilityManager = AccessibilityManager.getInstance(mInstance.getContext());
+
+ // Inflate MediaControlView2 from XML
+ View root = makeControllerView();
+ mInstance.addView(root);
+ }
+
+ @Override
+ public void setController_impl(MediaController controller) {
+ mController = controller;
+ if (controller != null) {
+ mControls = controller.getTransportControls();
+ // Set mMetadata and mPlaybackState to existing MediaSession variables since they may
+ // be called before the callback is called
+ mPlaybackState = mController.getPlaybackState();
+ mMetadata = mController.getMetadata();
+ updateDuration();
+ updateTitle();
+
+ mController.registerCallback(new MediaControllerCallback());
+ }
+ }
+
+ @Override
+ public void show_impl() {
+ mInstance.show(DEFAULT_TIMEOUT_MS);
+ }
+
+ @Override
+ public void show_impl(int timeout) {
+ if (!mShowing) {
+ setProgress();
+ if (mPlayPauseButton != null) {
+ mPlayPauseButton.requestFocus();
+ }
+ disableUnsupportedButtons();
+ mInstance.setVisibility(View.VISIBLE);
+ mShowing = true;
+ }
+ // cause the progress bar to be updated even if mShowing
+ // was already true. This happens, for example, if we're
+ // paused with the progress bar showing the user hits play.
+ mInstance.post(mShowProgress);
+
+ if (timeout != 0 && !mAccessibilityManager.isTouchExplorationEnabled()) {
+ mInstance.removeCallbacks(mFadeOut);
+ mInstance.postDelayed(mFadeOut, timeout);
+ }
+ }
+
+ @Override
+ public boolean isShowing_impl() {
+ return mShowing;
+ }
+
+ @Override
+ public void hide_impl() {
+ if (mShowing) {
+ try {
+ mInstance.removeCallbacks(mShowProgress);
+ // Remove existing call to mFadeOut to avoid from being called later.
+ mInstance.removeCallbacks(mFadeOut);
+ mInstance.setVisibility(View.GONE);
+ } catch (IllegalArgumentException ex) {
+ Log.w(TAG, "already removed");
+ }
+ mShowing = false;
+ }
+ }
+
+ @Override
+ public void showSubtitle_impl() {
+ mController.sendCommand(COMMAND_SHOW_SUBTITLE, null, null);
+ }
+
+ @Override
+ public void hideSubtitle_impl() {
+ mController.sendCommand(COMMAND_HIDE_SUBTITLE, null, null);
+ }
+
+ @Override
+ public void setPrevNextListeners_impl(View.OnClickListener next, View.OnClickListener prev) {
+ mNextListener = next;
+ mPrevListener = prev;
+
+ if (mNextButton != null) {
+ mNextButton.setOnClickListener(mNextListener);
+ mNextButton.setEnabled(mNextListener != null);
+ mNextButton.setVisibility(View.VISIBLE);
+ }
+ if (mPrevButton != null) {
+ mPrevButton.setOnClickListener(mPrevListener);
+ mPrevButton.setEnabled(mPrevListener != null);
+ mPrevButton.setVisibility(View.VISIBLE);
+ }
+ }
+
+ @Override
+ public void setButtonVisibility_impl(int button, boolean visible) {
+ switch (button) {
+ case MediaControlView2.BUTTON_PLAY_PAUSE:
+ if (mPlayPauseButton != null && canPause()) {
+ mPlayPauseButton.setVisibility((visible) ? View.VISIBLE : View.GONE);
+ }
+ break;
+ case MediaControlView2.BUTTON_FFWD:
+ if (mFfwdButton != null && canSeekForward()) {
+ mFfwdButton.setVisibility((visible) ? View.VISIBLE : View.GONE);
+ }
+ break;
+ case MediaControlView2.BUTTON_REW:
+ if (mRewButton != null && canSeekBackward()) {
+ mRewButton.setVisibility((visible) ? View.VISIBLE : View.GONE);
+ }
+ break;
+ case MediaControlView2.BUTTON_NEXT:
+ // TODO: this button is not visible unless its listener is manually set. Should this
+ // function still be provided?
+ if (mNextButton != null) {
+ mNextButton.setVisibility((visible) ? View.VISIBLE : View.GONE);
+ }
+ break;
+ case MediaControlView2.BUTTON_PREV:
+ // TODO: this button is not visible unless its listener is manually set. Should this
+ // function still be provided?
+ if (mPrevButton != null) {
+ mPrevButton.setVisibility((visible) ? View.VISIBLE : View.GONE);
+ }
+ break;
+ case MediaControlView2.BUTTON_SUBTITLE:
+ if (mSubtitleButton != null && mContainsSubtitle) {
+ mSubtitleButton.setVisibility((visible) ? View.VISIBLE : View.GONE);
+ }
+ break;
+ case MediaControlView2.BUTTON_FULL_SCREEN:
+ if (mFullScreenButton != null) {
+ mFullScreenButton.setVisibility((visible) ? View.VISIBLE : View.GONE);
+ }
+ break;
+ case MediaControlView2.BUTTON_OVERFLOW:
+ if (mOverflowButtonRight != null) {
+ mOverflowButtonRight.setVisibility((visible) ? View.VISIBLE : View.GONE);
+ }
+ break;
+ case MediaControlView2.BUTTON_MUTE:
+ if (mMuteButton != null) {
+ mMuteButton.setVisibility((visible) ? View.VISIBLE : View.GONE);
+ }
+ break;
+ case MediaControlView2.BUTTON_ASPECT_RATIO:
+ if (mAspectRationButton != null) {
+ mAspectRationButton.setVisibility((visible) ? View.VISIBLE : View.GONE);
+ }
+ break;
+ case MediaControlView2.BUTTON_SETTINGS:
+ if (mSettingsButton != null) {
+ mSettingsButton.setVisibility((visible) ? View.VISIBLE : View.GONE);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ @Override
+ public void onAttachedToWindow_impl() {
+ mSuperProvider.onAttachedToWindow_impl();
+ }
+
+ @Override
+ public void onDetachedFromWindow_impl() {
+ mSuperProvider.onDetachedFromWindow_impl();
+ }
+
+ @Override
+ public CharSequence getAccessibilityClassName_impl() {
+ return MediaControlView2.class.getName();
+ }
+
+ @Override
+ public boolean onTouchEvent_impl(MotionEvent ev) {
+ return false;
+ }
+
+ // TODO: Should this function be removed?
+ @Override
+ public boolean onTrackballEvent_impl(MotionEvent ev) {
+ mInstance.show(DEFAULT_TIMEOUT_MS);
+ return false;
+ }
+
+ @Override
+ public boolean onKeyDown_impl(int keyCode, KeyEvent event) {
+ return mSuperProvider.onKeyDown_impl(keyCode, event);
+ }
+
+ @Override
+ public void onFinishInflate_impl() {
+ mSuperProvider.onFinishInflate_impl();
+ }
+
+ @Override
+ public boolean dispatchKeyEvent_impl(KeyEvent event) {
+ int keyCode = event.getKeyCode();
+ final boolean uniqueDown = event.getRepeatCount() == 0
+ && event.getAction() == KeyEvent.ACTION_DOWN;
+ if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK
+ || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
+ || keyCode == KeyEvent.KEYCODE_SPACE) {
+ if (uniqueDown) {
+ togglePausePlayState();
+ mInstance.show(DEFAULT_TIMEOUT_MS);
+ if (mPlayPauseButton != null) {
+ mPlayPauseButton.requestFocus();
+ }
+ }
+ return true;
+ } else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) {
+ if (uniqueDown && !isPlaying()) {
+ togglePausePlayState();
+ mInstance.show(DEFAULT_TIMEOUT_MS);
+ }
+ return true;
+ } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP
+ || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) {
+ if (uniqueDown && isPlaying()) {
+ togglePausePlayState();
+ mInstance.show(DEFAULT_TIMEOUT_MS);
+ }
+ return true;
+ } else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
+ || keyCode == KeyEvent.KEYCODE_VOLUME_UP
+ || keyCode == KeyEvent.KEYCODE_VOLUME_MUTE
+ || keyCode == KeyEvent.KEYCODE_CAMERA) {
+ // don't show the controls for volume adjustment
+ return mSuperProvider.dispatchKeyEvent_impl(event);
+ } else if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_MENU) {
+ if (uniqueDown) {
+ mInstance.hide();
+ }
+ return true;
+ }
+
+ mInstance.show(DEFAULT_TIMEOUT_MS);
+ return mSuperProvider.dispatchKeyEvent_impl(event);
+ }
+
+ @Override
+ public void setEnabled_impl(boolean enabled) {
+ if (mPlayPauseButton != null) {
+ mPlayPauseButton.setEnabled(enabled);
+ }
+ if (mFfwdButton != null) {
+ mFfwdButton.setEnabled(enabled);
+ }
+ if (mRewButton != null) {
+ mRewButton.setEnabled(enabled);
+ }
+ if (mNextButton != null) {
+ mNextButton.setEnabled(enabled);
+ }
+ if (mPrevButton != null) {
+ mPrevButton.setEnabled(enabled);
+ }
+ if (mProgress != null) {
+ mProgress.setEnabled(enabled);
+ }
+ disableUnsupportedButtons();
+ mSuperProvider.setEnabled_impl(enabled);
+ }
+
+ ///////////////////////////////////////////////////
+ // Protected or private methods
+ ///////////////////////////////////////////////////
+
+ private boolean isPlaying() {
+ if (mPlaybackState != null) {
+ return mPlaybackState.getState() == PlaybackState.STATE_PLAYING;
+ }
+ return false;
+ }
+
+ private int getCurrentPosition() {
+ mPlaybackState = mController.getPlaybackState();
+ if (mPlaybackState != null) {
+ return (int) mPlaybackState.getPosition();
+ }
+ return 0;
+ }
+
+ private int getBufferPercentage() {
+ if (mDuration == 0) {
+ return 0;
+ }
+ mPlaybackState = mController.getPlaybackState();
+ if (mPlaybackState != null) {
+ return (int) (mPlaybackState.getBufferedPosition() * 100) / mDuration;
+ }
+ return 0;
+ }
+
+ private boolean canPause() {
+ if (mPlaybackState != null) {
+ return (mPlaybackState.getActions() & PlaybackState.ACTION_PAUSE) != 0;
+ }
+ return true;
+ }
+
+ private boolean canSeekBackward() {
+ if (mPlaybackState != null) {
+ return (mPlaybackState.getActions() & PlaybackState.ACTION_REWIND) != 0;
+ }
+ return true;
+ }
+
+ private boolean canSeekForward() {
+ if (mPlaybackState != null) {
+ return (mPlaybackState.getActions() & PlaybackState.ACTION_FAST_FORWARD) != 0;
+ }
+ return true;
+ }
+
+ /**
+ * Create the view that holds the widgets that control playback.
+ * Derived classes can override this to create their own.
+ *
+ * @return The controller view.
+ * @hide This doesn't work as advertised
+ */
+ protected View makeControllerView() {
+ View root = ApiHelper.inflateLibLayout(mInstance.getContext(), R.layout.media_controller);
+ initControllerView(root);
+ return root;
+ }
+
+ private void initControllerView(View v) {
+ Resources res = ApiHelper.getLibResources();
+ mPlayDescription = res.getText(R.string.lockscreen_play_button_content_description);
+ mPauseDescription = res.getText(R.string.lockscreen_pause_button_content_description);
+ mReplayDescription = res.getText(R.string.lockscreen_replay_button_content_description);
+
+ mPlayPauseButton = v.findViewById(R.id.pause);
+ if (mPlayPauseButton != null) {
+ mPlayPauseButton.requestFocus();
+ mPlayPauseButton.setOnClickListener(mPlayPauseListener);
+ mPlayPauseButton.setColorFilter(R.integer.gray);
+ mPlayPauseButton.setEnabled(false);
+ }
+ mFfwdButton = v.findViewById(R.id.ffwd);
+ if (mFfwdButton != null) {
+ mFfwdButton.setOnClickListener(mFfwdListener);
+ mFfwdButton.setColorFilter(R.integer.gray);
+ mFfwdButton.setEnabled(false);
+ }
+ mRewButton = v.findViewById(R.id.rew);
+ if (mRewButton != null) {
+ mRewButton.setOnClickListener(mRewListener);
+ mRewButton.setColorFilter(R.integer.gray);
+ mRewButton.setEnabled(false);
+ }
+ mNextButton = v.findViewById(R.id.next);
+ if (mNextButton != null) {
+ mNextButton.setVisibility(View.GONE);
+ }
+ mPrevButton = v.findViewById(R.id.prev);
+ if (mPrevButton != null) {
+ mPrevButton.setVisibility(View.GONE);
+ }
+
+ mBasicControls = v.findViewById(R.id.basic_controls);
+ mSubtitleButton = v.findViewById(R.id.subtitle);
+ if (mSubtitleButton != null) {
+ mSubtitleButton.setOnClickListener(mSubtitleListener);
+ mSubtitleButton.setColorFilter(R.integer.gray);
+ mSubtitleButton.setEnabled(false);
+ }
+ mFullScreenButton = v.findViewById(R.id.fullscreen);
+ if (mFullScreenButton != null) {
+ mFullScreenButton.setOnClickListener(mFullScreenListener);
+ // TODO: Show Fullscreen button when only it is possible.
+ }
+ mOverflowButtonRight = v.findViewById(R.id.overflow_right);
+ if (mOverflowButtonRight != null) {
+ mOverflowButtonRight.setOnClickListener(mOverflowRightListener);
+ }
+
+ // TODO: should these buttons be shown as default?
+ mExtraControls = v.findViewById(R.id.extra_controls);
+ mOverflowButtonLeft = v.findViewById(R.id.overflow_left);
+ if (mOverflowButtonLeft != null) {
+ mOverflowButtonLeft.setOnClickListener(mOverflowLeftListener);
+ }
+ mMuteButton = v.findViewById(R.id.mute);
+ mAspectRationButton = v.findViewById(R.id.aspect_ratio);
+ mSettingsButton = v.findViewById(R.id.settings);
+
+ mProgress = v.findViewById(R.id.mediacontroller_progress);
+ if (mProgress != null) {
+ if (mProgress instanceof SeekBar) {
+ SeekBar seeker = (SeekBar) mProgress;
+ seeker.setOnSeekBarChangeListener(mSeekListener);
+ }
+ mProgress.setMax(MAX_PROGRESS);
+ }
+
+ mTitleView = v.findViewById(R.id.title_text);
+
+ mEndTime = v.findViewById(R.id.time);
+ mCurrentTime = v.findViewById(R.id.time_current);
+ mFormatBuilder = new StringBuilder();
+ mFormatter = new Formatter(mFormatBuilder, Locale.getDefault());
+ }
+
+ /**
+ * Disable pause or seek buttons if the stream cannot be paused or seeked.
+ * This requires the control interface to be a MediaPlayerControlExt
+ */
+ private void disableUnsupportedButtons() {
+ try {
+ if (mPlayPauseButton != null && !canPause()) {
+ mPlayPauseButton.setEnabled(false);
+ }
+ if (mRewButton != null && !canSeekBackward()) {
+ mRewButton.setEnabled(false);
+ }
+ if (mFfwdButton != null && !canSeekForward()) {
+ mFfwdButton.setEnabled(false);
+ }
+ // TODO What we really should do is add a canSeek to the MediaPlayerControl interface;
+ // this scheme can break the case when applications want to allow seek through the
+ // progress bar but disable forward/backward buttons.
+ //
+ // However, currently the flags SEEK_BACKWARD_AVAILABLE, SEEK_FORWARD_AVAILABLE,
+ // and SEEK_AVAILABLE are all (un)set together; as such the aforementioned issue
+ // shouldn't arise in existing applications.
+ if (mProgress != null && !canSeekBackward() && !canSeekForward()) {
+ mProgress.setEnabled(false);
+ }
+ } catch (IncompatibleClassChangeError ex) {
+ // We were given an old version of the interface, that doesn't have
+ // the canPause/canSeekXYZ methods. This is OK, it just means we
+ // assume the media can be paused and seeked, and so we don't disable
+ // the buttons.
+ }
+ }
+
+ private final Runnable mFadeOut = new Runnable() {
+ @Override
+ public void run() {
+ if (isPlaying()) {
+ mInstance.hide();
+ }
+ }
+ };
+
+ private final Runnable mShowProgress = new Runnable() {
+ @Override
+ public void run() {
+ int pos = setProgress();
+ if (!mDragging && mShowing && isPlaying()) {
+ mInstance.postDelayed(mShowProgress,
+ DEFAULT_PROGRESS_UPDATE_TIME_MS - (pos % DEFAULT_PROGRESS_UPDATE_TIME_MS));
+ }
+ }
+ };
+
+ private String stringForTime(int timeMs) {
+ int totalSeconds = timeMs / 1000;
+
+ int seconds = totalSeconds % 60;
+ int minutes = (totalSeconds / 60) % 60;
+ int hours = totalSeconds / 3600;
+
+ mFormatBuilder.setLength(0);
+ if (hours > 0) {
+ return mFormatter.format("%d:%02d:%02d", hours, minutes, seconds).toString();
+ } else {
+ return mFormatter.format("%02d:%02d", minutes, seconds).toString();
+ }
+ }
+
+ private int setProgress() {
+ if (mController == null || mDragging) {
+ return 0;
+ }
+ int positionOnProgressBar = 0;
+ int currentPosition = getCurrentPosition();
+ if (mDuration > 0) {
+ positionOnProgressBar = (int) (MAX_PROGRESS * (long) currentPosition / mDuration);
+ }
+ if (mProgress != null && currentPosition != mDuration) {
+ mProgress.setProgress(positionOnProgressBar);
+ mProgress.setSecondaryProgress(getBufferPercentage() * 10);
+ }
+
+ if (mEndTime != null) {
+ mEndTime.setText(stringForTime(mDuration));
+
+ }
+ if (mCurrentTime != null) {
+ mCurrentTime.setText(stringForTime(currentPosition));
+ }
+
+ return currentPosition;
+ }
+
+ private void togglePausePlayState() {
+ if (isPlaying()) {
+ mControls.pause();
+ mPlayPauseButton.setImageDrawable(
+ ApiHelper.getLibResources().getDrawable(
+ R.drawable.ic_play_circle_filled, null));
+ mPlayPauseButton.setContentDescription(mPlayDescription);
+ } else {
+ mControls.play();
+ mPlayPauseButton.setImageDrawable(
+ ApiHelper.getLibResources().getDrawable(
+ R.drawable.ic_pause_circle_filled, null));
+ mPlayPauseButton.setContentDescription(mPauseDescription);
+ }
+ }
+
+ // There are two scenarios that can trigger the seekbar listener to trigger:
+ //
+ // The first is the user using the touchpad to adjust the posititon of the
+ // seekbar's thumb. In this case onStartTrackingTouch is called followed by
+ // a number of onProgressChanged notifications, concluded by onStopTrackingTouch.
+ // We're setting the field "mDragging" to true for the duration of the dragging
+ // session to avoid jumps in the position in case of ongoing playback.
+ //
+ // The second scenario involves the user operating the scroll ball, in this
+ // case there WON'T BE onStartTrackingTouch/onStopTrackingTouch notifications,
+ // we will simply apply the updated position without suspending regular updates.
+ private final OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() {
+ @Override
+ public void onStartTrackingTouch(SeekBar bar) {
+ if (!mSeekAvailable) {
+ return;
+ }
+ mInstance.show(3600000);
+
+ mDragging = true;
+
+ // By removing these pending progress messages we make sure
+ // that a) we won't update the progress while the user adjusts
+ // the seekbar and b) once the user is done dragging the thumb
+ // we will post one of these messages to the queue again and
+ // this ensures that there will be exactly one message queued up.
+ mInstance.removeCallbacks(mShowProgress);
+
+ // Check if playback is currently stopped. In this case, update the pause button to
+ // show the play image instead of the replay image.
+ if (mIsStopped) {
+ mPlayPauseButton.setImageDrawable(
+ ApiHelper.getLibResources().getDrawable(
+ R.drawable.ic_play_circle_filled, null));
+ mPlayPauseButton.setContentDescription(mPlayDescription);
+ mIsStopped = false;
+ }
+ }
+
+ @Override
+ public void onProgressChanged(SeekBar bar, int progress, boolean fromUser) {
+ if (!mSeekAvailable) {
+ return;
+ }
+ if (!fromUser) {
+ // We're not interested in programmatically generated changes to
+ // the progress bar's position.
+ return;
+ }
+ if (mDuration > 0) {
+ int newPosition = (int) (((long) mDuration * progress) / MAX_PROGRESS);
+ mControls.seekTo(newPosition);
+
+ if (mCurrentTime != null) {
+ mCurrentTime.setText(stringForTime(newPosition));
+ }
+ }
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar bar) {
+ if (!mSeekAvailable) {
+ return;
+ }
+ mDragging = false;
+
+ setProgress();
+ mInstance.show(DEFAULT_TIMEOUT_MS);
+
+ // Ensure that progress is properly updated in the future,
+ // the call to show() does not guarantee this because it is a
+ // no-op if we are already showing.
+ mInstance.post(mShowProgress);
+ }
+ };
+
+ private final View.OnClickListener mPlayPauseListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ togglePausePlayState();
+ mInstance.show(DEFAULT_TIMEOUT_MS);
+ }
+ };
+
+ private final View.OnClickListener mRewListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ int pos = getCurrentPosition() - REWIND_TIME_MS;
+ mControls.seekTo(pos);
+ setProgress();
+
+ mInstance.show(DEFAULT_TIMEOUT_MS);
+ }
+ };
+
+ private final View.OnClickListener mFfwdListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ int pos = getCurrentPosition() + FORWARD_TIME_MS;
+ mControls.seekTo(pos);
+ setProgress();
+
+ mInstance.show(DEFAULT_TIMEOUT_MS);
+ }
+ };
+
+ private final View.OnClickListener mSubtitleListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (!mSubtitleIsEnabled) {
+ mSubtitleButton.setImageDrawable(
+ ApiHelper.getLibResources().getDrawable(
+ R.drawable.ic_media_subtitle_enabled, null));
+ mInstance.showSubtitle();
+ mSubtitleIsEnabled = true;
+ } else {
+ mSubtitleButton.setImageDrawable(
+ ApiHelper.getLibResources().getDrawable(
+ R.drawable.ic_media_subtitle_disabled, null));
+ mInstance.hideSubtitle();
+ mSubtitleIsEnabled = false;
+ }
+ mInstance.show(DEFAULT_TIMEOUT_MS);
+ }
+ };
+
+ private final View.OnClickListener mFullScreenListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ final boolean isEnteringFullScreen = !mIsFullScreen;
+ // TODO: Re-arrange the button layouts according to the UX.
+ if (isEnteringFullScreen) {
+ mFullScreenButton.setImageDrawable(
+ ApiHelper.getLibResources().getDrawable(
+ R.drawable.ic_fullscreen_exit, null));
+ } else {
+ mFullScreenButton.setImageDrawable(
+ ApiHelper.getLibResources().getDrawable(R.drawable.ic_fullscreen, null));
+ }
+ Bundle args = new Bundle();
+ args.putBoolean(ARGUMENT_KEY_FULLSCREEN, isEnteringFullScreen);
+ mController.sendCommand(COMMAND_SET_FULLSCREEN, args, null);
+
+ mIsFullScreen = isEnteringFullScreen;
+ mInstance.show(DEFAULT_TIMEOUT_MS);
+ }
+ };
+
+ private final View.OnClickListener mOverflowRightListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mBasicControls.setVisibility(View.GONE);
+ mExtraControls.setVisibility(View.VISIBLE);
+ mInstance.show(DEFAULT_TIMEOUT_MS);
+ }
+ };
+
+ private final View.OnClickListener mOverflowLeftListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mBasicControls.setVisibility(View.VISIBLE);
+ mExtraControls.setVisibility(View.GONE);
+ }
+ };
+
+ private void updateDuration() {
+ if (mMetadata != null) {
+ if (mMetadata.containsKey(MediaMetadata.METADATA_KEY_DURATION)) {
+ mDuration = (int) mMetadata.getLong(MediaMetadata.METADATA_KEY_DURATION);
+ // update progress bar
+ setProgress();
+ }
+ }
+ }
+
+ private void updateTitle() {
+ if (mMetadata != null) {
+ if (mMetadata.containsKey(MediaMetadata.METADATA_KEY_TITLE)) {
+ mTitleView.setText(mMetadata.getString(MediaMetadata.METADATA_KEY_TITLE));
+ }
+ }
+ }
+
+ private class MediaControllerCallback extends MediaController.Callback {
+ @Override
+ public void onPlaybackStateChanged(PlaybackState state) {
+ mPlaybackState = state;
+
+ // Update pause button depending on playback state for the following two reasons:
+ // 1) Need to handle case where app customizes playback state behavior when app
+ // activity is resumed.
+ // 2) Need to handle case where the media file reaches end of duration.
+ if (mPlaybackState.getState() != mPrevState) {
+ switch (mPlaybackState.getState()) {
+ case PlaybackState.STATE_PLAYING:
+ mPlayPauseButton.setImageDrawable(
+ ApiHelper.getLibResources().getDrawable(
+ R.drawable.ic_pause_circle_filled, null));
+ mPlayPauseButton.setContentDescription(mPauseDescription);
+ break;
+ case PlaybackState.STATE_PAUSED:
+ mPlayPauseButton.setImageDrawable(
+ ApiHelper.getLibResources().getDrawable(
+ R.drawable.ic_play_circle_filled, null));
+ mPlayPauseButton.setContentDescription(mPlayDescription);
+ break;
+ case PlaybackState.STATE_STOPPED:
+ mPlayPauseButton.setImageDrawable(
+ ApiHelper.getLibResources().getDrawable(
+ R.drawable.ic_replay, null));
+ mPlayPauseButton.setContentDescription(mReplayDescription);
+ mIsStopped = true;
+ break;
+ default:
+ break;
+ }
+ mPrevState = mPlaybackState.getState();
+ }
+
+ if (mPlaybackActions != mPlaybackState.getActions()) {
+ long newActions = mPlaybackState.getActions();
+ if ((newActions & PlaybackState.ACTION_PAUSE) != 0) {
+ mPlayPauseButton.clearColorFilter();
+ mPlayPauseButton.setEnabled(true);
+ }
+ if ((newActions & PlaybackState.ACTION_REWIND) != 0) {
+ mRewButton.clearColorFilter();
+ mRewButton.setEnabled(true);
+ }
+ if ((newActions & PlaybackState.ACTION_FAST_FORWARD) != 0) {
+ mFfwdButton.clearColorFilter();
+ mFfwdButton.setEnabled(true);
+ }
+ if ((newActions & PlaybackState.ACTION_SEEK_TO) != 0) {
+ mSeekAvailable = true;
+ } else {
+ mSeekAvailable = false;
+ }
+ mPlaybackActions = newActions;
+ }
+ }
+
+ @Override
+ public void onMetadataChanged(MediaMetadata metadata) {
+ mMetadata = metadata;
+ updateDuration();
+ updateTitle();
+ }
+
+ @Override
+ public void onSessionEvent(String event, Bundle extras) {
+ if (event.equals(EVENT_UPDATE_SUBTITLE_STATUS)) {
+ boolean newSubtitleStatus = extras.getBoolean(KEY_STATE_CONTAINS_SUBTITLE);
+ if (newSubtitleStatus != mContainsSubtitle) {
+ if (newSubtitleStatus) {
+ mSubtitleButton.clearColorFilter();
+ mSubtitleButton.setEnabled(true);
+ } else {
+ mSubtitleButton.setColorFilter(R.integer.gray);
+ mSubtitleButton.setEnabled(false);
+ }
+ mContainsSubtitle = newSubtitleStatus;
+ }
+ }
+ }
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/widget/MediaController2Impl.java b/packages/MediaComponents/src/com/android/widget/MediaController2Impl.java
deleted file mode 100644
index d322a20..0000000
--- a/packages/MediaComponents/src/com/android/widget/MediaController2Impl.java
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.widget;
-
-import android.graphics.Canvas;
-import android.media.session.MediaController;
-import android.media.update.MediaController2Provider;
-import android.media.update.ViewProvider;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.MediaController2;
-
-public class MediaController2Impl implements MediaController2Provider {
- private final MediaController2 mInstance;
- private final ViewProvider mSuperProvider;
-
- public MediaController2Impl(MediaController2 instance, ViewProvider superProvider) {
- mInstance = instance;
- mSuperProvider = superProvider;
-
- // TODO: Implement
- }
-
- @Override
- public void setController_impl(MediaController controller) {
- // TODO: Implement
- }
-
- @Override
- public void setAnchorView_impl(View view) {
- // TODO: Implement
- }
-
- @Override
- public void show_impl() {
- // TODO: Implement
- }
-
- @Override
- public void show_impl(int timeout) {
- // TODO: Implement
- }
-
- @Override
- public boolean isShowing_impl() {
- // TODO: Implement
- return false;
- }
-
- @Override
- public void hide_impl() {
- // TODO: Implement
- }
-
- @Override
- public void setPrevNextListeners_impl(OnClickListener next, OnClickListener prev) {
- // TODO: Implement
- }
-
- @Override
- public void showCCButton_impl() {
- // TODO: Implement
- }
-
- @Override
- public boolean isPlaying_impl() {
- // TODO: Implement
- return false;
- }
-
- @Override
- public int getCurrentPosition_impl() {
- // TODO: Implement
- return 0;
- }
-
- @Override
- public int getBufferPercentage_impl() {
- // TODO: Implement
- return 0;
- }
-
- @Override
- public boolean canPause_impl() {
- // TODO: Implement
- return false;
- }
-
- @Override
- public boolean canSeekBackward_impl() {
- // TODO: Implement
- return false;
- }
-
- @Override
- public boolean canSeekForward_impl() {
- // TODO: Implement
- return false;
- }
-
- @Override
- public void showSubtitle_impl() {
- // TODO: Implement
- }
-
- @Override
- public void hideSubtitle_impl() {
- // TODO: Implement
- }
-
- @Override
- public void onAttachedToWindow_impl() {
- mSuperProvider.onAttachedToWindow_impl();
- // TODO: Implement
- }
-
- @Override
- public void onDetachedFromWindow_impl() {
- mSuperProvider.onDetachedFromWindow_impl();
- // TODO: Implement
- }
-
- @Override
- public void onLayout_impl(boolean changed, int left, int top, int right, int bottom) {
- mSuperProvider.onLayout_impl(changed, left, top, right, bottom);
- // TODO: Implement
- }
-
- @Override
- public void draw_impl(Canvas canvas) {
- mSuperProvider.draw_impl(canvas);
- // TODO: Implement
- }
-
- @Override
- public CharSequence getAccessibilityClassName_impl() {
- // TODO: Implement
- return MediaController2.class.getName();
- }
-
- @Override
- public boolean onTouchEvent_impl(MotionEvent ev) {
- // TODO: Implement
- return mSuperProvider.onTouchEvent_impl(ev);
- }
-
- @Override
- public boolean onTrackballEvent_impl(MotionEvent ev) {
- // TODO: Implement
- return mSuperProvider.onTrackballEvent_impl(ev);
- }
-
- @Override
- public boolean onKeyDown_impl(int keyCode, KeyEvent event) {
- // TODO: Implement
- return mSuperProvider.onKeyDown_impl(keyCode, event);
- }
-
- @Override
- public void onFinishInflate_impl() {
- mSuperProvider.onFinishInflate_impl();
- // TODO: Implement
- }
-
- @Override
- public boolean dispatchKeyEvent_impl(KeyEvent event) {
- // TODO: Implement
- return mSuperProvider.dispatchKeyEvent_impl(event);
- }
-
- @Override
- public void setEnabled_impl(boolean enabled) {
- mSuperProvider.setEnabled_impl(enabled);
- // TODO: Implement
- }
-}
diff --git a/packages/MediaComponents/src/com/android/widget/SubtitleView.java b/packages/MediaComponents/src/com/android/widget/SubtitleView.java
new file mode 100644
index 0000000..9071967
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/widget/SubtitleView.java
@@ -0,0 +1,142 @@
+/*
+ * 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.widget;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.media.SubtitleController.Anchor;
+import android.media.SubtitleTrack.RenderingWidget;
+import android.os.Looper;
+import android.support.annotation.Nullable;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+
+class SubtitleView extends FrameLayout implements Anchor {
+ private static final String TAG = "SubtitleView";
+
+ private RenderingWidget mSubtitleWidget;
+ private RenderingWidget.OnChangedListener mSubtitlesChangedListener;
+
+ public SubtitleView(Context context) {
+ this(context, null);
+ }
+
+ public SubtitleView(Context context, @Nullable AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public SubtitleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public SubtitleView(
+ Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ public void setSubtitleWidget(RenderingWidget subtitleWidget) {
+ if (mSubtitleWidget == subtitleWidget) {
+ return;
+ }
+
+ final boolean attachedToWindow = isAttachedToWindow();
+ if (mSubtitleWidget != null) {
+ if (attachedToWindow) {
+ mSubtitleWidget.onDetachedFromWindow();
+ }
+
+ mSubtitleWidget.setOnChangedListener(null);
+ }
+ mSubtitleWidget = subtitleWidget;
+
+ if (subtitleWidget != null) {
+ if (mSubtitlesChangedListener == null) {
+ mSubtitlesChangedListener = new RenderingWidget.OnChangedListener() {
+ @Override
+ public void onChanged(RenderingWidget renderingWidget) {
+ invalidate();
+ }
+ };
+ }
+
+ setWillNotDraw(false);
+ subtitleWidget.setOnChangedListener(mSubtitlesChangedListener);
+
+ if (attachedToWindow) {
+ subtitleWidget.onAttachedToWindow();
+ requestLayout();
+ }
+ } else {
+ setWillNotDraw(true);
+ }
+
+ invalidate();
+ }
+
+ @Override
+ public Looper getSubtitleLooper() {
+ return Looper.getMainLooper();
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ if (mSubtitleWidget != null) {
+ mSubtitleWidget.onAttachedToWindow();
+ }
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+
+ if (mSubtitleWidget != null) {
+ mSubtitleWidget.onDetachedFromWindow();
+ }
+ }
+
+ @Override
+ public void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+
+ if (mSubtitleWidget != null) {
+ final int width = getWidth() - getPaddingLeft() - getPaddingRight();
+ final int height = getHeight() - getPaddingTop() - getPaddingBottom();
+
+ mSubtitleWidget.setSize(width, height);
+ }
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ super.draw(canvas);
+
+ if (mSubtitleWidget != null) {
+ final int saveCount = canvas.save();
+ canvas.translate(getPaddingLeft(), getPaddingTop());
+ mSubtitleWidget.draw(canvas);
+ canvas.restoreToCount(saveCount);
+ }
+ }
+
+ @Override
+ public CharSequence getAccessibilityClassName() {
+ return SubtitleView.class.getName();
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/widget/VideoSurfaceView.java b/packages/MediaComponents/src/com/android/widget/VideoSurfaceView.java
new file mode 100644
index 0000000..8577123
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/widget/VideoSurfaceView.java
@@ -0,0 +1,198 @@
+/*
+ * 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.widget;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.media.MediaPlayer;
+import android.support.annotation.NonNull;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+
+import static android.widget.VideoView2.VIEW_TYPE_SURFACEVIEW;
+
+class VideoSurfaceView extends SurfaceView implements VideoViewInterface, SurfaceHolder.Callback {
+ private static final String TAG = "VideoSurfaceView";
+ private static final boolean DEBUG = true; // STOPSHIP: Log.isLoggable(TAG, Log.DEBUG);
+ private SurfaceHolder mSurfaceHolder = null;
+ private SurfaceListener mSurfaceListener = null;
+ private MediaPlayer mMediaPlayer;
+ // A flag to indicate taking over other view should be proceed.
+ private boolean mIsTakingOverOldView;
+ private VideoViewInterface mOldView;
+
+
+ public VideoSurfaceView(Context context) {
+ this(context, null);
+ }
+
+ public VideoSurfaceView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public VideoSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public VideoSurfaceView(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ getHolder().addCallback(this);
+ }
+
+ ////////////////////////////////////////////////////
+ // implements VideoViewInterface
+ ////////////////////////////////////////////////////
+
+ @Override
+ public boolean assignSurfaceToMediaPlayer(MediaPlayer mp) {
+ Log.d(TAG, "assignSurfaceToMediaPlayer(): mSurfaceHolder: " + mSurfaceHolder);
+ if (mp == null || !hasAvailableSurface()) {
+ return false;
+ }
+ mp.setDisplay(mSurfaceHolder);
+ return true;
+ }
+
+ @Override
+ public void setSurfaceListener(SurfaceListener l) {
+ mSurfaceListener = l;
+ }
+
+ @Override
+ public int getViewType() {
+ return VIEW_TYPE_SURFACEVIEW;
+ }
+
+ @Override
+ public void setMediaPlayer(MediaPlayer mp) {
+ mMediaPlayer = mp;
+ if (mIsTakingOverOldView) {
+ takeOver(mOldView);
+ }
+ }
+
+ @Override
+ public void takeOver(@NonNull VideoViewInterface oldView) {
+ if (assignSurfaceToMediaPlayer(mMediaPlayer)) {
+ ((View) oldView).setVisibility(GONE);
+ mIsTakingOverOldView = false;
+ mOldView = null;
+ if (mSurfaceListener != null) {
+ mSurfaceListener.onSurfaceTakeOverDone(this);
+ }
+ } else {
+ mIsTakingOverOldView = true;
+ mOldView = oldView;
+ }
+ }
+
+ @Override
+ public boolean hasAvailableSurface() {
+ return (mSurfaceHolder != null && mSurfaceHolder.getSurface() != null);
+ }
+
+ ////////////////////////////////////////////////////
+ // implements SurfaceHolder.Callback
+ ////////////////////////////////////////////////////
+
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ Log.d(TAG, "surfaceCreated: mSurfaceHolder: " + mSurfaceHolder + ", new holder: " + holder);
+ mSurfaceHolder = holder;
+ if (mIsTakingOverOldView) {
+ takeOver(mOldView);
+ } else {
+ assignSurfaceToMediaPlayer(mMediaPlayer);
+ }
+
+ if (mSurfaceListener != null) {
+ Rect rect = mSurfaceHolder.getSurfaceFrame();
+ mSurfaceListener.onSurfaceCreated(this, rect.width(), rect.height());
+ }
+ }
+
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+ if (mSurfaceListener != null) {
+ mSurfaceListener.onSurfaceChanged(this, width, height);
+ }
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ // After we return from this we can't use the surface any more
+ mSurfaceHolder = null;
+ if (mSurfaceListener != null) {
+ mSurfaceListener.onSurfaceDestroyed(this);
+ }
+ }
+
+ // TODO: Investigate the way to move onMeasure() code into FrameLayout.
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int videoWidth = (mMediaPlayer == null) ? 0 : mMediaPlayer.getVideoWidth();
+ int videoHeight = (mMediaPlayer == null) ? 0 : mMediaPlayer.getVideoHeight();
+ if (DEBUG) {
+ Log.d(TAG, "onMeasure(" + MeasureSpec.toString(widthMeasureSpec) + ", "
+ + MeasureSpec.toString(heightMeasureSpec) + ")");
+ Log.i(TAG, " measuredSize: " + getMeasuredWidth() + "/" + getMeasuredHeight());
+ Log.i(TAG, " viewSize: " + getWidth() + "/" + getHeight());
+ Log.i(TAG, " mVideoWidth/height: " + videoWidth + ", " + videoHeight);
+ }
+
+ int width = getDefaultSize(videoWidth, widthMeasureSpec);
+ int height = getDefaultSize(videoHeight, heightMeasureSpec);
+
+ if (videoWidth > 0 && videoHeight > 0) {
+ int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
+ int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
+
+ width = widthSpecSize;
+ height = heightSpecSize;
+
+ // for compatibility, we adjust size based on aspect ratio
+ if (videoWidth * height < width * videoHeight) {
+ width = height * videoWidth / videoHeight;
+ if (DEBUG) {
+ Log.d(TAG, "image too wide, correcting. width: " + width);
+ }
+ } else if (videoWidth * height > width * videoHeight) {
+ height = width * videoHeight / videoWidth;
+ if (DEBUG) {
+ Log.d(TAG, "image too tall, correcting. height: " + height);
+ }
+ }
+ } else {
+ // no size yet, just adopt the given spec sizes
+ }
+ setMeasuredDimension(width, height);
+ if (DEBUG) {
+ Log.i(TAG, "end of onMeasure()");
+ Log.i(TAG, " measuredSize: " + getMeasuredWidth() + "/" + getMeasuredHeight());
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "ViewType: SurfaceView / Visibility: " + getVisibility()
+ + " / surfaceHolder: " + mSurfaceHolder;
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/widget/VideoTextureView.java b/packages/MediaComponents/src/com/android/widget/VideoTextureView.java
new file mode 100644
index 0000000..69a4ebe
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/widget/VideoTextureView.java
@@ -0,0 +1,210 @@
+/*
+ * 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.widget;
+
+import android.content.Context;
+import android.graphics.SurfaceTexture;
+import android.media.MediaPlayer;
+import android.support.annotation.NonNull;
+import android.support.annotation.RequiresApi;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Surface;
+import android.view.TextureView;
+import android.view.View;
+
+import static android.widget.VideoView2.VIEW_TYPE_TEXTUREVIEW;
+
+@RequiresApi(26)
+class VideoTextureView extends TextureView
+ implements VideoViewInterface, TextureView.SurfaceTextureListener {
+ private static final String TAG = "VideoTextureView";
+ private static final boolean DEBUG = true; // STOPSHIP: Log.isLoggable(TAG, Log.DEBUG);
+
+ private SurfaceTexture mSurfaceTexture;
+ private Surface mSurface;
+ private SurfaceListener mSurfaceListener;
+ private MediaPlayer mMediaPlayer;
+ // A flag to indicate taking over other view should be proceed.
+ private boolean mIsTakingOverOldView;
+ private VideoViewInterface mOldView;
+
+ public VideoTextureView(Context context) {
+ this(context, null);
+ }
+
+ public VideoTextureView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public VideoTextureView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public VideoTextureView(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ setSurfaceTextureListener(this);
+ }
+
+ ////////////////////////////////////////////////////
+ // implements VideoViewInterface
+ ////////////////////////////////////////////////////
+
+ @Override
+ public boolean assignSurfaceToMediaPlayer(MediaPlayer mp) {
+ Log.d(TAG, "assignSurfaceToMediaPlayer(): mSurfaceTexture: " + mSurfaceTexture);
+ if (mp == null || !hasAvailableSurface()) {
+ // Surface is not ready.
+ return false;
+ }
+ mp.setSurface(mSurface);
+ return true;
+ }
+
+ @Override
+ public void setSurfaceListener(SurfaceListener l) {
+ mSurfaceListener = l;
+ }
+
+ @Override
+ public int getViewType() {
+ return VIEW_TYPE_TEXTUREVIEW;
+ }
+
+ @Override
+ public void setMediaPlayer(MediaPlayer mp) {
+ mMediaPlayer = mp;
+ if (mIsTakingOverOldView) {
+ takeOver(mOldView);
+ }
+ }
+
+ @Override
+ public void takeOver(@NonNull VideoViewInterface oldView) {
+ if (assignSurfaceToMediaPlayer(mMediaPlayer)) {
+ ((View) oldView).setVisibility(GONE);
+ mIsTakingOverOldView = false;
+ mOldView = null;
+ if (mSurfaceListener != null) {
+ mSurfaceListener.onSurfaceTakeOverDone(this);
+ }
+ } else {
+ mIsTakingOverOldView = true;
+ mOldView = oldView;
+ }
+ }
+
+ @Override
+ public boolean hasAvailableSurface() {
+ return (mSurfaceTexture != null && !mSurfaceTexture.isReleased() && mSurface != null);
+ }
+
+ ////////////////////////////////////////////////////
+ // implements TextureView.SurfaceTextureListener
+ ////////////////////////////////////////////////////
+
+ @Override
+ public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
+ Log.d(TAG, "onSurfaceTextureAvailable: mSurfaceTexture: " + mSurfaceTexture
+ + ", new surface: " + surfaceTexture);
+ mSurfaceTexture = surfaceTexture;
+ mSurface = new Surface(mSurfaceTexture);
+ if (mIsTakingOverOldView) {
+ takeOver(mOldView);
+ } else {
+ assignSurfaceToMediaPlayer(mMediaPlayer);
+ }
+ if (mSurfaceListener != null) {
+ mSurfaceListener.onSurfaceCreated(this, width, height);
+ }
+ }
+
+ @Override
+ public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) {
+ if (mSurfaceListener != null) {
+ mSurfaceListener.onSurfaceChanged(this, width, height);
+ }
+ // requestLayout(); // TODO: figure out if it should be called here?
+ }
+
+ @Override
+ public void onSurfaceTextureUpdated(SurfaceTexture surface) {
+ // no-op
+ }
+
+ @Override
+ public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
+ if (mSurfaceListener != null) {
+ mSurfaceListener.onSurfaceDestroyed(this);
+ }
+ mSurfaceTexture = null;
+ mSurface = null;
+ return true;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int videoWidth = (mMediaPlayer == null) ? 0 : mMediaPlayer.getVideoWidth();
+ int videoHeight = (mMediaPlayer == null) ? 0 : mMediaPlayer.getVideoHeight();
+ if (DEBUG) {
+ Log.d(TAG, "onMeasure(" + MeasureSpec.toString(widthMeasureSpec) + ", "
+ + MeasureSpec.toString(heightMeasureSpec) + ")");
+ Log.i(TAG, " measuredSize: " + getMeasuredWidth() + "/" + getMeasuredHeight());
+ Log.i(TAG, " viewSize: " + getWidth() + "/" + getHeight());
+ Log.i(TAG, " mVideoWidth/height: " + videoWidth + ", " + videoHeight);
+ }
+
+ int width = getDefaultSize(videoWidth, widthMeasureSpec);
+ int height = getDefaultSize(videoHeight, heightMeasureSpec);
+
+ if (videoWidth > 0 && videoHeight > 0) {
+ int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
+ int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
+
+ width = widthSpecSize;
+ height = heightSpecSize;
+
+ // for compatibility, we adjust size based on aspect ratio
+ if (videoWidth * height < width * videoHeight) {
+ width = height * videoWidth / videoHeight;
+ if (DEBUG) {
+ Log.d(TAG, "image too wide, correcting. width: " + width);
+ }
+ } else if (videoWidth * height > width * videoHeight) {
+ height = width * videoHeight / videoWidth;
+ if (DEBUG) {
+ Log.d(TAG, "image too tall, correcting. height: " + height);
+ }
+ }
+ } else {
+ // no size yet, just adopt the given spec sizes
+ }
+ setMeasuredDimension(width, height);
+ if (DEBUG) {
+ Log.i(TAG, "end of onMeasure()");
+ Log.i(TAG, " measuredSize: " + getMeasuredWidth() + "/" + getMeasuredHeight());
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "ViewType: TextureView / Visibility: " + getVisibility()
+ + " / surfaceTexture: " + mSurfaceTexture;
+
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java b/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
index 66b5ed5..f3aaec8 100644
--- a/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
+++ b/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
@@ -16,220 +16,436 @@
package com.android.widget;
-import android.graphics.Canvas;
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.Resources;
import android.media.AudioAttributes;
import android.media.AudioFocusRequest;
+import android.media.AudioManager;
+import android.media.MediaMetadata;
import android.media.MediaPlayer;
+import android.media.MediaPlayerBase;
+import android.media.Cea708CaptionRenderer;
+import android.media.ClosedCaptionRenderer;
+import android.media.MediaMetadata;
+import android.media.MediaPlayer;
+import android.media.Metadata;
+import android.media.PlaybackParams;
+import android.media.SubtitleController;
+import android.media.TtmlRenderer;
+import android.media.WebVttRenderer;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
+import android.media.session.PlaybackState;
import android.media.update.VideoView2Provider;
import android.media.update.ViewProvider;
import android.net.Uri;
+import android.os.Bundle;
+import android.os.ResultReceiver;
+import android.support.annotation.Nullable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Gravity;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
-import android.widget.MediaController2;
+import android.widget.FrameLayout.LayoutParams;
+import android.widget.MediaControlView2;
import android.widget.VideoView2;
+import com.android.media.update.ApiHelper;
+import com.android.media.update.R;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Map;
public class VideoView2Impl implements VideoView2Provider, VideoViewInterface.SurfaceListener {
+ private static final String TAG = "VideoView2";
+ private static final boolean DEBUG = true; // STOPSHIP: Log.isLoggable(TAG, Log.DEBUG);
private final VideoView2 mInstance;
private final ViewProvider mSuperProvider;
- public VideoView2Impl(VideoView2 instance, ViewProvider superProvider) {
+ private static final int STATE_ERROR = -1;
+ private static final int STATE_IDLE = 0;
+ private static final int STATE_PREPARING = 1;
+ private static final int STATE_PREPARED = 2;
+ private static final int STATE_PLAYING = 3;
+ private static final int STATE_PAUSED = 4;
+ private static final int STATE_PLAYBACK_COMPLETED = 5;
+
+ private final AudioManager mAudioManager;
+ private AudioAttributes mAudioAttributes;
+ private int mAudioFocusType = AudioManager.AUDIOFOCUS_GAIN; // legacy focus gain
+ private int mAudioSession;
+
+ private VideoView2.OnPreparedListener mOnPreparedListener;
+ private VideoView2.OnCompletionListener mOnCompletionListener;
+ private VideoView2.OnErrorListener mOnErrorListener;
+ private VideoView2.OnInfoListener mOnInfoListener;
+ private VideoView2.OnViewTypeChangedListener mOnViewTypeChangedListener;
+ private VideoView2.OnFullScreenChangedListener mOnFullScreenChangedListener;
+
+ private VideoViewInterface mCurrentView;
+ private VideoTextureView mTextureView;
+ private VideoSurfaceView mSurfaceView;
+
+ private MediaPlayer mMediaPlayer;
+ private MediaControlView2 mMediaControlView;
+ private MediaSession mMediaSession;
+ private MediaController mMediaController;
+ private Metadata mMetadata;
+ private String mTitle;
+
+ private PlaybackState.Builder mStateBuilder;
+ private int mTargetState = STATE_IDLE;
+ private int mCurrentState = STATE_IDLE;
+ private int mCurrentBufferPercentage;
+ private long mSeekWhenPrepared; // recording the seek position while preparing
+
+ private int mVideoWidth;
+ private int mVideoHeight;
+
+ private boolean mCCEnabled;
+ private int mSelectedTrackIndex;
+
+ private SubtitleView mSubtitleView;
+ private float mSpeed;
+ // 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.
+
+ public VideoView2Impl(VideoView2 instance, ViewProvider superProvider,
+ @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
mInstance = instance;
mSuperProvider = superProvider;
- // TODO: Implement
+ mVideoWidth = 0;
+ mVideoHeight = 0;
+ mSpeed = 1.0f;
+ mFallbackSpeed = mSpeed;
+
+ mAudioManager = (AudioManager) mInstance.getContext()
+ .getSystemService(Context.AUDIO_SERVICE);
+ mAudioAttributes = new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA)
+ .setContentType(AudioAttributes.CONTENT_TYPE_MOVIE).build();
+ mInstance.setFocusable(true);
+ mInstance.setFocusableInTouchMode(true);
+ mInstance.requestFocus();
+
+ // TODO: try to keep a single child at a time rather than always having both.
+ mTextureView = new VideoTextureView(mInstance.getContext());
+ mSurfaceView = new VideoSurfaceView(mInstance.getContext());
+ LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT,
+ LayoutParams.MATCH_PARENT);
+ params.gravity = Gravity.CENTER;
+ mTextureView.setLayoutParams(params);
+ mSurfaceView.setLayoutParams(params);
+ mTextureView.setSurfaceListener(this);
+ mSurfaceView.setSurfaceListener(this);
+
+ // TODO: Choose TextureView when SurfaceView cannot be created.
+ // Choose surface view by default
+ mTextureView.setVisibility(View.GONE);
+ mSurfaceView.setVisibility(View.VISIBLE);
+ mInstance.addView(mTextureView);
+ mInstance.addView(mSurfaceView);
+ mCurrentView = mSurfaceView;
+
+ LayoutParams subtitleParams = new LayoutParams(LayoutParams.MATCH_PARENT,
+ LayoutParams.MATCH_PARENT);
+ mSubtitleView = new SubtitleView(mInstance.getContext());
+ mSubtitleView.setLayoutParams(subtitleParams);
+ mSubtitleView.setBackgroundColor(0);
+ mInstance.addView(mSubtitleView);
+
+ // TODO: Need a common namespace for attributes those are defined in updatable library.
+ boolean enableControlView = (attrs == null) || attrs.getAttributeBooleanValue(
+ "http://schemas.android.com/apk/com.android.media.update",
+ "enableControlView", true);
+ if (enableControlView) {
+ mMediaControlView = new MediaControlView2(mInstance.getContext());
+ }
}
@Override
- public void start_impl() {
- // TODO: Implement
+ public void setMediaControlView2_impl(MediaControlView2 mediaControlView) {
+ mMediaControlView = mediaControlView;
+
+ if (mInstance.isAttachedToWindow()) {
+ attachMediaControlView();
+ }
}
@Override
- public void pause_impl() {
- // TODO: Implement
+ public MediaController getMediaController_impl() {
+ return mMediaController;
}
@Override
- public int getDuration_impl() {
- // TODO: Implement
- return -1;
- }
-
- @Override
- public int getCurrentPosition_impl() {
- // TODO: Implement
- return 0;
- }
-
- @Override
- public void seekTo_impl(int msec) {
- // TODO: Implement
- }
-
- @Override
- public boolean isPlaying_impl() {
- // TODO: Implement
- return false;
- }
-
- @Override
- public int getBufferPercentage_impl() {
- return -1;
+ public MediaControlView2 getMediaControlView2_impl() {
+ return mMediaControlView;
}
@Override
public int getAudioSessionId_impl() {
- // TODO: Implement
- return 0;
+ if (mAudioSession == 0) {
+ MediaPlayer foo = new MediaPlayer();
+ mAudioSession = foo.getAudioSessionId();
+ foo.release();
+ }
+ return mAudioSession;
}
@Override
public void showSubtitle_impl() {
- // TODO: Implement
+ // Retrieve all tracks that belong to the current video.
+ MediaPlayer.TrackInfo[] trackInfos = mMediaPlayer.getTrackInfo();
+
+ List<Integer> subtitleTrackIndices = new ArrayList<>();
+ for (int i = 0; i < trackInfos.length; ++i) {
+ int trackType = trackInfos[i].getTrackType();
+ if (trackType == MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE) {
+ subtitleTrackIndices.add(i);
+ }
+ }
+ if (subtitleTrackIndices.size() > 0) {
+ // Select first subtitle track
+ mCCEnabled = true;
+ mSelectedTrackIndex = subtitleTrackIndices.get(0);
+ mMediaPlayer.selectTrack(mSelectedTrackIndex);
+ }
}
@Override
public void hideSubtitle_impl() {
- // TODO: Implement
+ if (mCCEnabled) {
+ mMediaPlayer.deselectTrack(mSelectedTrackIndex);
+ mCCEnabled = false;
+ }
+ }
+
+ @Override
+ public void setFullScreen_impl(boolean fullScreen) {
+ if (mOnFullScreenChangedListener != null) {
+ mOnFullScreenChangedListener.onFullScreenChanged(fullScreen);
+ }
+ }
+
+ // TODO: remove setSpeed_impl once MediaController2 is ready.
+ @Override
+ public void setSpeed_impl(float speed) {
+ if (speed <= 0.0f) {
+ Log.e(TAG, "Unsupported speed (" + speed + ") is ignored.");
+ return;
+ }
+ mSpeed = speed;
+ if (mMediaPlayer != null && mMediaPlayer.isPlaying()) {
+ applySpeed();
+ }
+ updatePlaybackState();
}
@Override
public void setAudioFocusRequest_impl(int focusGain) {
- // TODO: Implement
+ if (focusGain != AudioManager.AUDIOFOCUS_NONE
+ && focusGain != AudioManager.AUDIOFOCUS_GAIN
+ && focusGain != AudioManager.AUDIOFOCUS_GAIN_TRANSIENT
+ && focusGain != AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
+ && focusGain != AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE) {
+ throw new IllegalArgumentException("Illegal audio focus type " + focusGain);
+ }
+ mAudioFocusType = focusGain;
}
@Override
public void setAudioAttributes_impl(AudioAttributes attributes) {
- // TODO: Implement
+ if (attributes == null) {
+ throw new IllegalArgumentException("Illegal null AudioAttributes");
+ }
+ mAudioAttributes = attributes;
+ }
+
+ @Override
+ public void setRouteAttributes_impl(List<String> routeCategories, MediaPlayerBase player) {
+ // TODO: implement this.
}
@Override
public void setVideoPath_impl(String path) {
- // TODO: Implement
+ mInstance.setVideoURI(Uri.parse(path));
}
@Override
public void setVideoURI_impl(Uri uri) {
- // TODO: Implement
+ mInstance.setVideoURI(uri, null);
}
@Override
public void setVideoURI_impl(Uri uri, Map<String, String> headers) {
- // TODO: Implement
- }
-
- @Override
- public void setMediaController2_impl(MediaController2 controllerView) {
- // TODO: Implement
+ mSeekWhenPrepared = 0;
+ openVideo(uri, headers);
}
@Override
public void setViewType_impl(int viewType) {
- // TODO: Implement
+ if (viewType == mCurrentView.getViewType()) {
+ return;
+ }
+ VideoViewInterface targetView;
+ if (viewType == VideoView2.VIEW_TYPE_TEXTUREVIEW) {
+ Log.d(TAG, "switching to TextureView");
+ targetView = mTextureView;
+ } else if (viewType == VideoView2.VIEW_TYPE_SURFACEVIEW) {
+ Log.d(TAG, "switching to SurfaceView");
+ targetView = mSurfaceView;
+ } else {
+ throw new IllegalArgumentException("Unknown view type: " + viewType);
+ }
+ ((View) targetView).setVisibility(View.VISIBLE);
+ targetView.takeOver(mCurrentView);
+ mInstance.requestLayout();
}
@Override
public int getViewType_impl() {
- // TODO: Implement
- return -1;
+ return mCurrentView.getViewType();
}
@Override
- public void stopPlayback_impl() {
- // TODO: Implement
+ public void setOnPreparedListener_impl(VideoView2.OnPreparedListener l) {
+ mOnPreparedListener = l;
}
@Override
- public void setOnPreparedListener_impl(MediaPlayer.OnPreparedListener l) {
- // TODO: Implement
+ public void setOnCompletionListener_impl(VideoView2.OnCompletionListener l) {
+ mOnCompletionListener = l;
}
@Override
- public void setOnCompletionListener_impl(MediaPlayer.OnCompletionListener l) {
- // TODO: Implement
+ public void setOnErrorListener_impl(VideoView2.OnErrorListener l) {
+ mOnErrorListener = l;
}
@Override
- public void setOnErrorListener_impl(MediaPlayer.OnErrorListener l) {
- // TODO: Implement
- }
-
- @Override
- public void setOnInfoListener_impl(MediaPlayer.OnInfoListener l) {
- // TODO: Implement
+ public void setOnInfoListener_impl(VideoView2.OnInfoListener l) {
+ mOnInfoListener = l;
}
@Override
public void setOnViewTypeChangedListener_impl(VideoView2.OnViewTypeChangedListener l) {
- // TODO: Implement
+ mOnViewTypeChangedListener = l;
+ }
+
+ @Override
+ public void setFullScreenChangedListener_impl(VideoView2.OnFullScreenChangedListener l) {
+ mOnFullScreenChangedListener = l;
}
@Override
public void onAttachedToWindow_impl() {
mSuperProvider.onAttachedToWindow_impl();
- // TODO: Implement
+
+ // Create MediaSession
+ mMediaSession = new MediaSession(mInstance.getContext(), "VideoView2MediaSession");
+ mMediaSession.setCallback(new MediaSessionCallback());
+ mMediaController = mMediaSession.getController();
+
+ attachMediaControlView();
}
@Override
public void onDetachedFromWindow_impl() {
mSuperProvider.onDetachedFromWindow_impl();
- // TODO: Implement
- }
-
- @Override
- public void onLayout_impl(boolean changed, int left, int top, int right, int bottom) {
- mSuperProvider.onLayout_impl(changed, left, top, right, bottom);
- // TODO: Implement
- }
-
- @Override
- public void draw_impl(Canvas canvas) {
- mSuperProvider.draw_impl(canvas);
- // TODO: Implement
+ mMediaSession.release();
+ mMediaSession = null;
}
@Override
public CharSequence getAccessibilityClassName_impl() {
- // TODO: Implement
- return null;
+ return VideoView2.class.getName();
}
@Override
public boolean onTouchEvent_impl(MotionEvent ev) {
- // TODO: Implement
- return false;
+ if (DEBUG) {
+ Log.d(TAG, "onTouchEvent(). mCurrentState=" + mCurrentState
+ + ", mTargetState=" + mTargetState);
+ }
+ if (ev.getAction() == MotionEvent.ACTION_UP
+ && isInPlaybackState() && mMediaControlView != null) {
+ toggleMediaControlViewVisibility();
+ }
+ return mSuperProvider.onTouchEvent_impl(ev);
}
@Override
public boolean onTrackballEvent_impl(MotionEvent ev) {
- // TODO: Implement
- return false;
+ if (ev.getAction() == MotionEvent.ACTION_UP
+ && isInPlaybackState() && mMediaControlView != null) {
+ toggleMediaControlViewVisibility();
+ }
+ return mSuperProvider.onTrackballEvent_impl(ev);
}
@Override
public boolean onKeyDown_impl(int keyCode, KeyEvent event) {
- // TODO: Implement
- return false;
+ Log.v(TAG, "onKeyDown_impl: " + keyCode);
+ boolean isKeyCodeSupported = keyCode != KeyEvent.KEYCODE_BACK
+ && keyCode != KeyEvent.KEYCODE_VOLUME_UP
+ && keyCode != KeyEvent.KEYCODE_VOLUME_DOWN
+ && keyCode != KeyEvent.KEYCODE_VOLUME_MUTE
+ && keyCode != KeyEvent.KEYCODE_MENU
+ && keyCode != KeyEvent.KEYCODE_CALL
+ && keyCode != KeyEvent.KEYCODE_ENDCALL;
+ if (isInPlaybackState() && isKeyCodeSupported && mMediaControlView != null) {
+ if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK
+ || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) {
+ if (mMediaPlayer.isPlaying()) {
+ mMediaController.getTransportControls().pause();
+ mMediaControlView.show();
+ } else {
+ mMediaController.getTransportControls().play();
+ mMediaControlView.hide();
+ }
+ return true;
+ } else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) {
+ if (!mMediaPlayer.isPlaying()) {
+ mMediaController.getTransportControls().play();
+ mMediaControlView.hide();
+ }
+ return true;
+ } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP
+ || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) {
+ if (mMediaPlayer.isPlaying()) {
+ mMediaController.getTransportControls().pause();
+ mMediaControlView.show();
+ }
+ return true;
+ } else {
+ toggleMediaControlViewVisibility();
+ }
+ }
+
+ return mSuperProvider.onKeyDown_impl(keyCode, event);
}
@Override
public void onFinishInflate_impl() {
- // TODO: Implement
+ mSuperProvider.onFinishInflate_impl();
}
@Override
public boolean dispatchKeyEvent_impl(KeyEvent event) {
- // TODO: Implement
- return false;
+ return mSuperProvider.dispatchKeyEvent_impl(event);
}
@Override
public void setEnabled_impl(boolean enabled) {
- // TODO: Implement
+ mSuperProvider.setEnabled_impl(enabled);
}
///////////////////////////////////////////////////
@@ -238,21 +454,524 @@
@Override
public void onSurfaceCreated(View view, int width, int height) {
- // TODO: Implement
+ if (DEBUG) {
+ Log.d(TAG, "onSurfaceCreated(). mCurrentState=" + mCurrentState
+ + ", mTargetState=" + mTargetState + ", width/height: " + width + "/" + height
+ + ", " + view.toString());
+ }
+ if (needToStart()) {
+ mMediaController.getTransportControls().play();
+ }
}
@Override
public void onSurfaceDestroyed(View view) {
- // TODO: Implement
+ if (DEBUG) {
+ Log.d(TAG, "onSurfaceDestroyed(). mCurrentState=" + mCurrentState
+ + ", mTargetState=" + mTargetState + ", " + view.toString());
+ }
+ if (mMediaControlView != null) {
+ mMediaControlView.hide();
+ }
}
@Override
public void onSurfaceChanged(View view, int width, int height) {
- // TODO: Implement
+ // TODO: Do we need to call requestLayout here?
+ if (DEBUG) {
+ Log.d(TAG, "onSurfaceChanged(). width/height: " + width + "/" + height
+ + ", " + view.toString());
+ }
}
@Override
public void onSurfaceTakeOverDone(VideoViewInterface view) {
- // TODO: Implement
+ if (DEBUG) {
+ Log.d(TAG, "onSurfaceTakeOverDone(). Now current view is: " + view);
+ }
+ mCurrentView = view;
+ if (mOnViewTypeChangedListener != null) {
+ mOnViewTypeChangedListener.onViewTypeChanged(view.getViewType());
+ }
+ if (needToStart()) {
+ mMediaController.getTransportControls().play();
+ }
+ }
+
+ ///////////////////////////////////////////////////
+ // Protected or private methods
+ ///////////////////////////////////////////////////
+
+ private void attachMediaControlView() {
+ // Get MediaController from MediaSession and set it inside MediaControlView
+ mMediaControlView.setController(mMediaSession.getController());
+
+ LayoutParams params =
+ new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+ mInstance.addView(mMediaControlView, params);
+ }
+
+ private boolean isInPlaybackState() {
+ return (mMediaPlayer != null
+ && mCurrentState != STATE_ERROR
+ && mCurrentState != STATE_IDLE
+ && mCurrentState != STATE_PREPARING);
+ }
+
+ private boolean needToStart() {
+ return (mMediaPlayer != null
+ && mCurrentState != STATE_PLAYING
+ && mTargetState == STATE_PLAYING);
+ }
+
+ // Creates a MediaPlayer instance and prepare playback.
+ private void openVideo(Uri uri, Map<String, String> headers) {
+ resetPlayer();
+ if (mAudioFocusType != AudioManager.AUDIOFOCUS_NONE) {
+ // TODO this should have a focus listener
+ AudioFocusRequest focusRequest;
+ focusRequest = new AudioFocusRequest.Builder(mAudioFocusType)
+ .setAudioAttributes(mAudioAttributes)
+ .build();
+ mAudioManager.requestAudioFocus(focusRequest);
+ }
+
+ try {
+ Log.d(TAG, "openVideo(): creating new MediaPlayer instance.");
+ mMediaPlayer = new MediaPlayer();
+ mSurfaceView.setMediaPlayer(mMediaPlayer);
+ mTextureView.setMediaPlayer(mMediaPlayer);
+ mCurrentView.assignSurfaceToMediaPlayer(mMediaPlayer);
+
+ // TODO: create SubtitleController in MediaPlayer, but we need
+ // a context for the subtitle renderers
+ final Context context = mInstance.getContext();
+ final SubtitleController controller = new SubtitleController(
+ context, mMediaPlayer.getMediaTimeProvider(), mMediaPlayer);
+ controller.registerRenderer(new WebVttRenderer(context));
+ controller.registerRenderer(new TtmlRenderer(context));
+ controller.registerRenderer(new Cea708CaptionRenderer(context));
+ controller.registerRenderer(new ClosedCaptionRenderer(context));
+ mMediaPlayer.setSubtitleAnchor(controller, (SubtitleController.Anchor) mSubtitleView);
+
+ if (mAudioSession != 0) {
+ mMediaPlayer.setAudioSessionId(mAudioSession);
+ } else {
+ mAudioSession = mMediaPlayer.getAudioSessionId();
+ }
+ mMediaPlayer.setOnPreparedListener(mPreparedListener);
+ mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener);
+ mMediaPlayer.setOnCompletionListener(mCompletionListener);
+ mMediaPlayer.setOnErrorListener(mErrorListener);
+ mMediaPlayer.setOnInfoListener(mInfoListener);
+ mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener);
+ mCurrentBufferPercentage = 0;
+ mMediaPlayer.setDataSource(mInstance.getContext(), uri, headers);
+ mMediaPlayer.setAudioAttributes(mAudioAttributes);
+ // we don't set the target state here either, but preserve the
+ // target state that was there before.
+ mCurrentState = STATE_PREPARING;
+ mMediaPlayer.prepareAsync();
+
+ // Save file name as title since the file may not have a title Metadata.
+ mTitle = uri.getPath();
+ String scheme = uri.getScheme();
+ if (scheme != null && scheme.equals("file")) {
+ mTitle = uri.getLastPathSegment();
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "openVideo(). mCurrentState=" + mCurrentState
+ + ", mTargetState=" + mTargetState);
+ }
+ /*
+ for (Pair<InputStream, MediaFormat> pending: mPendingSubtitleTracks) {
+ try {
+ mMediaPlayer.addSubtitleSource(pending.first, pending.second);
+ } catch (IllegalStateException e) {
+ mInfoListener.onInfo(
+ mMediaPlayer, MediaPlayer.MEDIA_INFO_UNSUPPORTED_SUBTITLE, 0);
+ }
+ }
+ */
+ } catch (IOException | IllegalArgumentException ex) {
+ Log.w(TAG, "Unable to open content: " + uri, ex);
+ mCurrentState = STATE_ERROR;
+ mTargetState = STATE_ERROR;
+ mErrorListener.onError(mMediaPlayer,
+ MediaPlayer.MEDIA_ERROR_UNKNOWN, MediaPlayer.MEDIA_ERROR_IO);
+ } finally {
+ //mPendingSubtitleTracks.clear();
+ }
+ }
+
+ /*
+ * Reset the media player in any state
+ */
+ // TODO: Figure out if the legacy code's boolean parameter: cleartargetstate is necessary.
+ private void resetPlayer() {
+ if (mMediaPlayer != null) {
+ mMediaPlayer.reset();
+ mMediaPlayer.release();
+ mMediaPlayer = null;
+ //mPendingSubtitleTracks.clear();
+ mCurrentState = STATE_IDLE;
+ mTargetState = STATE_IDLE;
+ if (mAudioFocusType != AudioManager.AUDIOFOCUS_NONE) {
+ mAudioManager.abandonAudioFocus(null);
+ }
+ }
+ mVideoWidth = 0;
+ mVideoHeight = 0;
+ }
+
+ private void updatePlaybackState() {
+ if (mStateBuilder == null) {
+ // Get the capabilities of the player for this stream
+ mMetadata = mMediaPlayer.getMetadata(MediaPlayer.METADATA_ALL,
+ MediaPlayer.BYPASS_METADATA_FILTER);
+
+ // Add Play action as default
+ long playbackActions = PlaybackState.ACTION_PLAY;
+ if (mMetadata != null) {
+ if (!mMetadata.has(Metadata.PAUSE_AVAILABLE)
+ || mMetadata.getBoolean(Metadata.PAUSE_AVAILABLE)) {
+ playbackActions |= PlaybackState.ACTION_PAUSE;
+ }
+ if (!mMetadata.has(Metadata.SEEK_BACKWARD_AVAILABLE)
+ || mMetadata.getBoolean(Metadata.SEEK_BACKWARD_AVAILABLE)) {
+ playbackActions |= PlaybackState.ACTION_REWIND;
+ }
+ if (!mMetadata.has(Metadata.SEEK_FORWARD_AVAILABLE)
+ || mMetadata.getBoolean(Metadata.SEEK_FORWARD_AVAILABLE)) {
+ playbackActions |= PlaybackState.ACTION_FAST_FORWARD;
+ }
+ if (!mMetadata.has(Metadata.SEEK_AVAILABLE)
+ || mMetadata.getBoolean(Metadata.SEEK_AVAILABLE)) {
+ playbackActions |= PlaybackState.ACTION_SEEK_TO;
+ }
+ } else {
+ playbackActions |= (PlaybackState.ACTION_PAUSE |
+ PlaybackState.ACTION_REWIND | PlaybackState.ACTION_FAST_FORWARD |
+ PlaybackState.ACTION_SEEK_TO);
+ }
+ mStateBuilder = new PlaybackState.Builder();
+ mStateBuilder.setActions(playbackActions);
+ mStateBuilder.addCustomAction(MediaControlView2Impl.COMMAND_SHOW_SUBTITLE, null, -1);
+ mStateBuilder.addCustomAction(MediaControlView2Impl.COMMAND_HIDE_SUBTITLE, null, -1);
+ }
+ mStateBuilder.setState(getCorrespondingPlaybackState(),
+ mMediaPlayer.getCurrentPosition(), mSpeed);
+ mStateBuilder.setBufferedPosition(
+ (long) (mCurrentBufferPercentage / 100.0) * mMediaPlayer.getDuration());
+
+ // Set PlaybackState for MediaSession
+ if (mMediaSession != null) {
+ PlaybackState state = mStateBuilder.build();
+ mMediaSession.setPlaybackState(state);
+ }
+ }
+
+ private int getCorrespondingPlaybackState() {
+ switch (mCurrentState) {
+ case STATE_ERROR:
+ return PlaybackState.STATE_ERROR;
+ case STATE_IDLE:
+ return PlaybackState.STATE_NONE;
+ case STATE_PREPARING:
+ return PlaybackState.STATE_CONNECTING;
+ case STATE_PREPARED:
+ return PlaybackState.STATE_PAUSED;
+ case STATE_PLAYING:
+ return PlaybackState.STATE_PLAYING;
+ case STATE_PAUSED:
+ return PlaybackState.STATE_PAUSED;
+ case STATE_PLAYBACK_COMPLETED:
+ return PlaybackState.STATE_STOPPED;
+ default:
+ return -1;
+ }
+ }
+
+ private void toggleMediaControlViewVisibility() {
+ if (mMediaControlView.isShowing()) {
+ mMediaControlView.hide();
+ } else {
+ mMediaControlView.show();
+ }
+ }
+
+ private void applySpeed() {
+ PlaybackParams params = mMediaPlayer.getPlaybackParams().allowDefaults();
+ if (mSpeed != params.getSpeed()) {
+ try {
+ params.setSpeed(mSpeed);
+ mMediaPlayer.setPlaybackParams(params);
+ mFallbackSpeed = mSpeed;
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "PlaybackParams has unsupported value: " + e);
+ // TODO: should revise this part after integrating with MP2.
+ // If mSpeed had an illegal value for speed rate, system will determine best
+ // handling (see PlaybackParams.AUDIO_FALLBACK_MODE_DEFAULT).
+ // Note: The pre-MP2 returns 0.0f when it is paused. In this case, VideoView2 will
+ // use mFallbackSpeed instead.
+ float fallbackSpeed = mMediaPlayer.getPlaybackParams().allowDefaults().getSpeed();
+ if (fallbackSpeed > 0.0f) {
+ mFallbackSpeed = fallbackSpeed;
+ }
+ mSpeed = mFallbackSpeed;
+ }
+ }
+ }
+
+ MediaPlayer.OnVideoSizeChangedListener mSizeChangedListener =
+ new MediaPlayer.OnVideoSizeChangedListener() {
+ public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
+ if (DEBUG) {
+ Log.d(TAG, "OnVideoSizeChanged(): size: " + width + "/" + height);
+ }
+ mVideoWidth = mp.getVideoWidth();
+ mVideoHeight = mp.getVideoHeight();
+ if (DEBUG) {
+ Log.d(TAG, "OnVideoSizeChanged(): mVideoSize:" + mVideoWidth + "/"
+ + mVideoHeight);
+ }
+
+ if (mVideoWidth != 0 && mVideoHeight != 0) {
+ mInstance.requestLayout();
+ }
+ }
+ };
+
+ MediaPlayer.OnPreparedListener mPreparedListener = new MediaPlayer.OnPreparedListener() {
+ public void onPrepared(MediaPlayer mp) {
+ if (DEBUG) {
+ Log.d(TAG, "OnPreparedListener(). mCurrentState=" + mCurrentState
+ + ", mTargetState=" + mTargetState);
+ }
+ mCurrentState = STATE_PREPARED;
+ if (mOnPreparedListener != null) {
+ mOnPreparedListener.onPrepared();
+ }
+ if (mMediaControlView != null) {
+ mMediaControlView.setEnabled(true);
+ }
+ int videoWidth = mp.getVideoWidth();
+ int videoHeight = mp.getVideoHeight();
+
+ // mSeekWhenPrepared may be changed after seekTo() call
+ long seekToPosition = mSeekWhenPrepared;
+ if (seekToPosition != 0) {
+ mMediaController.getTransportControls().seekTo(seekToPosition);
+ }
+
+ if (videoWidth != 0 && videoHeight != 0) {
+ if (videoWidth != mVideoWidth || videoHeight != mVideoHeight) {
+ if (DEBUG) {
+ Log.i(TAG, "OnPreparedListener() : ");
+ Log.i(TAG, " video size: " + videoWidth + "/" + videoHeight);
+ Log.i(TAG, " measuredSize: " + mInstance.getMeasuredWidth() + "/"
+ + mInstance.getMeasuredHeight());
+ Log.i(TAG, " viewSize: " + mInstance.getWidth() + "/"
+ + mInstance.getHeight());
+ }
+
+ mVideoWidth = videoWidth;
+ mVideoHeight = videoHeight;
+ mInstance.requestLayout();
+ }
+
+ if (needToStart()) {
+ mMediaController.getTransportControls().play();
+ if (mMediaControlView != null) {
+ mMediaControlView.show();
+ }
+ } else if (!(isInPlaybackState() && mMediaPlayer.isPlaying())
+ && (seekToPosition != 0 || mMediaPlayer.getCurrentPosition() > 0)) {
+ if (mMediaControlView != null) {
+ // Show the media controls when we're paused into a video and
+ // make them stick.
+ mMediaControlView.show(0);
+ }
+ }
+ } else {
+ // We don't know the video size yet, but should start anyway.
+ // The video size might be reported to us later.
+ if (needToStart()) {
+ mMediaController.getTransportControls().play();
+ }
+ }
+ // Create and set playback state for MediaControlView2
+ updatePlaybackState();
+
+ // Get and set duration and title values as MediaMetadata for MediaControlView2
+ MediaMetadata.Builder builder = new MediaMetadata.Builder();
+ if (mMetadata != null && mMetadata.has(Metadata.TITLE)) {
+ mTitle = mMetadata.getString(Metadata.TITLE);
+ }
+ builder.putString(MediaMetadata.METADATA_KEY_TITLE, mTitle);
+ builder.putLong(MediaMetadata.METADATA_KEY_DURATION, mMediaPlayer.getDuration());
+
+ if (mMediaSession != null) {
+ mMediaSession.setMetadata(builder.build());
+ }
+ }
+ };
+
+ private MediaPlayer.OnCompletionListener mCompletionListener =
+ new MediaPlayer.OnCompletionListener() {
+ public void onCompletion(MediaPlayer mp) {
+ mCurrentState = STATE_PLAYBACK_COMPLETED;
+ mTargetState = STATE_PLAYBACK_COMPLETED;
+ updatePlaybackState();
+
+ if (mOnCompletionListener != null) {
+ mOnCompletionListener.onCompletion();
+ }
+ if (mAudioFocusType != AudioManager.AUDIOFOCUS_NONE) {
+ mAudioManager.abandonAudioFocus(null);
+ }
+ }
+ };
+
+ private MediaPlayer.OnInfoListener mInfoListener =
+ new MediaPlayer.OnInfoListener() {
+ public boolean onInfo(MediaPlayer mp, int what, int extra) {
+ if (mOnInfoListener != null) {
+ mOnInfoListener.onInfo(what, extra);
+ }
+ return true;
+ }
+ };
+
+ private MediaPlayer.OnErrorListener mErrorListener =
+ new MediaPlayer.OnErrorListener() {
+ public boolean onError(MediaPlayer mp, int frameworkErr, int implErr) {
+ if (DEBUG) {
+ Log.d(TAG, "Error: " + frameworkErr + "," + implErr);
+ }
+ mCurrentState = STATE_ERROR;
+ mTargetState = STATE_ERROR;
+ updatePlaybackState();
+
+ if (mMediaControlView != null) {
+ mMediaControlView.hide();
+ }
+
+ /* If an error handler has been supplied, use it and finish. */
+ if (mOnErrorListener != null) {
+ if (mOnErrorListener.onError(frameworkErr, implErr)) {
+ return true;
+ }
+ }
+
+ /* Otherwise, pop up an error dialog so the user knows that
+ * something bad has happened. Only try and pop up the dialog
+ * if we're attached to a window. When we're going away and no
+ * longer have a window, don't bother showing the user an error.
+ */
+ if (mInstance.getWindowToken() != null) {
+ int messageId;
+
+ if (frameworkErr
+ == MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK) {
+ messageId = R.string.VideoView2_error_text_invalid_progressive_playback;
+ } else {
+ messageId = R.string.VideoView2_error_text_unknown;
+ }
+
+ Resources res = ApiHelper.getLibResources();
+ new AlertDialog.Builder(mInstance.getContext())
+ .setMessage(res.getString(messageId))
+ .setPositiveButton(res.getString(R.string.VideoView2_error_button),
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog,
+ int whichButton) {
+ /* If we get here, there is no onError listener, so
+ * at least inform them that the video is over.
+ */
+ if (mOnCompletionListener != null) {
+ mOnCompletionListener.onCompletion();
+ }
+ }
+ })
+ .setCancelable(false)
+ .show();
+ }
+ return true;
+ }
+ };
+
+ private MediaPlayer.OnBufferingUpdateListener mBufferingUpdateListener =
+ new MediaPlayer.OnBufferingUpdateListener() {
+ public void onBufferingUpdate(MediaPlayer mp, int percent) {
+ mCurrentBufferPercentage = percent;
+ updatePlaybackState();
+ }
+ };
+
+ private class MediaSessionCallback extends MediaSession.Callback {
+ @Override
+ public void onCommand(String command, Bundle args, ResultReceiver receiver) {
+ switch (command) {
+ case MediaControlView2Impl.COMMAND_SHOW_SUBTITLE:
+ mInstance.showSubtitle();
+ break;
+ case MediaControlView2Impl.COMMAND_HIDE_SUBTITLE:
+ mInstance.hideSubtitle();
+ break;
+ case MediaControlView2Impl.COMMAND_SET_FULLSCREEN:
+ mInstance.setFullScreen(
+ args.getBoolean(MediaControlView2Impl.ARGUMENT_KEY_FULLSCREEN));
+ break;
+ }
+ }
+
+ @Override
+ public void onPlay() {
+ if (isInPlaybackState() && mCurrentView.hasAvailableSurface()) {
+ applySpeed();
+ mMediaPlayer.start();
+ mCurrentState = STATE_PLAYING;
+ updatePlaybackState();
+ }
+ mTargetState = STATE_PLAYING;
+ if (DEBUG) {
+ Log.d(TAG, "onPlay(). mCurrentState=" + mCurrentState
+ + ", mTargetState=" + mTargetState);
+ }
+ }
+
+ @Override
+ public void onPause() {
+ if (isInPlaybackState()) {
+ if (mMediaPlayer.isPlaying()) {
+ mMediaPlayer.pause();
+ mCurrentState = STATE_PAUSED;
+ updatePlaybackState();
+ }
+ }
+ mTargetState = STATE_PAUSED;
+ if (DEBUG) {
+ Log.d(TAG, "onPause(). mCurrentState=" + mCurrentState
+ + ", mTargetState=" + mTargetState);
+ }
+ }
+
+ @Override
+ public void onSeekTo(long pos) {
+ if (isInPlaybackState()) {
+ mMediaPlayer.seekTo(pos, MediaPlayer.SEEK_PREVIOUS_SYNC);
+ mSeekWhenPrepared = 0;
+ updatePlaybackState();
+ } else {
+ mSeekWhenPrepared = pos;
+ }
+ }
+
+ @Override
+ public void onStop() {
+ resetPlayer();
+ }
}
}
diff --git a/packages/MediaComponents/test/Android.mk b/packages/MediaComponents/test/Android.mk
new file mode 100644
index 0000000..8703b9f
--- /dev/null
+++ b/packages/MediaComponents/test/Android.mk
@@ -0,0 +1,28 @@
+# Copyright 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+# TODO(jaewan): Copy this to the CTS as well
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ mockito-target-minus-junit4 \
+ compatibility-device-util
+
+LOCAL_PACKAGE_NAME := MediaComponentsTest
+include $(BUILD_PACKAGE)
diff --git a/packages/MediaComponents/test/AndroidManifest.xml b/packages/MediaComponents/test/AndroidManifest.xml
new file mode 100644
index 0000000..30bac87
--- /dev/null
+++ b/packages/MediaComponents/test/AndroidManifest.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.media.test">
+
+ <application android:label="Media API Test">
+ <uses-library android:name="android.test.runner" />
+
+ <activity android:name="android.widget2.VideoView2TestActivity"
+ android:configChanges="keyboardHidden|orientation|screenSize"
+ android:label="VideoView2TestActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <!-- Keep the test services synced together with the TestUtils.java -->
+ <service android:name="android.media.MockMediaSessionService2">
+ <intent-filter>
+ <action android:name="android.media.MediaSessionService2" />
+ </intent-filter>
+ <meta-data android:name="android.media.session" android:value="TestSession" />
+ </service>
+ <!-- Keep the test services synced together with the MockMediaLibraryService -->
+ <service android:name="android.media.MockMediaLibraryService2">
+ <intent-filter>
+ <action android:name="android.media.MediaLibraryService2" />
+ </intent-filter>
+ <meta-data android:name="android.media.session" android:value="TestBrowser" />
+ </service>
+ </application>
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.media.test"
+ android:label="Media API test" />
+
+</manifest>
diff --git a/packages/MediaComponents/test/runtest.sh b/packages/MediaComponents/test/runtest.sh
new file mode 100644
index 0000000..5c0ef51
--- /dev/null
+++ b/packages/MediaComponents/test/runtest.sh
@@ -0,0 +1,189 @@
+#!/bin/bash
+# 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.
+
+# Usage '. runtest.sh'
+
+function _runtest_mediacomponent_usage() {
+ echo 'runtest-MediaComponents [option]: Run MediaComponents test'
+ echo ' -h|--help: This help'
+ echo ' --skip: Skip build. Just rerun-tests.'
+ echo ' --min: Only rebuild test apk and updatable library.'
+ 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.'
+ echo ' --ignore: Keep repeating tests even when it fails.'
+ echo ' -t [test]: Only run the specific test. Can be either a class or a method.'
+}
+
+function runtest-MediaComponents() {
+ # Edit here if you want to support other tests.
+ # List up libs and apks in the media_api needed for tests, and place test target at the last.
+ local TEST_PACKAGE_DIR=("frameworks/av/packages/MediaComponents/test")
+ local BUILD_TARGETS=("MediaComponents" "MediaComponentsTest")
+ local INSTALL_TARGETS=("MediaComponentsTest")
+ local TEST_RUNNER="android.support.test.runner.AndroidJUnitRunner"
+ local DEPENDENCIES=("mockito-target-minus-junit4" "android-support-test" "compatibility-device-util")
+
+ if [[ -z "${ANDROID_BUILD_TOP}" ]]; then
+ echo "Needs to lunch a target first"
+ return
+ fi
+
+ local old_path=${OLDPWD}
+ while true; do
+ local OPTION_SKIP="false"
+ local OPTION_MIN="false"
+ local OPTION_REPEAT_COUNT="1"
+ local OPTION_IGNORE="false"
+ local OPTION_TEST_TARGET=""
+ local adbhost_local
+ while (( "$#" )); do
+ case "${1}" in
+ -h|--help)
+ _runtest_mediacomponent_usage
+ return
+ ;;
+ --skip)
+ OPTION_SKIP="true"
+ ;;
+ --min)
+ OPTION_MIN="true"
+ ;;
+ -s)
+ shift
+ adbhost_local=${1}
+ ;;
+ -r)
+ shift
+ OPTION_REPEAT_COUNT="${1}"
+ ;;
+ --ignore)
+ OPTION_IGNORE="true"
+ ;;
+ -t)
+ shift
+ OPTION_TEST_TARGET="${1}"
+ esac
+ shift
+ done
+
+ # Build adb command.
+ local adb
+ if [[ -z "${adbhost_local}" ]]; then
+ adbhost_local=${ADBHOST}
+ fi
+ if [[ -z "${adbhost_local}" ]]; then
+ local device_count=$(adb devices | sed '/^[[:space:]]*$/d' | wc -l)
+ if [[ "${device_count}" != "2" ]]; then
+ echo "Too many devices. Specify a device." && break
+ fi
+ adb="adb"
+ else
+ adb="adb -s ${adbhost_local}"
+ fi
+
+ local target_dir="${ANDROID_BUILD_TOP}/${TEST_PACKAGE_DIR}"
+ local TEST_PACKAGE=$(sed -n 's/^.*\bpackage\b="\([a-z0-9\.]*\)".*$/\1/p' ${target_dir}/AndroidManifest.xml)
+
+ if [[ "${OPTION_SKIP}" != "true" ]]; then
+ # Build dependencies if needed.
+ local dependency
+ local build_dependency=""
+ for dependency in ${DEPENDENCIES[@]}; do
+ if [[ "${dependency}" == "out/"* ]]; then
+ if [[ ! -f ${ANDROID_BUILD_TOP}/${dependency} ]]; then
+ build_dependency="true"
+ break
+ fi
+ else
+ if [[ "$(find ${OUT} -name ${dependency}_intermediates | wc -l)" == "0" ]]; then
+ build_dependency="true"
+ break
+ fi
+ fi
+ done
+ if [[ "${build_dependency}" == "true" ]]; then
+ echo "Building dependencies. Will only print stderr."
+ m ${DEPENDENCIES[@]} -j > /dev/null
+ fi
+
+ # Build test apk and required apk.
+ local build_targets="${BUILD_TARGETS[@]}"
+ if [[ "${OPTION_MIN}" != "true" ]]; then
+ build_targets="${build_targets} droid"
+ fi
+ m ${build_targets} -j || break
+
+ ${adb} root
+ ${adb} remount
+ ${adb} shell stop
+ ${adb} sync
+ ${adb} shell start
+ ${adb} wait-for-device || break
+ # Ensure package manager is loaded.
+ sleep 5
+
+ # Install apks
+ local install_failed="false"
+ for target in ${INSTALL_TARGETS[@]}; do
+ echo "${target}"
+ local target_dir=$(mgrep -l -e '^LOCAL_PACKAGE_NAME.*'"${target}$")
+ if [[ -z ${target_dir} ]]; then
+ continue
+ fi
+ target_dir=$(dirname ${target_dir})
+ local package=$(sed -n 's/^.*\bpackage\b="\([a-z0-9\._]*\)".*$/\1/p' ${target_dir}/AndroidManifest.xml)
+ local apk_path=$(find ${OUT} -name ${target}.apk)
+ if [[ -z "${apk_path}" ]]; then
+ echo "Cannot locate ${target}.apk" && break
+ fi
+ echo "Installing ${target}.apk. path=${apk_path}"
+ ${adb} install -r ${apk_path}
+ if [[ "${?}" != "0" ]]; then
+ install_failed="true"
+ break
+ fi
+ done
+ if [[ "${install_failed}" == "true" ]]; then
+ echo "Failed to install. Test wouldn't run."
+ break
+ fi
+ fi
+
+ local test_target=""
+ if [[ -n "${OPTION_TEST_TARGET}" ]]; then
+ test_target="-e class ${OPTION_TEST_TARGET}"
+ fi
+
+ local i
+ local tmpfile=$(tempfile)
+ for ((i=1; i <= ${OPTION_REPEAT_COUNT}; i++)); do
+ echo "Run test ${i}/${OPTION_REPEAT_COUNT}"
+ ${adb} shell am instrument ${test_target} -w ${TEST_PACKAGE}/${TEST_RUNNER} >& ${tmpfile}
+ cat ${tmpfile}
+ if [[ "${OPTION_IGNORE}" != "true" ]]; then
+ if [[ -n "$(grep ${tmpfile} -e 'FAILURE\|crashed')" ]]; then
+ # am instrument doesn't return error code so need to grep result message instead
+ break
+ fi
+ fi
+ done
+ rm ${tmpfile}
+ break
+ done
+}
+
+echo "Following functions are added to your environment:"
+_runtest_mediacomponent_usage
diff --git a/packages/MediaComponents/test/src/android/media/MediaBrowser2Test.java b/packages/MediaComponents/test/src/android/media/MediaBrowser2Test.java
new file mode 100644
index 0000000..ef75060
--- /dev/null
+++ b/packages/MediaComponents/test/src/android/media/MediaBrowser2Test.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import android.content.Context;
+import android.media.MediaBrowser2.BrowserCallback;
+import android.media.MediaSession2.CommandGroup;
+import android.os.Bundle;
+import android.support.annotation.CallSuper;
+import android.support.annotation.NonNull;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests {@link MediaBrowser2}.
+ * <p>
+ * This test inherits {@link MediaController2Test} to ensure that inherited APIs from
+ * {@link MediaController2} works cleanly.
+ */
+// TODO(jaewan): Implement host-side test so browser and service can run in different processes.
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MediaBrowser2Test extends MediaController2Test {
+ private static final String TAG = "MediaBrowser2Test";
+
+ @Override
+ TestControllerInterface onCreateController(@NonNull SessionToken2 token,
+ @NonNull TestControllerCallbackInterface callback) {
+ return new TestMediaBrowser(mContext, token, new TestBrowserCallback(callback));
+ }
+
+ @Test
+ public void testGetBrowserRoot() throws InterruptedException {
+ final Bundle param = new Bundle();
+ param.putString(TAG, TAG);
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ final TestControllerCallbackInterface callback = new TestControllerCallbackInterface() {
+ @Override
+ public void onGetRootResult(Bundle rootHints, String rootMediaId, Bundle rootExtra) {
+ assertTrue(TestUtils.equals(param, rootHints));
+ assertEquals(MockMediaLibraryService2.ROOT_ID, rootMediaId);
+ assertTrue(TestUtils.equals(MockMediaLibraryService2.EXTRA, rootExtra));
+ latch.countDown();
+ }
+ };
+
+ final SessionToken2 token = MockMediaLibraryService2.getToken(mContext);
+ MediaBrowser2 browser =
+ (MediaBrowser2) createController(token,true, callback);
+ browser.getBrowserRoot(param);
+ assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ }
+
+ public static class TestBrowserCallback extends BrowserCallback
+ implements WaitForConnectionInterface {
+ private final TestControllerCallbackInterface mCallbackProxy;
+ public final CountDownLatch connectLatch = new CountDownLatch(1);
+ public final CountDownLatch disconnectLatch = new CountDownLatch(1);
+
+ TestBrowserCallback(TestControllerCallbackInterface callbackProxy) {
+ mCallbackProxy = callbackProxy;
+ }
+
+ @CallSuper
+ @Override
+ public void onConnected(CommandGroup commands) {
+ super.onConnected(commands);
+ connectLatch.countDown();
+ }
+
+ @CallSuper
+ @Override
+ public void onDisconnected() {
+ super.onDisconnected();
+ disconnectLatch.countDown();
+ }
+
+ @Override
+ public void onGetRootResult(Bundle rootHints, String rootMediaId, Bundle rootExtra) {
+ mCallbackProxy.onGetRootResult(rootHints, rootMediaId, rootExtra);
+ }
+
+ @Override
+ public void waitForConnect(boolean expect) throws InterruptedException {
+ if (expect) {
+ assertTrue(connectLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ } else {
+ assertFalse(connectLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ }
+ }
+
+ @Override
+ public void waitForDisconnect(boolean expect) throws InterruptedException {
+ if (expect) {
+ assertTrue(disconnectLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ } else {
+ assertFalse(disconnectLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ }
+ }
+ }
+
+ public class TestMediaBrowser extends MediaBrowser2 implements TestControllerInterface {
+ private final BrowserCallback mCallback;
+
+ public TestMediaBrowser(@NonNull Context context, @NonNull SessionToken2 token,
+ @NonNull ControllerCallback callback) {
+ super(context, token, sHandlerExecutor, (BrowserCallback) callback);
+ mCallback = (BrowserCallback) callback;
+ }
+
+ @Override
+ public BrowserCallback getCallback() {
+ return mCallback;
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/MediaComponents/test/src/android/media/MediaController2Test.java b/packages/MediaComponents/test/src/android/media/MediaController2Test.java
new file mode 100644
index 0000000..ae67a95
--- /dev/null
+++ b/packages/MediaComponents/test/src/android/media/MediaController2Test.java
@@ -0,0 +1,487 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.media.MediaPlayerBase.PlaybackListener;
+import android.media.MediaSession2.ControllerInfo;
+import android.media.MediaSession2.SessionCallback;
+import android.media.TestUtils.SyncHandler;
+import android.media.session.PlaybackState;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.support.test.filters.FlakyTest;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import static android.media.TestUtils.createPlaybackState;
+import static org.junit.Assert.*;
+
+/**
+ * Tests {@link MediaController2}.
+ */
+// TODO(jaewan): Implement host-side test so controller and session can run in different processes.
+// TODO(jaewan): Fix flaky failure -- see MediaController2Impl.getController()
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@FlakyTest
+public class MediaController2Test extends MediaSession2TestBase {
+ private static final String TAG = "MediaController2Test";
+
+ MediaSession2 mSession;
+ MediaController2 mController;
+ MockPlayer mPlayer;
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ // Create this test specific MediaSession2 to use our own Handler.
+ sHandler.postAndSync(()->{
+ mPlayer = new MockPlayer(1);
+ mSession = new MediaSession2.Builder(mContext, mPlayer).setId(TAG).build();
+ });
+
+ mController = createController(mSession.getToken());
+ TestServiceRegistry.getInstance().setHandler(sHandler);
+ }
+
+ @After
+ @Override
+ public void cleanUp() throws Exception {
+ super.cleanUp();
+ sHandler.postAndSync(() -> {
+ if (mSession != null) {
+ mSession.close();
+ }
+ });
+ TestServiceRegistry.getInstance().cleanUp();
+ }
+
+ @Test
+ public void testPlay() throws InterruptedException {
+ mController.play();
+ try {
+ assertTrue(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ } catch (InterruptedException e) {
+ fail(e.getMessage());
+ }
+ assertTrue(mPlayer.mPlayCalled);
+ }
+
+ @Test
+ public void testPause() throws InterruptedException {
+ mController.pause();
+ try {
+ assertTrue(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ } catch (InterruptedException e) {
+ fail(e.getMessage());
+ }
+ assertTrue(mPlayer.mPauseCalled);
+ }
+
+
+ @Test
+ public void testSkipToPrevious() throws InterruptedException {
+ mController.skipToPrevious();
+ try {
+ assertTrue(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ } catch (InterruptedException e) {
+ fail(e.getMessage());
+ }
+ assertTrue(mPlayer.mSkipToPreviousCalled);
+ }
+
+ @Test
+ public void testSkipToNext() throws InterruptedException {
+ mController.skipToNext();
+ try {
+ assertTrue(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ } catch (InterruptedException e) {
+ fail(e.getMessage());
+ }
+ assertTrue(mPlayer.mSkipToNextCalled);
+ }
+
+ @Test
+ public void testStop() throws InterruptedException {
+ mController.stop();
+ try {
+ assertTrue(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ } catch (InterruptedException e) {
+ fail(e.getMessage());
+ }
+ assertTrue(mPlayer.mStopCalled);
+ }
+
+ @Test
+ public void testGetPackageName() {
+ assertEquals(mContext.getPackageName(), mController.getSessionToken().getPackageName());
+ }
+
+ @Test
+ public void testGetPlaybackState() throws InterruptedException {
+ // TODO(jaewan): add equivalent test later
+ /*
+ final CountDownLatch latch = new CountDownLatch(1);
+ final MediaPlayerBase.PlaybackListener listener = (state) -> {
+ assertEquals(PlaybackState.STATE_BUFFERING, state.getState());
+ latch.countDown();
+ };
+ assertNull(mController.getPlaybackState());
+ mController.addPlaybackListener(listener, sHandler);
+
+ mPlayer.notifyPlaybackState(createPlaybackState(PlaybackState.STATE_BUFFERING));
+ assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ assertEquals(PlaybackState.STATE_BUFFERING, mController.getPlaybackState().getState());
+ */
+ }
+
+ // TODO(jaewan): add equivalent test later
+ /*
+ @Test
+ public void testAddPlaybackListener() throws InterruptedException {
+ final CountDownLatch latch = new CountDownLatch(2);
+ final MediaPlayerBase.PlaybackListener listener = (state) -> {
+ switch ((int) latch.getCount()) {
+ case 2:
+ assertEquals(PlaybackState.STATE_PLAYING, state.getState());
+ break;
+ case 1:
+ assertEquals(PlaybackState.STATE_PAUSED, state.getState());
+ break;
+ }
+ latch.countDown();
+ };
+
+ mController.addPlaybackListener(listener, sHandler);
+ sHandler.postAndSync(()->{
+ mPlayer.notifyPlaybackState(createPlaybackState(PlaybackState.STATE_PLAYING));
+ mPlayer.notifyPlaybackState(createPlaybackState(PlaybackState.STATE_PAUSED));
+ });
+ assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ }
+
+ @Test
+ public void testRemovePlaybackListener() throws InterruptedException {
+ final CountDownLatch latch = new CountDownLatch(1);
+ final MediaPlayerBase.PlaybackListener listener = (state) -> {
+ fail();
+ latch.countDown();
+ };
+ mController.addPlaybackListener(listener, sHandler);
+ mController.removePlaybackListener(listener);
+ mPlayer.notifyPlaybackState(createPlaybackState(PlaybackState.STATE_PLAYING));
+ assertFalse(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ }
+ */
+
+ @Test
+ public void testControllerCallback_onConnected() throws InterruptedException {
+ // createController() uses controller callback to wait until the controller becomes
+ // available.
+ MediaController2 controller = createController(mSession.getToken());
+ assertNotNull(controller);
+ }
+
+ @Test
+ public void testControllerCallback_sessionRejects() throws InterruptedException {
+ final MediaSession2.SessionCallback sessionCallback = new SessionCallback() {
+ @Override
+ public MediaSession2.CommandGroup onConnect(ControllerInfo controller) {
+ return null;
+ }
+ };
+ sHandler.postAndSync(() -> {
+ mSession.close();
+ mSession = new MediaSession2.Builder(mContext, mPlayer)
+ .setSessionCallback(sHandlerExecutor, sessionCallback).build();
+ });
+ MediaController2 controller =
+ createController(mSession.getToken(), false, null);
+ assertNotNull(controller);
+ waitForConnect(controller, false);
+ waitForDisconnect(controller, true);
+ }
+
+ @Test
+ public void testControllerCallback_releaseSession() throws InterruptedException {
+ sHandler.postAndSync(() -> {
+ mSession.close();
+ });
+ waitForDisconnect(mController, true);
+ }
+
+ @Test
+ public void testControllerCallback_release() throws InterruptedException {
+ mController.close();
+ waitForDisconnect(mController, true);
+ }
+
+ @Test
+ public void testIsConnected() throws InterruptedException {
+ assertTrue(mController.isConnected());
+ sHandler.postAndSync(()->{
+ mSession.close();
+ });
+ // postAndSync() to wait until the disconnection is propagated.
+ sHandler.postAndSync(()->{
+ assertFalse(mController.isConnected());
+ });
+ }
+
+ /**
+ * Test potential deadlock for calls between controller and session.
+ */
+ @Test
+ public void testDeadlock() throws InterruptedException {
+ sHandler.postAndSync(() -> {
+ mSession.close();
+ mSession = null;
+ });
+
+ // Two more threads are needed not to block test thread nor test wide thread (sHandler).
+ final HandlerThread sessionThread = new HandlerThread("testDeadlock_session");
+ final HandlerThread testThread = new HandlerThread("testDeadlock_test");
+ sessionThread.start();
+ testThread.start();
+ final SyncHandler sessionHandler = new SyncHandler(sessionThread.getLooper());
+ final Handler testHandler = new Handler(testThread.getLooper());
+ final CountDownLatch latch = new CountDownLatch(1);
+ try {
+ final MockPlayer player = new MockPlayer(0);
+ sessionHandler.postAndSync(() -> {
+ mSession = new MediaSession2.Builder(mContext, mPlayer)
+ .setId("testDeadlock").build();
+ });
+ final MediaController2 controller = createController(mSession.getToken());
+ testHandler.post(() -> {
+ final PlaybackState2 state = createPlaybackState(PlaybackState.STATE_ERROR);
+ for (int i = 0; i < 100; i++) {
+ // triggers call from session to controller.
+ player.notifyPlaybackState(state);
+ // triggers call from controller to session.
+ controller.play();
+
+ // Repeat above
+ player.notifyPlaybackState(state);
+ controller.pause();
+ player.notifyPlaybackState(state);
+ controller.stop();
+ player.notifyPlaybackState(state);
+ controller.skipToNext();
+ player.notifyPlaybackState(state);
+ controller.skipToPrevious();
+ }
+ // This may hang if deadlock happens.
+ latch.countDown();
+ });
+ assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ } finally {
+ if (mSession != null) {
+ sessionHandler.postAndSync(() -> {
+ // Clean up here because sessionHandler will be removed afterwards.
+ mSession.close();
+ mSession = null;
+ });
+ }
+ if (sessionThread != null) {
+ sessionThread.quitSafely();
+ }
+ if (testThread != null) {
+ testThread.quitSafely();
+ }
+ }
+ }
+
+ @Ignore
+ @Test
+ public void testGetServiceToken() {
+ SessionToken2 token = TestUtils.getServiceToken(mContext, MockMediaSessionService2.ID);
+ assertNotNull(token);
+ assertEquals(mContext.getPackageName(), token.getPackageName());
+ assertEquals(MockMediaSessionService2.ID, token.getId());
+ assertNull(token.getSessionBinder());
+ assertEquals(SessionToken2.TYPE_SESSION_SERVICE, token.getType());
+ }
+
+ private void connectToService(SessionToken2 token) throws InterruptedException {
+ mController = createController(token);
+ mSession = TestServiceRegistry.getInstance().getServiceInstance().getSession();
+ mPlayer = (MockPlayer) mSession.getPlayer();
+ }
+
+ // TODO(jaewan): Reenable when session manager detects app installs
+ @Ignore
+ @Test
+ public void testConnectToService_sessionService() throws InterruptedException {
+ connectToService(TestUtils.getServiceToken(mContext, MockMediaSessionService2.ID));
+ testConnectToService();
+ }
+
+ // TODO(jaewan): Reenable when session manager detects app installs
+ @Ignore
+ @Test
+ public void testConnectToService_libraryService() throws InterruptedException {
+ connectToService(TestUtils.getServiceToken(mContext, MockMediaLibraryService2.ID));
+ testConnectToService();
+ }
+
+ public void testConnectToService() throws InterruptedException {
+ TestServiceRegistry serviceInfo = TestServiceRegistry.getInstance();
+ ControllerInfo info = serviceInfo.getOnConnectControllerInfo();
+ assertEquals(mContext.getPackageName(), info.getPackageName());
+ assertEquals(Process.myUid(), info.getUid());
+ assertFalse(info.isTrusted());
+
+ // Test command from controller to session service
+ mController.play();
+ assertTrue(mPlayer.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ assertTrue(mPlayer.mPlayCalled);
+
+ // Test command from session service to controller
+ // TODO(jaewan): Add equivalent tests again
+ /*
+ final CountDownLatch latch = new CountDownLatch(1);
+ mController.addPlaybackListener((state) -> {
+ assertNotNull(state);
+ assertEquals(PlaybackState.STATE_REWINDING, state.getState());
+ latch.countDown();
+ }, sHandler);
+ mPlayer.notifyPlaybackState(
+ TestUtils.createPlaybackState(PlaybackState.STATE_REWINDING));
+ assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ */
+ }
+
+ @Test
+ public void testControllerAfterSessionIsGone_session() throws InterruptedException {
+ testControllerAfterSessionIsGone(mSession.getToken().getId());
+ }
+
+ @Ignore
+ @Test
+ public void testControllerAfterSessionIsGone_sessionService() throws InterruptedException {
+ connectToService(TestUtils.getServiceToken(mContext, MockMediaSessionService2.ID));
+ testControllerAfterSessionIsGone(MockMediaSessionService2.ID);
+ }
+
+ @Test
+ public void testClose_beforeConnected() throws InterruptedException {
+ MediaController2 controller =
+ createController(mSession.getToken(), false, null);
+ controller.close();
+ }
+
+ @Test
+ public void testClose_twice() throws InterruptedException {
+ mController.close();
+ mController.close();
+ }
+
+ @Test
+ public void testClose_session() throws InterruptedException {
+ final String id = mSession.getToken().getId();
+ mController.close();
+ // close is done immediately for session.
+ testNoInteraction();
+
+ // Test whether the controller is notified about later close of the session or
+ // re-creation.
+ testControllerAfterSessionIsGone(id);
+ }
+
+ // TODO(jaewan): Reenable when session manager detects app installs
+ @Ignore
+ @Test
+ public void testClose_sessionService() throws InterruptedException {
+ connectToService(TestUtils.getServiceToken(mContext, MockMediaSessionService2.ID));
+ testCloseFromService();
+ }
+
+ // TODO(jaewan): Reenable when session manager detects app installs
+ @Ignore
+ @Test
+ public void testClose_libraryService() throws InterruptedException {
+ connectToService(TestUtils.getServiceToken(mContext, MockMediaSessionService2.ID));
+ testCloseFromService();
+ }
+
+ private void testCloseFromService() throws InterruptedException {
+ final String id = mController.getSessionToken().getId();
+ final CountDownLatch latch = new CountDownLatch(1);
+ TestServiceRegistry.getInstance().setServiceInstanceChangedCallback((service) -> {
+ if (service == null) {
+ // Destroying..
+ latch.countDown();
+ }
+ });
+ mController.close();
+ // Wait until close triggers onDestroy() of the session service.
+ assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ assertNull(TestServiceRegistry.getInstance().getServiceInstance());
+ testNoInteraction();
+
+ // Test whether the controller is notified about later close of the session or
+ // re-creation.
+ testControllerAfterSessionIsGone(id);
+ }
+
+ private void testControllerAfterSessionIsGone(final String id) throws InterruptedException {
+ sHandler.postAndSync(() -> {
+ // TODO(jaewan): Use Session.close later when we add the API.
+ mSession.close();
+ });
+ waitForDisconnect(mController, true);
+ testNoInteraction();
+
+ // Test with the newly created session.
+ sHandler.postAndSync(() -> {
+ // Recreated session has different session stub, so previously created controller
+ // shouldn't be available.
+ mSession = new MediaSession2.Builder(mContext, mPlayer).setId(id).build();
+ });
+ testNoInteraction();
+ }
+
+ private void testNoInteraction() throws InterruptedException {
+ final CountDownLatch latch = new CountDownLatch(1);
+ final PlaybackListener playbackListener = (state) -> {
+ fail("Controller shouldn't be notified about change in session after the close.");
+ latch.countDown();
+ };
+ // TODO(jaewan): Add equivalent tests again
+ /*
+ mController.addPlaybackListener(playbackListener, sHandler);
+ mPlayer.notifyPlaybackState(TestUtils.createPlaybackState(PlaybackState.STATE_BUFFERING));
+ assertFalse(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ mController.removePlaybackListener(playbackListener);
+ */
+ }
+
+ // TODO(jaewan): Add test for service connect rejection, when we differentiate session
+ // active/inactive and connection accept/refuse
+}
diff --git a/packages/MediaComponents/test/src/android/media/MediaSession2Test.java b/packages/MediaComponents/test/src/android/media/MediaSession2Test.java
new file mode 100644
index 0000000..045dcd5
--- /dev/null
+++ b/packages/MediaComponents/test/src/android/media/MediaSession2Test.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.media.MediaPlayerBase.PlaybackListener;
+import android.media.MediaSession2.Builder;
+import android.media.MediaSession2.ControllerInfo;
+import android.media.MediaSession2.SessionCallback;
+import android.media.session.PlaybackState;
+import android.os.Process;
+import android.os.Looper;
+import android.support.annotation.NonNull;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import java.util.ArrayList;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import static android.media.TestUtils.createPlaybackState;
+import static org.junit.Assert.*;
+
+/**
+ * Tests {@link MediaSession2}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MediaSession2Test extends MediaSession2TestBase {
+ private static final String TAG = "MediaSession2Test";
+
+ private MediaSession2 mSession;
+ private MockPlayer mPlayer;
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ sHandler.postAndSync(() -> {
+ mPlayer = new MockPlayer(0);
+ mSession = new MediaSession2.Builder(mContext, mPlayer).build();
+ });
+ }
+
+ @After
+ @Override
+ public void cleanUp() throws Exception {
+ super.cleanUp();
+ sHandler.postAndSync(() -> {
+ mSession.close();
+ });
+ }
+
+ @Test
+ public void testBuilder() throws Exception {
+ try {
+ MediaSession2.Builder builder = new Builder(mContext, null);
+ fail("null player shouldn't be allowed");
+ } catch (IllegalArgumentException e) {
+ // expected. pass-through
+ }
+ MediaSession2.Builder builder = new Builder(mContext, mPlayer);
+ try {
+ builder.setId(null);
+ fail("null id shouldn't be allowed");
+ } catch (IllegalArgumentException e) {
+ // expected. pass-through
+ }
+ }
+
+ @Test
+ public void testSetPlayer() throws Exception {
+ sHandler.postAndSync(() -> {
+ MockPlayer player = new MockPlayer(0);
+ // Test if setPlayer doesn't crash with various situations.
+ mSession.setPlayer(mPlayer);
+ mSession.setPlayer(player);
+ mSession.close();
+ });
+ }
+
+ @Test
+ public void testPlay() throws Exception {
+ sHandler.postAndSync(() -> {
+ mSession.play();
+ assertTrue(mPlayer.mPlayCalled);
+ });
+ }
+
+ @Test
+ public void testPause() throws Exception {
+ sHandler.postAndSync(() -> {
+ mSession.pause();
+ assertTrue(mPlayer.mPauseCalled);
+ });
+ }
+
+ @Test
+ public void testStop() throws Exception {
+ sHandler.postAndSync(() -> {
+ mSession.stop();
+ assertTrue(mPlayer.mStopCalled);
+ });
+ }
+
+ @Test
+ public void testSkipToNext() throws Exception {
+ sHandler.postAndSync(() -> {
+ mSession.skipToNext();
+ assertTrue(mPlayer.mSkipToNextCalled);
+ });
+ }
+
+ @Test
+ public void testSkipToPrevious() throws Exception {
+ sHandler.postAndSync(() -> {
+ mSession.skipToPrevious();
+ assertTrue(mPlayer.mSkipToPreviousCalled);
+ });
+ }
+
+ @Test
+ public void testPlaybackStateChangedListener() throws InterruptedException {
+ // TODO(jaewan): Add equivalent tests again
+ /*
+ final CountDownLatch latch = new CountDownLatch(2);
+ final MockPlayer player = new MockPlayer(0);
+ final PlaybackListener listener = (state) -> {
+ assertEquals(sHandler.getLooper(), Looper.myLooper());
+ assertNotNull(state);
+ switch ((int) latch.getCount()) {
+ case 2:
+ assertEquals(PlaybackState.STATE_PLAYING, state.getState());
+ break;
+ case 1:
+ assertEquals(PlaybackState.STATE_PAUSED, state.getState());
+ break;
+ case 0:
+ fail();
+ }
+ latch.countDown();
+ };
+ player.notifyPlaybackState(createPlaybackState(PlaybackState.STATE_PLAYING));
+ sHandler.postAndSync(() -> {
+ mSession.addPlaybackListener(listener, sHandler);
+ // When the player is set, listeners will be notified about the player's current state.
+ mSession.setPlayer(player);
+ });
+ player.notifyPlaybackState(createPlaybackState(PlaybackState.STATE_PAUSED));
+ assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ */
+ }
+
+ @Test
+ public void testBadPlayer() throws InterruptedException {
+ // TODO(jaewan): Add equivalent tests again
+ /*
+ final CountDownLatch latch = new CountDownLatch(3); // expected call + 1
+ final BadPlayer player = new BadPlayer(0);
+ sHandler.postAndSync(() -> {
+ mSession.addPlaybackListener((state) -> {
+ // This will be called for every setPlayer() calls, but no more.
+ assertNull(state);
+ latch.countDown();
+ }, sHandler);
+ mSession.setPlayer(player);
+ mSession.setPlayer(mPlayer);
+ });
+ player.notifyPlaybackState(createPlaybackState(PlaybackState.STATE_PAUSED));
+ assertFalse(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ */
+ }
+
+ private static class BadPlayer extends MockPlayer {
+ public BadPlayer(int count) {
+ super(count);
+ }
+
+ @Override
+ public void removePlaybackListener(@NonNull PlaybackListener listener) {
+ // No-op. This bad player will keep push notification to the listener that is previously
+ // registered by session.setPlayer().
+ }
+ }
+
+ @Test
+ public void testOnCommandCallback() throws InterruptedException {
+ final MockOnCommandCallback callback = new MockOnCommandCallback();
+ sHandler.postAndSync(() -> {
+ mSession.close();
+ mPlayer = new MockPlayer(1);
+ mSession = new MediaSession2.Builder(mContext, mPlayer)
+ .setSessionCallback(sHandlerExecutor, callback).build();
+ });
+ MediaController2 controller = createController(mSession.getToken());
+ controller.pause();
+ assertFalse(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ assertFalse(mPlayer.mPauseCalled);
+ assertEquals(1, callback.commands.size());
+ assertEquals(MediaSession2.COMMAND_CODE_PLAYBACK_PAUSE,
+ (long) callback.commands.get(0).getCommandCode());
+ controller.skipToNext();
+ assertTrue(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ assertTrue(mPlayer.mSkipToNextCalled);
+ assertFalse(mPlayer.mPauseCalled);
+ assertEquals(2, callback.commands.size());
+ assertEquals(MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_NEXT_ITEM,
+ (long) callback.commands.get(1).getCommandCode());
+ }
+
+ @Test
+ public void testOnConnectCallback() throws InterruptedException {
+ final MockOnConnectCallback sessionCallback = new MockOnConnectCallback();
+ sHandler.postAndSync(() -> {
+ mSession.close();
+ mSession = new MediaSession2.Builder(mContext, mPlayer)
+ .setSessionCallback(sHandlerExecutor, sessionCallback).build();
+ });
+ MediaController2 controller =
+ createController(mSession.getToken(), false, null);
+ assertNotNull(controller);
+ waitForConnect(controller, false);
+ waitForDisconnect(controller, true);
+ }
+
+ public class MockOnConnectCallback extends SessionCallback {
+ @Override
+ public MediaSession2.CommandGroup onConnect(ControllerInfo controllerInfo) {
+ if (Process.myUid() != controllerInfo.getUid()) {
+ return null;
+ }
+ assertEquals(mContext.getPackageName(), controllerInfo.getPackageName());
+ assertEquals(Process.myUid(), controllerInfo.getUid());
+ assertFalse(controllerInfo.isTrusted());
+ // Reject all
+ return null;
+ }
+ }
+
+ public class MockOnCommandCallback extends SessionCallback {
+ public final ArrayList<MediaSession2.Command> commands = new ArrayList<>();
+
+ @Override
+ public boolean onCommandRequest(ControllerInfo controllerInfo, MediaSession2.Command command) {
+ assertEquals(mContext.getPackageName(), controllerInfo.getPackageName());
+ assertEquals(Process.myUid(), controllerInfo.getUid());
+ assertFalse(controllerInfo.isTrusted());
+ commands.add(command);
+ if (command.getCommandCode() == MediaSession2.COMMAND_CODE_PLAYBACK_PAUSE) {
+ return false;
+ }
+ return true;
+ }
+ }
+}
diff --git a/packages/MediaComponents/test/src/android/media/MediaSession2TestBase.java b/packages/MediaComponents/test/src/android/media/MediaSession2TestBase.java
new file mode 100644
index 0000000..7834a42
--- /dev/null
+++ b/packages/MediaComponents/test/src/android/media/MediaSession2TestBase.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import android.content.Context;
+import android.media.MediaController2.ControllerCallback;
+import android.media.MediaSession2.CommandGroup;
+import android.os.Bundle;
+import android.os.HandlerThread;
+import android.support.annotation.CallSuper;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.test.InstrumentationRegistry;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+
+/**
+ * Base class for session test.
+ */
+abstract class MediaSession2TestBase {
+ // Expected success
+ static final int WAIT_TIME_MS = 1000;
+
+ // Expected timeout
+ static final int TIMEOUT_MS = 500;
+
+ static TestUtils.SyncHandler sHandler;
+ static Executor sHandlerExecutor;
+
+ Context mContext;
+ private List<MediaController2> mControllers = new ArrayList<>();
+
+ interface TestControllerInterface {
+ ControllerCallback getCallback();
+ }
+
+ interface TestControllerCallbackInterface {
+ // Currently empty. Add methods in ControllerCallback/BrowserCallback that you want to test.
+
+ // Browser specific callbacks
+ default void onGetRootResult(Bundle rootHints, String rootMediaId, Bundle rootExtra) {}
+ }
+
+ interface WaitForConnectionInterface {
+ void waitForConnect(boolean expect) throws InterruptedException;
+ void waitForDisconnect(boolean expect) throws InterruptedException;
+ }
+
+ @BeforeClass
+ public static void setUpThread() {
+ if (sHandler == null) {
+ HandlerThread handlerThread = new HandlerThread("MediaSession2TestBase");
+ handlerThread.start();
+ sHandler = new TestUtils.SyncHandler(handlerThread.getLooper());
+ sHandlerExecutor = (runnable) -> {
+ sHandler.post(runnable);
+ };
+ }
+ }
+
+ @AfterClass
+ public static void cleanUpThread() {
+ if (sHandler != null) {
+ sHandler.getLooper().quitSafely();
+ sHandler = null;
+ sHandlerExecutor = null;
+ }
+ }
+
+ @CallSuper
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getTargetContext();
+ }
+
+ @CallSuper
+ public void cleanUp() throws Exception {
+ for (int i = 0; i < mControllers.size(); i++) {
+ mControllers.get(i).close();
+ }
+ }
+
+ final MediaController2 createController(SessionToken2 token) throws InterruptedException {
+ return createController(token, true, null);
+ }
+
+ final MediaController2 createController(@NonNull SessionToken2 token,
+ boolean waitForConnect, @Nullable TestControllerCallbackInterface callback)
+ throws InterruptedException {
+ TestControllerInterface instance = onCreateController(token, callback);
+ if (!(instance instanceof MediaController2)) {
+ throw new RuntimeException("Test has a bug. Expected MediaController2 but returned "
+ + instance);
+ }
+ MediaController2 controller = (MediaController2) instance;
+ mControllers.add(controller);
+ if (waitForConnect) {
+ waitForConnect(controller, true);
+ }
+ return controller;
+ }
+
+ private static WaitForConnectionInterface getWaitForConnectionInterface(
+ MediaController2 controller) {
+ if (!(controller instanceof TestControllerInterface)) {
+ throw new RuntimeException("Test has a bug. Expected controller implemented"
+ + " TestControllerInterface but got " + controller);
+ }
+ ControllerCallback callback = ((TestControllerInterface) controller).getCallback();
+ if (!(callback instanceof WaitForConnectionInterface)) {
+ throw new RuntimeException("Test has a bug. Expected controller with callback "
+ + " implemented WaitForConnectionInterface but got " + controller);
+ }
+ return (WaitForConnectionInterface) callback;
+ }
+
+ public static void waitForConnect(MediaController2 controller, boolean expected)
+ throws InterruptedException {
+ getWaitForConnectionInterface(controller).waitForConnect(expected);
+ }
+
+ public static void waitForDisconnect(MediaController2 controller, boolean expected)
+ throws InterruptedException {
+ getWaitForConnectionInterface(controller).waitForDisconnect(expected);
+ }
+
+ TestControllerInterface onCreateController(@NonNull SessionToken2 token,
+ @NonNull TestControllerCallbackInterface callback) {
+ return new TestMediaController(mContext, token, new TestControllerCallback(callback));
+ }
+
+ public static class TestControllerCallback extends MediaController2.ControllerCallback
+ implements WaitForConnectionInterface {
+ public final TestControllerCallbackInterface mCallbackProxy;
+ public final CountDownLatch connectLatch = new CountDownLatch(1);
+ public final CountDownLatch disconnectLatch = new CountDownLatch(1);
+
+ TestControllerCallback(TestControllerCallbackInterface callbackProxy) {
+ mCallbackProxy = callbackProxy;
+ }
+
+ @CallSuper
+ @Override
+ public void onConnected(CommandGroup commands) {
+ super.onConnected(commands);
+ connectLatch.countDown();
+ }
+
+ @CallSuper
+ @Override
+ public void onDisconnected() {
+ super.onDisconnected();
+ disconnectLatch.countDown();
+ }
+
+ @Override
+ public void waitForConnect(boolean expect) throws InterruptedException {
+ if (expect) {
+ assertTrue(connectLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ } else {
+ assertFalse(connectLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ }
+ }
+
+ @Override
+ public void waitForDisconnect(boolean expect) throws InterruptedException {
+ if (expect) {
+ assertTrue(disconnectLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ } else {
+ assertFalse(disconnectLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ }
+ }
+ }
+
+ public class TestMediaController extends MediaController2 implements TestControllerInterface {
+ private final ControllerCallback mCallback;
+
+ public TestMediaController(@NonNull Context context, @NonNull SessionToken2 token,
+ @NonNull ControllerCallback callback) {
+ super(context, token, sHandlerExecutor, callback);
+ mCallback = callback;
+ }
+
+ @Override
+ public ControllerCallback getCallback() {
+ return mCallback;
+ }
+ }
+}
diff --git a/packages/MediaComponents/test/src/android/media/MediaSessionManager_MediaSession2.java b/packages/MediaComponents/test/src/android/media/MediaSessionManager_MediaSession2.java
new file mode 100644
index 0000000..192cbc2
--- /dev/null
+++ b/packages/MediaComponents/test/src/android/media/MediaSessionManager_MediaSession2.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.content.Context;
+import android.media.MediaSession2.ControllerInfo;
+import android.media.MediaSession2.SessionCallback;
+import android.media.session.MediaSessionManager;
+import android.media.session.PlaybackState;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import static android.media.TestUtils.createPlaybackState;
+import static org.junit.Assert.*;
+
+/**
+ * Tests {@link MediaSessionManager} with {@link MediaSession2} specific APIs.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Ignore
+// TODO(jaewan): Reenable test when the media session service detects newly installed sesison
+// service app.
+public class MediaSessionManager_MediaSession2 extends MediaSession2TestBase {
+ private static final String TAG = "MediaSessionManager_MediaSession2";
+
+ private MediaSessionManager mManager;
+ private MediaSession2 mSession;
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ mManager = (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
+
+ // Specify TAG here so {@link MediaSession2.getInstance()} doesn't complaint about
+ // per test thread differs across the {@link MediaSession2} with the same TAG.
+ final MockPlayer player = new MockPlayer(1);
+ sHandler.postAndSync(() -> {
+ mSession = new MediaSession2.Builder(mContext, player).setId(TAG).build();
+ });
+ ensureChangeInSession();
+ }
+
+ @After
+ @Override
+ public void cleanUp() throws Exception {
+ super.cleanUp();
+ sHandler.removeCallbacksAndMessages(null);
+ sHandler.postAndSync(() -> {
+ mSession.close();
+ });
+ }
+
+ // TODO(jaewan): Make this host-side test to see per-user behavior.
+ @Test
+ public void testGetMediaSession2Tokens_hasMediaController() throws InterruptedException {
+ final MockPlayer player = (MockPlayer) mSession.getPlayer();
+ player.notifyPlaybackState(createPlaybackState(PlaybackState.STATE_STOPPED));
+
+ MediaController2 controller = null;
+ List<SessionToken2> tokens = mManager.getActiveSessionTokens();
+ assertNotNull(tokens);
+ for (int i = 0; i < tokens.size(); i++) {
+ SessionToken2 token = tokens.get(i);
+ if (mContext.getPackageName().equals(token.getPackageName())
+ && TAG.equals(token.getId())) {
+ assertNotNull(token.getSessionBinder());
+ assertNull(controller);
+ controller = createController(token);
+ }
+ }
+ assertNotNull(controller);
+
+ // Test if the found controller is correct one.
+ assertEquals(PlaybackState.STATE_STOPPED, controller.getPlaybackState().getState());
+ controller.play();
+
+ assertTrue(player.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ assertTrue(player.mPlayCalled);
+ }
+
+ /**
+ * Test if server recognizes session even if session refuses the connection from server.
+ *
+ * @throws InterruptedException
+ */
+ @Test
+ public void testGetSessionTokens_sessionRejected() throws InterruptedException {
+ sHandler.postAndSync(() -> {
+ mSession.close();
+ mSession = new MediaSession2.Builder(mContext, new MockPlayer(0)).setId(TAG)
+ .setSessionCallback(sHandlerExecutor, new SessionCallback() {
+ @Override
+ public MediaSession2.CommandGroup onConnect(ControllerInfo controller) {
+ // Reject all connection request.
+ return null;
+ }
+ }).build();
+ });
+ ensureChangeInSession();
+
+ boolean foundSession = false;
+ List<SessionToken2> tokens = mManager.getActiveSessionTokens();
+ assertNotNull(tokens);
+ for (int i = 0; i < tokens.size(); i++) {
+ SessionToken2 token = tokens.get(i);
+ if (mContext.getPackageName().equals(token.getPackageName())
+ && TAG.equals(token.getId())) {
+ assertFalse(foundSession);
+ foundSession = true;
+ }
+ }
+ assertTrue(foundSession);
+ }
+
+ @Test
+ public void testGetMediaSession2Tokens_playerRemoved() throws InterruptedException {
+ // Release
+ sHandler.postAndSync(() -> {
+ mSession.close();
+ });
+ ensureChangeInSession();
+
+ // When the mSession's player becomes null, it should lose binder connection between server.
+ // So server will forget the session.
+ List<SessionToken2> tokens = mManager.getActiveSessionTokens();
+ for (int i = 0; i < tokens.size(); i++) {
+ SessionToken2 token = tokens.get(i);
+ assertFalse(mContext.getPackageName().equals(token.getPackageName())
+ && TAG.equals(token.getId()));
+ }
+ }
+
+ @Test
+ public void testGetMediaSessionService2Token() throws InterruptedException {
+ boolean foundTestSessionService = false;
+ boolean foundTestLibraryService = false;
+ List<SessionToken2> tokens = mManager.getSessionServiceTokens();
+ for (int i = 0; i < tokens.size(); i++) {
+ SessionToken2 token = tokens.get(i);
+ if (mContext.getPackageName().equals(token.getPackageName())
+ && MockMediaSessionService2.ID.equals(token.getId())) {
+ assertFalse(foundTestSessionService);
+ assertEquals(SessionToken2.TYPE_SESSION_SERVICE, token.getType());
+ assertNull(token.getSessionBinder());
+ foundTestSessionService = true;
+ } else if (mContext.getPackageName().equals(token.getPackageName())
+ && MockMediaLibraryService2.ID.equals(token.getId())) {
+ assertFalse(foundTestLibraryService);
+ assertEquals(SessionToken2.TYPE_LIBRARY_SERVICE, token.getType());
+ assertNull(token.getSessionBinder());
+ foundTestLibraryService = true;
+ }
+ }
+ assertTrue(foundTestSessionService);
+ assertTrue(foundTestLibraryService);
+ }
+
+ @Test
+ public void testGetAllSessionTokens() throws InterruptedException {
+ boolean foundTestSession = false;
+ boolean foundTestSessionService = false;
+ boolean foundTestLibraryService = false;
+ List<SessionToken2> tokens = mManager.getAllSessionTokens();
+ for (int i = 0; i < tokens.size(); i++) {
+ SessionToken2 token = tokens.get(i);
+ if (!mContext.getPackageName().equals(token.getPackageName())) {
+ continue;
+ }
+ switch (token.getId()) {
+ case TAG:
+ assertFalse(foundTestSession);
+ foundTestSession = true;
+ break;
+ case MockMediaSessionService2.ID:
+ assertFalse(foundTestSessionService);
+ foundTestSessionService = true;
+ assertEquals(SessionToken2.TYPE_SESSION_SERVICE, token.getType());
+ break;
+ case MockMediaLibraryService2.ID:
+ assertFalse(foundTestLibraryService);
+ assertEquals(SessionToken2.TYPE_LIBRARY_SERVICE, token.getType());
+ foundTestLibraryService = true;
+ break;
+ default:
+ fail("Unexpected session " + token + " exists in the package");
+ }
+ }
+ assertTrue(foundTestSession);
+ assertTrue(foundTestSessionService);
+ assertTrue(foundTestLibraryService);
+ }
+
+ // Ensures if the session creation/release is notified to the server.
+ private void ensureChangeInSession() throws InterruptedException {
+ // TODO(jaewan): Wait by listener.
+ Thread.sleep(WAIT_TIME_MS);
+ }
+}
diff --git a/packages/MediaComponents/test/src/android/media/MockMediaLibraryService2.java b/packages/MediaComponents/test/src/android/media/MockMediaLibraryService2.java
new file mode 100644
index 0000000..14cf257
--- /dev/null
+++ b/packages/MediaComponents/test/src/android/media/MockMediaLibraryService2.java
@@ -0,0 +1,98 @@
+/*
+* Copyright 2018 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package android.media;
+
+import static junit.framework.Assert.fail;
+
+import android.content.Context;
+import android.media.MediaSession2.CommandGroup;
+import android.media.MediaSession2.ControllerInfo;
+import android.media.TestUtils.SyncHandler;
+import android.os.Bundle;
+import android.os.Process;
+
+import javax.annotation.concurrent.GuardedBy;
+
+/**
+ * Mock implementation of {@link MediaLibraryService2} for testing.
+ */
+public class MockMediaLibraryService2 extends MediaLibraryService2 {
+ // Keep in sync with the AndroidManifest.xml
+ public static final String ID = "TestLibrary";
+
+ public static final String ROOT_ID = "rootId";
+ public static final Bundle EXTRA = new Bundle();
+ static {
+ EXTRA.putString(ROOT_ID, ROOT_ID);
+ }
+ @GuardedBy("MockMediaLibraryService2.class")
+ private static SessionToken2 sToken;
+
+ private MediaLibrarySession mSession;
+
+ @Override
+ public MediaLibrarySession onCreateSession(String sessionId) {
+ final MockPlayer player = new MockPlayer(1);
+ final SyncHandler handler = (SyncHandler) TestServiceRegistry.getInstance().getHandler();
+ try {
+ handler.postAndSync(() -> {
+ TestLibrarySessionCallback callback = new TestLibrarySessionCallback();
+ mSession = new MediaLibrarySessionBuilder(MockMediaLibraryService2.this,
+ player, (runnable) -> handler.post(runnable), callback)
+ .setId(sessionId).build();
+ });
+ } catch (InterruptedException e) {
+ fail(e.toString());
+ }
+ return mSession;
+ }
+
+ @Override
+ public void onDestroy() {
+ TestServiceRegistry.getInstance().cleanUp();
+ super.onDestroy();
+ }
+
+ public static SessionToken2 getToken(Context context) {
+ synchronized (MockMediaLibraryService2.class) {
+ if (sToken == null) {
+ sToken = new SessionToken2(SessionToken2.TYPE_LIBRARY_SERVICE,
+ context.getPackageName(), ID,
+ MockMediaLibraryService2.class.getName(), null);
+ }
+ return sToken;
+ }
+ }
+
+ private class TestLibrarySessionCallback extends MediaLibrarySessionCallback {
+ @Override
+ public CommandGroup onConnect(ControllerInfo controller) {
+ if (Process.myUid() != controller.getUid()) {
+ // It's system app wants to listen changes. Ignore.
+ return super.onConnect(controller);
+ }
+ TestServiceRegistry.getInstance().setServiceInstance(
+ MockMediaLibraryService2.this, controller);
+ return super.onConnect(controller);
+ }
+
+ @Override
+ public BrowserRoot onGetRoot(ControllerInfo controller, Bundle rootHints) {
+ return new BrowserRoot(ROOT_ID, EXTRA);
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/MediaComponents/test/src/android/media/MockMediaSessionService2.java b/packages/MediaComponents/test/src/android/media/MockMediaSessionService2.java
new file mode 100644
index 0000000..b058117
--- /dev/null
+++ b/packages/MediaComponents/test/src/android/media/MockMediaSessionService2.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import static junit.framework.Assert.fail;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.media.MediaSession2.ControllerInfo;
+import android.media.MediaSession2.SessionCallback;
+import android.media.TestUtils.SyncHandler;
+import android.media.session.PlaybackState;
+import android.os.Process;
+
+/**
+ * Mock implementation of {@link android.media.MediaSessionService2} for testing.
+ */
+public class MockMediaSessionService2 extends MediaSessionService2 {
+ // Keep in sync with the AndroidManifest.xml
+ public static final String ID = "TestSession";
+
+ private static final String DEFAULT_MEDIA_NOTIFICATION_CHANNEL_ID = "media_session_service";
+ private static final int DEFAULT_MEDIA_NOTIFICATION_ID = 1001;
+
+ private NotificationChannel mDefaultNotificationChannel;
+ private MediaSession2 mSession;
+ private NotificationManager mNotificationManager;
+
+ @Override
+ public MediaSession2 onCreateSession(String sessionId) {
+ final MockPlayer player = new MockPlayer(1);
+ final SyncHandler handler = (SyncHandler) TestServiceRegistry.getInstance().getHandler();
+ try {
+ handler.postAndSync(() -> {
+ mSession = new MediaSession2.Builder(MockMediaSessionService2.this, player)
+ .setId(sessionId).setSessionCallback((runnable)->handler.post(runnable),
+ new MySessionCallback()).build();
+ });
+ } catch (InterruptedException e) {
+ fail(e.toString());
+ }
+ return mSession;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ }
+
+ @Override
+ public void onDestroy() {
+ TestServiceRegistry.getInstance().cleanUp();
+ super.onDestroy();
+ }
+
+ @Override
+ public MediaNotification onUpdateNotification(PlaybackState2 state) {
+ if (mDefaultNotificationChannel == null) {
+ mDefaultNotificationChannel = new NotificationChannel(
+ DEFAULT_MEDIA_NOTIFICATION_CHANNEL_ID,
+ DEFAULT_MEDIA_NOTIFICATION_CHANNEL_ID,
+ NotificationManager.IMPORTANCE_DEFAULT);
+ mNotificationManager.createNotificationChannel(mDefaultNotificationChannel);
+ }
+ Notification notification = new Notification.Builder(
+ this, DEFAULT_MEDIA_NOTIFICATION_CHANNEL_ID)
+ .setContentTitle(getPackageName())
+ .setContentText("Playback state: " + state.getState())
+ .setSmallIcon(android.R.drawable.sym_def_app_icon).build();
+ return MediaNotification.create(DEFAULT_MEDIA_NOTIFICATION_ID, notification);
+ }
+
+ private class MySessionCallback extends SessionCallback {
+ @Override
+ public MediaSession2.CommandGroup onConnect(ControllerInfo controller) {
+ if (Process.myUid() != controller.getUid()) {
+ // It's system app wants to listen changes. Ignore.
+ return super.onConnect(controller);
+ }
+ TestServiceRegistry.getInstance().setServiceInstance(
+ MockMediaSessionService2.this, controller);
+ return super.onConnect(controller);
+ }
+ }
+}
diff --git a/packages/MediaComponents/test/src/android/media/MockPlayer.java b/packages/MediaComponents/test/src/android/media/MockPlayer.java
new file mode 100644
index 0000000..ad7ba2f
--- /dev/null
+++ b/packages/MediaComponents/test/src/android/media/MockPlayer.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.media.MediaSession2.PlaylistParams;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+
+/**
+ * A mock implementation of {@link MediaPlayerBase} for testing.
+ */
+public class MockPlayer extends MediaPlayerBase {
+ public final CountDownLatch mCountDownLatch;
+
+ public boolean mPlayCalled;
+ public boolean mPauseCalled;
+ public boolean mStopCalled;
+ public boolean mSkipToPreviousCalled;
+ public boolean mSkipToNextCalled;
+ public List<PlaybackListenerHolder> mListeners = new ArrayList<>();
+ private PlaybackState2 mLastPlaybackState;
+
+ public MockPlayer(int count) {
+ mCountDownLatch = (count > 0) ? new CountDownLatch(count) : null;
+ }
+
+ @Override
+ public void play() {
+ mPlayCalled = true;
+ if (mCountDownLatch != null) {
+ mCountDownLatch.countDown();
+ }
+ }
+
+ @Override
+ public void pause() {
+ mPauseCalled = true;
+ if (mCountDownLatch != null) {
+ mCountDownLatch.countDown();
+ }
+ }
+
+ @Override
+ public void stop() {
+ mStopCalled = true;
+ if (mCountDownLatch != null) {
+ mCountDownLatch.countDown();
+ }
+ }
+
+ @Override
+ public void skipToPrevious() {
+ mSkipToPreviousCalled = true;
+ if (mCountDownLatch != null) {
+ mCountDownLatch.countDown();
+ }
+ }
+
+ @Override
+ public void skipToNext() {
+ mSkipToNextCalled = true;
+ if (mCountDownLatch != null) {
+ mCountDownLatch.countDown();
+ }
+ }
+
+
+
+ @Nullable
+ @Override
+ public PlaybackState2 getPlaybackState() {
+ return mLastPlaybackState;
+ }
+
+ @Override
+ public void addPlaybackListener(@NonNull Executor executor,
+ @NonNull PlaybackListener listener) {
+ mListeners.add(new PlaybackListenerHolder(executor, listener));
+ }
+
+ @Override
+ public void removePlaybackListener(@NonNull PlaybackListener listener) {
+ int index = PlaybackListenerHolder.indexOf(mListeners, listener);
+ if (index >= 0) {
+ mListeners.remove(index);
+ }
+ }
+
+ public void notifyPlaybackState(final PlaybackState2 state) {
+ mLastPlaybackState = state;
+ for (int i = 0; i < mListeners.size(); i++) {
+ mListeners.get(i).postPlaybackChange(state);
+ }
+ }
+
+ // No-op. Should be added for test later.
+ @Override
+ public void prepare() {
+ }
+
+ @Override
+ public void seekTo(long pos) {
+ }
+
+ @Override
+ public void fastFoward() {
+ }
+
+ @Override
+ public void rewind() {
+ }
+
+ @Override
+ public AudioAttributes getAudioAttributes() {
+ return null;
+ }
+
+ @Override
+ public void setPlaylist(List<MediaItem2> item, PlaylistParams param) {
+ }
+
+ @Override
+ public void setCurrentPlaylistItem(int index) {
+ }
+}
diff --git a/packages/MediaComponents/test/src/android/media/PlaybackListenerHolder.java b/packages/MediaComponents/test/src/android/media/PlaybackListenerHolder.java
new file mode 100644
index 0000000..4e19d4d
--- /dev/null
+++ b/packages/MediaComponents/test/src/android/media/PlaybackListenerHolder.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.media.MediaPlayerBase.PlaybackListener;
+import android.media.session.PlaybackState;
+import android.os.Handler;
+import android.os.Message;
+import android.support.annotation.NonNull;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * Holds {@link PlaybackListener} with the {@link Handler}.
+ */
+public class PlaybackListenerHolder {
+ public final Executor executor;
+ public final PlaybackListener listener;
+
+ public PlaybackListenerHolder(Executor executor, @NonNull PlaybackListener listener) {
+ this.executor = executor;
+ this.listener = listener;
+ }
+
+ public void postPlaybackChange(final PlaybackState2 state) {
+ executor.execute(() -> listener.onPlaybackChanged(state));
+ }
+
+ /**
+ * Returns {@code true} if the given list contains a {@link PlaybackListenerHolder} that holds
+ * the given listener.
+ *
+ * @param list list to check
+ * @param listener listener to check
+ * @return {@code true} if the given list contains listener. {@code false} otherwise.
+ */
+ public static <Holder extends PlaybackListenerHolder> boolean contains(
+ @NonNull List<Holder> list, PlaybackListener listener) {
+ return indexOf(list, listener) >= 0;
+ }
+
+ /**
+ * Returns the index of the {@link PlaybackListenerHolder} that contains the given listener.
+ *
+ * @param list list to check
+ * @param listener listener to check
+ * @return {@code index} of item if the given list contains listener. {@code -1} otherwise.
+ */
+ public static <Holder extends PlaybackListenerHolder> int indexOf(
+ @NonNull List<Holder> list, PlaybackListener listener) {
+ for (int i = 0; i < list.size(); i++) {
+ if (list.get(i).listener == listener) {
+ return i;
+ }
+ }
+ return -1;
+ }
+}
diff --git a/packages/MediaComponents/test/src/android/media/TestServiceRegistry.java b/packages/MediaComponents/test/src/android/media/TestServiceRegistry.java
new file mode 100644
index 0000000..6f5512e
--- /dev/null
+++ b/packages/MediaComponents/test/src/android/media/TestServiceRegistry.java
@@ -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.
+ */
+
+package android.media;
+
+import static org.junit.Assert.fail;
+
+import android.media.MediaSession2.ControllerInfo;
+import android.media.TestUtils.SyncHandler;
+import android.os.Handler;
+import android.os.Looper;
+import android.support.annotation.GuardedBy;
+
+/**
+ * Keeps the instance of currently running {@link MockMediaSessionService2}. And also provides
+ * a way to control them in one place.
+ * <p>
+ * It only support only one service at a time.
+ */
+public class TestServiceRegistry {
+ public interface ServiceInstanceChangedCallback {
+ void OnServiceInstanceChanged(MediaSessionService2 service);
+ }
+
+ @GuardedBy("TestServiceRegistry.class")
+ private static TestServiceRegistry sInstance;
+ @GuardedBy("TestServiceRegistry.class")
+ private MediaSessionService2 mService;
+ @GuardedBy("TestServiceRegistry.class")
+ private SyncHandler mHandler;
+ @GuardedBy("TestServiceRegistry.class")
+ private ControllerInfo mOnConnectControllerInfo;
+ @GuardedBy("TestServiceRegistry.class")
+ private ServiceInstanceChangedCallback mCallback;
+
+ public static TestServiceRegistry getInstance() {
+ synchronized (TestServiceRegistry.class) {
+ if (sInstance == null) {
+ sInstance = new TestServiceRegistry();
+ }
+ return sInstance;
+ }
+ }
+
+ public void setHandler(Handler handler) {
+ synchronized (TestServiceRegistry.class) {
+ mHandler = new SyncHandler(handler.getLooper());
+ }
+ }
+
+ public void setServiceInstanceChangedCallback(ServiceInstanceChangedCallback callback) {
+ synchronized (TestServiceRegistry.class) {
+ mCallback = callback;
+ }
+ }
+
+ public Handler getHandler() {
+ synchronized (TestServiceRegistry.class) {
+ return mHandler;
+ }
+ }
+
+ public void setServiceInstance(MediaSessionService2 service, ControllerInfo controller) {
+ synchronized (TestServiceRegistry.class) {
+ if (mService != null) {
+ fail("Previous service instance is still running. Clean up manually to ensure"
+ + " previoulsy running service doesn't break current test");
+ }
+ mService = service;
+ mOnConnectControllerInfo = controller;
+ if (mCallback != null) {
+ mCallback.OnServiceInstanceChanged(service);
+ }
+ }
+ }
+
+ public MediaSessionService2 getServiceInstance() {
+ synchronized (TestServiceRegistry.class) {
+ return mService;
+ }
+ }
+
+ public ControllerInfo getOnConnectControllerInfo() {
+ synchronized (TestServiceRegistry.class) {
+ return mOnConnectControllerInfo;
+ }
+ }
+
+
+ public void cleanUp() {
+ synchronized (TestServiceRegistry.class) {
+ final ServiceInstanceChangedCallback callback = mCallback;
+ if (mService != null) {
+ try {
+ if (mHandler.getLooper() == Looper.myLooper()) {
+ mService.getSession().close();
+ } else {
+ mHandler.postAndSync(() -> {
+ mService.getSession().close();
+ });
+ }
+ } catch (InterruptedException e) {
+ // No-op. Service containing session will die, but shouldn't be a huge issue.
+ }
+ // stopSelf() would not kill service while the binder connection established by
+ // bindService() exists, and close() above will do the job instead.
+ // So stopSelf() isn't really needed, but just for sure.
+ mService.stopSelf();
+ mService = null;
+ }
+ if (mHandler != null) {
+ mHandler.removeCallbacksAndMessages(null);
+ }
+ mCallback = null;
+ mOnConnectControllerInfo = null;
+
+ if (callback != null) {
+ callback.OnServiceInstanceChanged(null);
+ }
+ }
+ }
+}
diff --git a/packages/MediaComponents/test/src/android/media/TestUtils.java b/packages/MediaComponents/test/src/android/media/TestUtils.java
new file mode 100644
index 0000000..9a1fa10
--- /dev/null
+++ b/packages/MediaComponents/test/src/android/media/TestUtils.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.content.Context;
+import android.media.session.MediaSessionManager;
+import android.media.session.PlaybackState;
+import android.os.Bundle;
+import android.os.Handler;
+
+import android.os.Looper;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Utilities for tests.
+ */
+public final class TestUtils {
+ private static final int WAIT_TIME_MS = 1000;
+ private static final int WAIT_SERVICE_TIME_MS = 5000;
+
+ /**
+ * Creates a {@link android.media.session.PlaybackState} with the given state.
+ *
+ * @param state one of the PlaybackState.STATE_xxx.
+ * @return a PlaybackState
+ */
+ public static PlaybackState2 createPlaybackState(int state) {
+ return new PlaybackState2(state, 0, 0, 1.0f,
+ 0, 0, null);
+ }
+
+ /**
+ * Finds the session with id in this test package.
+ *
+ * @param context
+ * @param id
+ * @return
+ */
+ // TODO(jaewan): Currently not working.
+ public static SessionToken2 getServiceToken(Context context, String id) {
+ MediaSessionManager manager =
+ (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);
+ List<SessionToken2> tokens = manager.getSessionServiceTokens();
+ for (int i = 0; i < tokens.size(); i++) {
+ SessionToken2 token = tokens.get(i);
+ if (context.getPackageName().equals(token.getPackageName())
+ && id.equals(token.getId())) {
+ return token;
+ }
+ }
+ fail("Failed to find service");
+ return null;
+ }
+
+ /**
+ * Compares contents of two bundles.
+ *
+ * @param a a bundle
+ * @param b another bundle
+ * @return {@code true} if two bundles are the same. {@code false} otherwise. This may be
+ * incorrect if any bundle contains a bundle.
+ */
+ public static boolean equals(Bundle a, Bundle b) {
+ if (a == b) {
+ return true;
+ }
+ if (a == null || b == null) {
+ return false;
+ }
+ if (!a.keySet().containsAll(b.keySet())
+ || !b.keySet().containsAll(a.keySet())) {
+ return false;
+ }
+ for (String key : a.keySet()) {
+ if (!Objects.equals(a.get(key), b.get(key))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Handler that always waits until the Runnable finishes.
+ */
+ public static class SyncHandler extends Handler {
+ public SyncHandler(Looper looper) {
+ super(looper);
+ }
+
+ public void postAndSync(Runnable runnable) throws InterruptedException {
+ final CountDownLatch latch = new CountDownLatch(1);
+ if (getLooper() == Looper.myLooper()) {
+ runnable.run();
+ } else {
+ post(()->{
+ runnable.run();
+ latch.countDown();
+ });
+ assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ }
+ }
+ }
+}
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index 4d5e094..113744f 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -155,6 +155,8 @@
mBtNrecIsOff(false),
mIsLowRamDevice(true),
mIsDeviceTypeKnown(false),
+ mTotalMemory(0),
+ mClientSharedHeapSize(kMinimumClientSharedHeapSizeBytes),
mGlobalEffectEnableTime(0),
mSystemReady(false)
{
@@ -262,6 +264,7 @@
audio_config_base_t *config,
const AudioClient& client,
audio_port_handle_t *deviceId,
+ audio_session_t *sessionId,
const sp<MmapStreamCallback>& callback,
sp<MmapStreamInterface>& interface,
audio_port_handle_t *handle)
@@ -274,7 +277,8 @@
status_t ret = NO_INIT;
if (af != 0) {
ret = af->openMmapStream(
- direction, attr, config, client, deviceId, callback, interface, handle);
+ direction, attr, config, client, deviceId,
+ sessionId, callback, interface, handle);
}
return ret;
}
@@ -284,6 +288,7 @@
audio_config_base_t *config,
const AudioClient& client,
audio_port_handle_t *deviceId,
+ audio_session_t *sessionId,
const sp<MmapStreamCallback>& callback,
sp<MmapStreamInterface>& interface,
audio_port_handle_t *handle)
@@ -292,8 +297,10 @@
if (ret != NO_ERROR) {
return ret;
}
-
- audio_session_t sessionId = (audio_session_t) newAudioUniqueId(AUDIO_UNIQUE_ID_USE_SESSION);
+ audio_session_t actualSessionId = *sessionId;
+ if (actualSessionId == AUDIO_SESSION_ALLOCATE) {
+ actualSessionId = (audio_session_t) newAudioUniqueId(AUDIO_UNIQUE_ID_USE_SESSION);
+ }
audio_stream_type_t streamType = AUDIO_STREAM_DEFAULT;
audio_io_handle_t io = AUDIO_IO_HANDLE_NONE;
audio_port_handle_t portId = AUDIO_PORT_HANDLE_NONE;
@@ -303,17 +310,18 @@
fullConfig.channel_mask = config->channel_mask;
fullConfig.format = config->format;
ret = AudioSystem::getOutputForAttr(attr, &io,
- sessionId,
- &streamType, client.clientUid,
+ actualSessionId,
+ &streamType, client.clientPid, client.clientUid,
&fullConfig,
(audio_output_flags_t)(AUDIO_OUTPUT_FLAG_MMAP_NOIRQ |
AUDIO_OUTPUT_FLAG_DIRECT),
deviceId, &portId);
} else {
ret = AudioSystem::getInputForAttr(attr, &io,
- sessionId,
+ actualSessionId,
client.clientPid,
client.clientUid,
+ client.packageName,
config,
AUDIO_INPUT_FLAG_MMAP_NOIRQ, deviceId, &portId);
}
@@ -326,13 +334,14 @@
sp<MmapThread> thread = mMmapThreads.valueFor(io);
if (thread != 0) {
interface = new MmapThreadHandle(thread);
- thread->configure(attr, streamType, sessionId, callback, *deviceId, portId);
+ thread->configure(attr, streamType, actualSessionId, callback, *deviceId, portId);
*handle = portId;
+ *sessionId = actualSessionId;
} else {
if (direction == MmapStreamInterface::DIRECTION_OUTPUT) {
- AudioSystem::releaseOutput(io, streamType, sessionId);
+ AudioSystem::releaseOutput(io, streamType, actualSessionId);
} else {
- AudioSystem::releaseInput(io, sessionId);
+ AudioSystem::releaseInput(portId);
}
ret = NO_INIT;
}
@@ -684,7 +693,7 @@
output.selectedDeviceId = input.selectedDeviceId;
lStatus = AudioSystem::getOutputForAttr(&input.attr, &output.outputId, sessionId, &streamType,
- clientUid, &input.config, input.flags,
+ clientPid, clientUid, &input.config, input.flags,
&output.selectedDeviceId, &portId);
if (lStatus != NO_ERROR || output.outputId == AUDIO_IO_HANDLE_NONE) {
@@ -987,6 +996,21 @@
return mute;
}
+void AudioFlinger::setRecordSilenced(uid_t uid, bool silenced)
+{
+ ALOGV("AudioFlinger::setRecordSilenced(uid:%d, silenced:%d)", uid, silenced);
+
+ // TODO: Notify MmapThreads
+
+ AutoMutex lock(mLock);
+ for (size_t i = 0; i < mRecordThreads.size(); i++) {
+ sp<RecordThread> thread = mRecordThreads.valueAt(i);
+ if (thread != 0) {
+ thread->setRecordSilenced(uid, silenced);
+ }
+ }
+}
+
status_t AudioFlinger::setMasterMute(bool muted)
{
status_t ret = initCheck();
@@ -1491,17 +1515,9 @@
mAudioFlinger(audioFlinger),
mPid(pid)
{
- size_t heapSize = property_get_int32("ro.af.client_heap_size_kbyte", 0);
- heapSize *= 1024;
- if (!heapSize) {
- heapSize = kClientSharedHeapSizeBytes;
- // Increase heap size on non low ram devices to limit risk of reconnection failure for
- // invalidated tracks
- if (!audioFlinger->isLowRamDevice()) {
- heapSize *= kClientSharedHeapSizeMultiplier;
- }
- }
- mMemoryDealer = new MemoryDealer(heapSize, "AudioFlinger::Client");
+ mMemoryDealer = new MemoryDealer(
+ audioFlinger->getClientSharedHeapSize(),
+ (std::string("AudioFlinger::Client(") + std::to_string(pid) + ")").c_str());
}
// Client destructor must be called with AudioFlinger::mClientLock held
@@ -1606,12 +1622,6 @@
clientPid = callingPid;
}
- // check calling permissions
- if (!recordingAllowed(input.opPackageName, input.clientInfo.clientTid, clientUid)) {
- ALOGE("createRecord() permission denied: recording not allowed");
- lStatus = PERMISSION_DENIED;
- goto Exit;
- }
// we don't yet support anything other than linear PCM
if (!audio_is_valid_format(input.config.format) || !audio_is_linear_pcm(input.config.format)) {
ALOGE("createRecord() invalid format %#x", input.config.format);
@@ -1648,7 +1658,7 @@
// release previously opened input if retrying.
if (output.inputId != AUDIO_IO_HANDLE_NONE) {
recordTrack.clear();
- AudioSystem::releaseInput(output.inputId, sessionId);
+ AudioSystem::releaseInput(portId);
output.inputId = AUDIO_IO_HANDLE_NONE;
}
lStatus = AudioSystem::getInputForAttr(&input.attr, &output.inputId,
@@ -1656,6 +1666,7 @@
// FIXME compare to AudioTrack
clientPid,
clientUid,
+ input.opPackageName,
&input.config,
output.flags, &output.selectedDeviceId, &portId);
@@ -1724,7 +1735,7 @@
}
recordTrack.clear();
if (output.inputId != AUDIO_IO_HANDLE_NONE) {
- AudioSystem::releaseInput(output.inputId, sessionId);
+ AudioSystem::releaseInput(portId);
}
}
@@ -1839,7 +1850,7 @@
// ----------------------------------------------------------------------------
-status_t AudioFlinger::setLowRamDevice(bool isLowRamDevice)
+status_t AudioFlinger::setLowRamDevice(bool isLowRamDevice, int64_t totalMemory)
{
uid_t uid = IPCThreadState::self()->getCallingUid();
if (uid != AID_SYSTEM) {
@@ -1850,10 +1861,43 @@
return INVALID_OPERATION;
}
mIsLowRamDevice = isLowRamDevice;
+ mTotalMemory = totalMemory;
+ // mIsLowRamDevice and mTotalMemory are obtained through ActivityManager;
+ // see ActivityManager.isLowRamDevice() and ActivityManager.getMemoryInfo().
+ // mIsLowRamDevice generally represent devices with less than 1GB of memory,
+ // though actual setting is determined through device configuration.
+ constexpr int64_t GB = 1024 * 1024 * 1024;
+ mClientSharedHeapSize =
+ isLowRamDevice ? kMinimumClientSharedHeapSizeBytes
+ : mTotalMemory < 2 * GB ? 4 * kMinimumClientSharedHeapSizeBytes
+ : mTotalMemory < 3 * GB ? 8 * kMinimumClientSharedHeapSizeBytes
+ : mTotalMemory < 4 * GB ? 16 * kMinimumClientSharedHeapSizeBytes
+ : 32 * kMinimumClientSharedHeapSizeBytes;
mIsDeviceTypeKnown = true;
+
+ // TODO: Cache the client shared heap size in a persistent property.
+ // It's possible that a native process or Java service or app accesses audioserver
+ // after it is registered by system server, but before AudioService updates
+ // the memory info. This would occur immediately after boot or an audioserver
+ // crash and restore. Before update from AudioService, the client would get the
+ // minimum heap size.
+
+ ALOGD("isLowRamDevice:%s totalMemory:%lld mClientSharedHeapSize:%zu",
+ (isLowRamDevice ? "true" : "false"),
+ (long long)mTotalMemory,
+ mClientSharedHeapSize.load());
return NO_ERROR;
}
+size_t AudioFlinger::getClientSharedHeapSize() const
+{
+ size_t heapSizeInBytes = property_get_int32("ro.af.client_heap_size_kbyte", 0) * 1024;
+ if (heapSizeInBytes != 0) { // read-only property overrides all.
+ return heapSizeInBytes;
+ }
+ return mClientSharedHeapSize;
+}
+
audio_hw_sync_t AudioFlinger::getAudioHwSyncForSession(audio_session_t sessionId)
{
Mutex::Autolock _l(mLock);
diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h
index bc73ffd..296f092 100644
--- a/services/audioflinger/AudioFlinger.h
+++ b/services/audioflinger/AudioFlinger.h
@@ -94,12 +94,6 @@
static const nsecs_t kDefaultStandbyTimeInNsecs = seconds(3);
-
-// Max shared memory size for audio tracks and audio records per client process
-static const size_t kClientSharedHeapSizeBytes = 1024*1024;
-// Shared memory size multiplier for non low ram devices
-static const size_t kClientSharedHeapSizeMultiplier = 4;
-
#define INCLUDING_FROM_AUDIOFLINGER_H
class AudioFlinger :
@@ -147,6 +141,8 @@
virtual status_t setMicMute(bool state);
virtual bool getMicMute() const;
+ virtual void setRecordSilenced(uid_t uid, bool silenced);
+
virtual status_t setParameters(audio_io_handle_t ioHandle, const String8& keyValuePairs);
virtual String8 getParameters(audio_io_handle_t ioHandle, const String8& keys) const;
@@ -225,7 +221,7 @@
virtual uint32_t getPrimaryOutputSamplingRate();
virtual size_t getPrimaryOutputFrameCount();
- virtual status_t setLowRamDevice(bool isLowRamDevice);
+ virtual status_t setLowRamDevice(bool isLowRamDevice, int64_t totalMemory) override;
/* List available audio ports and their attributes */
virtual status_t listAudioPorts(unsigned int *num_ports,
@@ -271,6 +267,7 @@
audio_config_base_t *config,
const AudioClient& client,
audio_port_handle_t *deviceId,
+ audio_session_t *sessionId,
const sp<MmapStreamCallback>& callback,
sp<MmapStreamInterface>& interface,
audio_port_handle_t *handle);
@@ -826,15 +823,18 @@
static const size_t kTeeSinkTrackFramesDefault = 0x200000;
#endif
- // This method reads from a variable without mLock, but the variable is updated under mLock. So
- // we might read a stale value, or a value that's inconsistent with respect to other variables.
- // In this case, it's safe because the return value isn't used for making an important decision.
- // The reason we don't want to take mLock is because it could block the caller for a long time.
+ // These methods read variables atomically without mLock,
+ // though the variables are updated with mLock.
bool isLowRamDevice() const { return mIsLowRamDevice; }
+ size_t getClientSharedHeapSize() const;
private:
- bool mIsLowRamDevice;
+ std::atomic<bool> mIsLowRamDevice;
bool mIsDeviceTypeKnown;
+ int64_t mTotalMemory;
+ std::atomic<size_t> mClientSharedHeapSize;
+ static constexpr size_t kMinimumClientSharedHeapSizeBytes = 1024 * 1024; // 1MB
+
nsecs_t mGlobalEffectEnableTime; // when a global effect was last enabled
sp<PatchPanel> mPatchPanel;
diff --git a/services/audioflinger/Configuration.h b/services/audioflinger/Configuration.h
index 6e0f2b6..ede8e3f 100644
--- a/services/audioflinger/Configuration.h
+++ b/services/audioflinger/Configuration.h
@@ -47,6 +47,9 @@
#ifdef FLOAT_EFFECT_CHAIN
// define FLOAT_AUX to process aux effect buffers in float (FLOAT_EFFECT_CHAIN must be defined)
#define FLOAT_AUX
+
+// define MULTICHANNEL_EFFECT_CHAIN to allow multichannel effects (FLOAT_EFFECT_CHAIN defined)
+#define MULTICHANNEL_EFFECT_CHAIN
#endif
#endif // ANDROID_AUDIOFLINGER_CONFIGURATION_H
diff --git a/services/audioflinger/Effects.cpp b/services/audioflinger/Effects.cpp
index b4ff0d6..979290f 100644
--- a/services/audioflinger/Effects.cpp
+++ b/services/audioflinger/Effects.cpp
@@ -26,6 +26,7 @@
#include <system/audio_effects/effect_aec.h>
#include <system/audio_effects/effect_ns.h>
#include <system/audio_effects/effect_visualizer.h>
+#include <audio_utils/channels.h>
#include <audio_utils/primitives.h>
#include <media/AudioEffect.h>
#include <media/audiohal/EffectHalInterface.h>
@@ -292,7 +293,6 @@
return;
}
- // TODO: Implement multichannel effects; here outChannelCount == FCC_2 == 2
const uint32_t inChannelCount =
audio_channel_count_from_out_mask(mConfig.inputCfg.channels);
const uint32_t outChannelCount =
@@ -342,6 +342,7 @@
if (isProcessImplemented()) {
if (auxType) {
// We overwrite the aux input buffer here and clear after processing.
+ // aux input is always mono.
#ifdef FLOAT_EFFECT_CHAIN
if (mSupportsFloat) {
#ifndef FLOAT_AUX
@@ -371,6 +372,28 @@
}
}
#ifdef FLOAT_EFFECT_CHAIN
+ sp<EffectBufferHalInterface> inBuffer = mInBuffer;
+ sp<EffectBufferHalInterface> outBuffer = mOutBuffer;
+
+ if (!auxType && mInChannelCountRequested != inChannelCount) {
+ adjust_channels(
+ inBuffer->audioBuffer()->f32, mInChannelCountRequested,
+ mInConversionBuffer->audioBuffer()->f32, inChannelCount,
+ sizeof(float),
+ sizeof(float)
+ * mInChannelCountRequested * mConfig.inputCfg.buffer.frameCount);
+ inBuffer = mInConversionBuffer;
+ }
+ if (mConfig.outputCfg.accessMode == EFFECT_BUFFER_ACCESS_ACCUMULATE
+ && mOutChannelCountRequested != outChannelCount) {
+ adjust_selected_channels(
+ outBuffer->audioBuffer()->f32, mOutChannelCountRequested,
+ mOutConversionBuffer->audioBuffer()->f32, outChannelCount,
+ sizeof(float),
+ sizeof(float)
+ * mOutChannelCountRequested * mConfig.outputCfg.buffer.frameCount);
+ outBuffer = mOutConversionBuffer;
+ }
if (!mSupportsFloat) { // convert input to int16_t as effect doesn't support float.
if (!auxType) {
if (mInConversionBuffer.get() == nullptr) {
@@ -379,8 +402,9 @@
}
memcpy_to_i16_from_float(
mInConversionBuffer->audioBuffer()->s16,
- mInBuffer->audioBuffer()->f32,
+ inBuffer->audioBuffer()->f32,
inChannelCount * mConfig.inputCfg.buffer.frameCount);
+ inBuffer = mInConversionBuffer;
}
if (mConfig.outputCfg.accessMode == EFFECT_BUFFER_ACCESS_ACCUMULATE) {
if (mOutConversionBuffer.get() == nullptr) {
@@ -389,21 +413,30 @@
}
memcpy_to_i16_from_float(
mOutConversionBuffer->audioBuffer()->s16,
- mOutBuffer->audioBuffer()->f32,
+ outBuffer->audioBuffer()->f32,
outChannelCount * mConfig.outputCfg.buffer.frameCount);
+ outBuffer = mOutConversionBuffer;
}
}
#endif
-
ret = mEffectInterface->process();
-
#ifdef FLOAT_EFFECT_CHAIN
if (!mSupportsFloat) { // convert output int16_t back to float.
+ sp<EffectBufferHalInterface> target =
+ mOutChannelCountRequested != outChannelCount
+ ? mOutConversionBuffer : mOutBuffer;
+
memcpy_to_float_from_i16(
- mOutBuffer->audioBuffer()->f32,
+ target->audioBuffer()->f32,
mOutConversionBuffer->audioBuffer()->s16,
outChannelCount * mConfig.outputCfg.buffer.frameCount);
}
+ if (mOutChannelCountRequested != outChannelCount) {
+ adjust_selected_channels(mOutConversionBuffer->audioBuffer()->f32, outChannelCount,
+ mOutBuffer->audioBuffer()->f32, mOutChannelCountRequested,
+ sizeof(float),
+ sizeof(float) * outChannelCount * mConfig.outputCfg.buffer.frameCount);
+ }
#endif
} else {
#ifdef FLOAT_EFFECT_CHAIN
@@ -476,15 +509,28 @@
}
// TODO: handle configuration of effects replacing track process
+ // TODO: handle configuration of input (record) SW effects above the HAL,
+ // similar to output EFFECT_FLAG_TYPE_INSERT/REPLACE,
+ // in which case input channel masks should be used here.
channelMask = thread->channelMask();
+ mConfig.inputCfg.channels = channelMask;
mConfig.outputCfg.channels = channelMask;
if ((mDescriptor.flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_AUXILIARY) {
- mConfig.inputCfg.channels = AUDIO_CHANNEL_OUT_MONO;
- mConfig.outputCfg.channels = AUDIO_CHANNEL_OUT_STEREO;
- ALOGV("Overriding auxiliary effect input as MONO and output as STEREO");
+ if (mConfig.inputCfg.channels != AUDIO_CHANNEL_OUT_MONO) {
+ mConfig.inputCfg.channels = AUDIO_CHANNEL_OUT_MONO;
+ ALOGV("Overriding auxiliary effect input channels %#x as MONO",
+ mConfig.inputCfg.channels);
+ }
+#ifndef MULTICHANNEL_EFFECT_CHAIN
+ if (mConfig.outputCfg.channels != AUDIO_CHANNEL_OUT_STEREO) {
+ mConfig.outputCfg.channels = AUDIO_CHANNEL_OUT_STEREO;
+ ALOGV("Overriding auxiliary effect output channels %#x as STEREO",
+ mConfig.outputCfg.channels);
+ }
+#endif
} else {
- mConfig.inputCfg.channels = channelMask;
+#ifndef MULTICHANNEL_EFFECT_CHAIN
// TODO: Update this logic when multichannel effects are implemented.
// For offloaded tracks consider mono output as stereo for proper effect initialization
if (channelMask == AUDIO_CHANNEL_OUT_MONO) {
@@ -492,7 +538,12 @@
mConfig.outputCfg.channels = AUDIO_CHANNEL_OUT_STEREO;
ALOGV("Overriding effect input and output as STEREO");
}
+#endif
}
+ mInChannelCountRequested =
+ audio_channel_count_from_out_mask(mConfig.inputCfg.channels);
+ mOutChannelCountRequested =
+ audio_channel_count_from_out_mask(mConfig.outputCfg.channels);
mConfig.inputCfg.format = EFFECT_BUFFER_FORMAT;
mConfig.outputCfg.format = EFFECT_BUFFER_FORMAT;
@@ -530,28 +581,58 @@
status_t cmdStatus;
size = sizeof(int);
status = mEffectInterface->command(EFFECT_CMD_SET_CONFIG,
- sizeof(effect_config_t),
+ sizeof(mConfig),
&mConfig,
&size,
&cmdStatus);
if (status == NO_ERROR) {
status = cmdStatus;
-#ifdef FLOAT_EFFECT_CHAIN
- mSupportsFloat = true;
-#endif
}
-#ifdef FLOAT_EFFECT_CHAIN
- else {
- ALOGV("EFFECT_CMD_SET_CONFIG failed with float format, retry with int16_t.");
- mConfig.inputCfg.format = AUDIO_FORMAT_PCM_16_BIT;
- mConfig.outputCfg.format = AUDIO_FORMAT_PCM_16_BIT;
+
+#ifdef MULTICHANNEL_EFFECT_CHAIN
+ if (status != NO_ERROR &&
+ (mConfig.inputCfg.channels != AUDIO_CHANNEL_OUT_STEREO
+ || mConfig.outputCfg.channels != AUDIO_CHANNEL_OUT_STEREO)) {
+ // Older effects may require exact STEREO position mask.
+ if (mConfig.inputCfg.channels != AUDIO_CHANNEL_OUT_STEREO) {
+ ALOGV("Overriding effect input channels %#x as STEREO", mConfig.inputCfg.channels);
+ mConfig.inputCfg.channels = AUDIO_CHANNEL_OUT_STEREO;
+ }
+ if (mConfig.outputCfg.channels != AUDIO_CHANNEL_OUT_STEREO) {
+ ALOGV("Overriding effect output channels %#x as STEREO", mConfig.outputCfg.channels);
+ mConfig.outputCfg.channels = AUDIO_CHANNEL_OUT_STEREO;
+ }
+ size = sizeof(int);
status = mEffectInterface->command(EFFECT_CMD_SET_CONFIG,
- sizeof(effect_config_t),
+ sizeof(mConfig),
&mConfig,
&size,
&cmdStatus);
if (status == NO_ERROR) {
status = cmdStatus;
+ }
+ }
+#endif
+
+#ifdef FLOAT_EFFECT_CHAIN
+ if (status == NO_ERROR) {
+ mSupportsFloat = true;
+ }
+
+ if (status != NO_ERROR) {
+ ALOGV("EFFECT_CMD_SET_CONFIG failed with float format, retry with int16_t.");
+ mConfig.inputCfg.format = AUDIO_FORMAT_PCM_16_BIT;
+ mConfig.outputCfg.format = AUDIO_FORMAT_PCM_16_BIT;
+ size = sizeof(int);
+ status = mEffectInterface->command(EFFECT_CMD_SET_CONFIG,
+ sizeof(mConfig),
+ &mConfig,
+ &size,
+ &cmdStatus);
+ if (status == NO_ERROR) {
+ status = cmdStatus;
+ }
+ if (status == NO_ERROR) {
mSupportsFloat = false;
ALOGVV("config worked with 16 bit");
} else {
@@ -929,11 +1010,15 @@
// the original buffer) when the output buffer is identical to the input buffer,
// but we don't optimize for it here.
const bool auxType = (mDescriptor.flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_AUXILIARY;
- if (!auxType && !mSupportsFloat && mInBuffer.get() != nullptr) {
+ const uint32_t inChannelCount =
+ audio_channel_count_from_out_mask(mConfig.inputCfg.channels);
+ const bool formatMismatch = !mSupportsFloat || mInChannelCountRequested != inChannelCount;
+ if (!auxType && formatMismatch && mInBuffer.get() != nullptr) {
// we need to translate - create hidl shared buffer and intercept
const size_t inFrameCount = mConfig.inputCfg.buffer.frameCount;
- const int inChannels = audio_channel_count_from_out_mask(mConfig.inputCfg.channels);
- const size_t size = inChannels * inFrameCount * sizeof(int16_t);
+ // Use FCC_2 in case mInChannelCountRequested is mono and the effect is stereo.
+ const uint32_t inChannels = std::max((uint32_t)FCC_2, mInChannelCountRequested);
+ const size_t size = inChannels * inFrameCount * std::max(sizeof(int16_t), sizeof(float));
ALOGV("%s: setInBuffer updating for inChannels:%d inFrameCount:%zu total size:%zu",
__func__, inChannels, inFrameCount, size);
@@ -942,7 +1027,9 @@
|| size > mInConversionBuffer->getSize())) {
mInConversionBuffer.clear();
ALOGV("%s: allocating mInConversionBuffer %zu", __func__, size);
- (void)EffectBufferHalInterface::allocate(size, &mInConversionBuffer);
+ sp<AudioFlinger> audioFlinger = mAudioFlinger.promote();
+ LOG_ALWAYS_FATAL_IF(audioFlinger == nullptr, "EM could not retrieved audioFlinger");
+ (void)audioFlinger->mEffectsFactoryHal->allocateBuffer(size, &mInConversionBuffer);
}
if (mInConversionBuffer.get() != nullptr) {
mInConversionBuffer->setFrameCount(inFrameCount);
@@ -970,10 +1057,14 @@
#ifdef FLOAT_EFFECT_CHAIN
// Note: Any effect that does not accumulate does not need mOutConversionBuffer and
// can do in-place conversion from int16_t to float. We don't optimize here.
- if (!mSupportsFloat && mOutBuffer.get() != nullptr) {
+ const uint32_t outChannelCount =
+ audio_channel_count_from_out_mask(mConfig.outputCfg.channels);
+ const bool formatMismatch = !mSupportsFloat || mOutChannelCountRequested != outChannelCount;
+ if (formatMismatch && mOutBuffer.get() != nullptr) {
const size_t outFrameCount = mConfig.outputCfg.buffer.frameCount;
- const int outChannels = audio_channel_count_from_out_mask(mConfig.outputCfg.channels);
- const size_t size = outChannels * outFrameCount * sizeof(int16_t);
+ // Use FCC_2 in case mOutChannelCountRequested is mono and the effect is stereo.
+ const uint32_t outChannels = std::max((uint32_t)FCC_2, mOutChannelCountRequested);
+ const size_t size = outChannels * outFrameCount * std::max(sizeof(int16_t), sizeof(float));
ALOGV("%s: setOutBuffer updating for outChannels:%d outFrameCount:%zu total size:%zu",
__func__, outChannels, outFrameCount, size);
@@ -982,7 +1073,9 @@
|| size > mOutConversionBuffer->getSize())) {
mOutConversionBuffer.clear();
ALOGV("%s: allocating mOutConversionBuffer %zu", __func__, size);
- (void)EffectBufferHalInterface::allocate(size, &mOutConversionBuffer);
+ sp<AudioFlinger> audioFlinger = mAudioFlinger.promote();
+ LOG_ALWAYS_FATAL_IF(audioFlinger == nullptr, "EM could not retrieved audioFlinger");
+ (void)audioFlinger->mEffectsFactoryHal->allocateBuffer(size, &mOutConversionBuffer);
}
if (mOutConversionBuffer.get() != nullptr) {
mOutConversionBuffer->setFrameCount(outFrameCount);
@@ -1813,14 +1906,8 @@
if (mInBuffer == NULL) {
return;
}
- // TODO: This will change in the future, depending on multichannel
- // and sample format changes for effects.
- // Currently effects processing is only available for stereo, AUDIO_FORMAT_PCM_16_BIT
- // (4 bytes frame size)
-
const size_t frameSize =
- audio_bytes_per_sample(EFFECT_BUFFER_FORMAT)
- * std::min((uint32_t)FCC_2, thread->channelCount());
+ audio_bytes_per_sample(EFFECT_BUFFER_FORMAT) * thread->channelCount();
memset(mInBuffer->audioBuffer()->raw, 0, thread->frameCount() * frameSize);
mInBuffer->commit();
@@ -1937,10 +2024,10 @@
size_t numSamples = thread->frameCount();
sp<EffectBufferHalInterface> halBuffer;
#ifdef FLOAT_EFFECT_CHAIN
- status_t result = EffectBufferHalInterface::allocate(
+ status_t result = thread->mAudioFlinger->mEffectsFactoryHal->allocateBuffer(
numSamples * sizeof(float), &halBuffer);
#else
- status_t result = EffectBufferHalInterface::allocate(
+ status_t result = thread->mAudioFlinger->mEffectsFactoryHal->allocateBuffer(
numSamples * sizeof(int32_t), &halBuffer);
#endif
if (result != OK) return result;
diff --git a/services/audioflinger/Effects.h b/services/audioflinger/Effects.h
index eea3208..2327bb9 100644
--- a/services/audioflinger/Effects.h
+++ b/services/audioflinger/Effects.h
@@ -173,6 +173,8 @@
bool mSupportsFloat; // effect supports float processing
sp<EffectBufferHalInterface> mInConversionBuffer; // Buffers for HAL conversion if needed.
sp<EffectBufferHalInterface> mOutConversionBuffer;
+ uint32_t mInChannelCountRequested;
+ uint32_t mOutChannelCountRequested;
#endif
};
diff --git a/services/audioflinger/RecordTracks.h b/services/audioflinger/RecordTracks.h
index f8da780..63a3d98 100644
--- a/services/audioflinger/RecordTracks.h
+++ b/services/audioflinger/RecordTracks.h
@@ -63,6 +63,9 @@
virtual bool isFastTrack() const { return (mFlags & AUDIO_INPUT_FLAG_FAST) != 0; }
+ void setSilenced(bool silenced) { mSilenced = silenced; }
+ bool isSilenced() const { return mSilenced; }
+
private:
friend class AudioFlinger; // for mState
@@ -91,6 +94,8 @@
// used by the record thread to convert frames to proper destination format
RecordBufferConverter *mRecordBufferConverter;
audio_input_flags_t mFlags;
+
+ bool mSilenced;
};
// playback track, used by PatchPanel
diff --git a/services/audioflinger/ServiceUtilities.cpp b/services/audioflinger/ServiceUtilities.cpp
index c1044ef..f08698e 100644
--- a/services/audioflinger/ServiceUtilities.cpp
+++ b/services/audioflinger/ServiceUtilities.cpp
@@ -153,4 +153,11 @@
return ok;
}
+bool modifyPhoneStateAllowed(pid_t pid, uid_t uid) {
+ static const String16 sModifyPhoneState("android.permission.MODIFY_PHONE_STATE");
+ bool ok = checkPermission(sModifyPhoneState, pid, uid);
+ if (!ok) ALOGE("Request requires android.permission.MODIFY_PHONE_STATE");
+ return ok;
+}
+
} // namespace android
diff --git a/services/audioflinger/ServiceUtilities.h b/services/audioflinger/ServiceUtilities.h
index 04cb9cd..83533dd 100644
--- a/services/audioflinger/ServiceUtilities.h
+++ b/services/audioflinger/ServiceUtilities.h
@@ -26,4 +26,5 @@
bool settingsAllowed();
bool modifyAudioRoutingAllowed();
bool dumpAllowed();
+bool modifyPhoneStateAllowed(pid_t pid, uid_t uid);
}
diff --git a/services/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp
index d5def48..7bfe802 100644
--- a/services/audioflinger/Threads.cpp
+++ b/services/audioflinger/Threads.cpp
@@ -56,6 +56,8 @@
#include <powermanager/PowerManager.h>
+#include <media/audiohal/EffectsFactoryHalInterface.h>
+
#include "AudioFlinger.h"
#include "FastMixer.h"
#include "FastCapture.h"
@@ -1176,6 +1178,7 @@
switch (mType) {
case MIXER: {
+#ifndef MULTICHANNEL_EFFECT_CHAIN
// Reject any effect on mixer multichannel sinks.
// TODO: fix both format and multichannel issues with effects.
if (mChannelCount != FCC_2) {
@@ -1183,6 +1186,7 @@
" thread %s", desc->name, mChannelCount, mThreadName);
return BAD_VALUE;
}
+#endif
audio_output_flags_t flags = mOutput->flags;
if (hasFastMixer() || (flags & AUDIO_OUTPUT_FLAG_FAST)) {
if (sessionId == AUDIO_SESSION_OUTPUT_MIX) {
@@ -1229,6 +1233,7 @@
desc->name, mThreadName);
return BAD_VALUE;
case DUPLICATING:
+#ifndef MULTICHANNEL_EFFECT_CHAIN
// Reject any effect on mixer multichannel sinks.
// TODO: fix both format and multichannel issues with effects.
if (mChannelCount != FCC_2) {
@@ -1236,6 +1241,7 @@
" on DUPLICATING thread %s", desc->name, mChannelCount, mThreadName);
return BAD_VALUE;
}
+#endif
if ((sessionId == AUDIO_SESSION_OUTPUT_STAGE) || (sessionId == AUDIO_SESSION_OUTPUT_MIX)) {
ALOGW("checkEffectCompatibility_l(): global effect %s on DUPLICATING"
" thread %s", desc->name, mThreadName);
@@ -2878,7 +2884,7 @@
{
audio_session_t session = chain->sessionId();
sp<EffectBufferHalInterface> halInBuffer, halOutBuffer;
- status_t result = EffectBufferHalInterface::mirror(
+ status_t result = mAudioFlinger->mEffectsFactoryHal->mirrorBuffer(
mEffectBufferEnabled ? mEffectBuffer : mSinkBuffer,
mEffectBufferEnabled ? mEffectBufferSize : mSinkBufferSize,
&halInBuffer);
@@ -2891,7 +2897,7 @@
// the sink buffer as input
if (mType != DIRECT) {
size_t numSamples = mNormalFrameCount * mChannelCount;
- status_t result = EffectBufferHalInterface::allocate(
+ status_t result = mAudioFlinger->mEffectsFactoryHal->allocateBuffer(
numSamples * sizeof(effect_buffer_t),
&halInBuffer);
if (result != OK) return result;
@@ -4868,7 +4874,7 @@
{
PlaybackThread::dumpInternals(fd, args);
dprintf(fd, " Thread throttle time (msecs): %u\n", mThreadThrottleTimeMs);
- dprintf(fd, " AudioMixer tracks: 0x%08x\n", mAudioMixer->trackNames());
+ dprintf(fd, " AudioMixer tracks: %s\n", mAudioMixer->trackNames().c_str());
dprintf(fd, " Master mono: %s\n", mMasterMono ? "on" : "off");
if (hasFastMixer()) {
@@ -6528,6 +6534,7 @@
rear = mRsmpInRear += framesRead;
size = activeTracks.size();
+
// loop over each active track
for (size_t i = 0; i < size; i++) {
activeTrack = activeTracks[i];
@@ -6584,6 +6591,11 @@
if (activeTrack->mFramesToDrop == 0) {
if (framesOut > 0) {
activeTrack->mSink.frameCount = framesOut;
+ // Sanitize before releasing if the track has no access to the source data
+ // An idle UID receives silence from non virtual devices until active
+ if (activeTrack->isSilenced()) {
+ memset(activeTrack->mSink.raw, 0, framesOut * mFrameSize);
+ }
activeTrack->releaseBuffer(&activeTrack->mSink);
}
} else {
@@ -6923,7 +6935,8 @@
status_t status = NO_ERROR;
if (recordTrack->isExternalTrack()) {
mLock.unlock();
- status = AudioSystem::startInput(mId, recordTrack->sessionId());
+ bool silenced;
+ status = AudioSystem::startInput(recordTrack->portId(), &silenced);
mLock.lock();
// FIXME should verify that recordTrack is still in mActiveTracks
if (status != NO_ERROR) {
@@ -6932,6 +6945,7 @@
ALOGV("RecordThread::start error %d", status);
return status;
}
+ recordTrack->setSilenced(silenced);
}
// Catch up with current buffer indices if thread is already running.
// This is what makes a new client discard all buffered data. If the track's mRsmpInFront
@@ -6954,7 +6968,7 @@
startError:
if (recordTrack->isExternalTrack()) {
- AudioSystem::stopInput(mId, recordTrack->sessionId());
+ AudioSystem::stopInput(recordTrack->portId());
}
recordTrack->clearSyncStartEvent();
// FIXME I wonder why we do not reset the state here?
@@ -7135,6 +7149,16 @@
write(fd, result.string(), result.size());
}
+void AudioFlinger::RecordThread::setRecordSilenced(uid_t uid, bool silenced)
+{
+ Mutex::Autolock _l(mLock);
+ for (size_t i = 0; i < mTracks.size() ; i++) {
+ sp<RecordTrack> track = mTracks[i];
+ if (track != 0 && track->uid() == uid) {
+ track->setSilenced(silenced);
+ }
+ }
+}
void AudioFlinger::RecordThread::ResamplerBufferProvider::reset()
{
@@ -7717,7 +7741,7 @@
if (isOutput()) {
AudioSystem::releaseOutput(mId, streamType(), mSessionId);
} else {
- AudioSystem::releaseInput(mId, mSessionId);
+ AudioSystem::releaseInput(mPortId);
}
}
@@ -7777,10 +7801,6 @@
return NO_ERROR;
}
- if (!isOutput() && !recordingAllowed(client.packageName, client.clientPid, client.clientUid)) {
- return PERMISSION_DENIED;
- }
-
audio_port_handle_t portId = AUDIO_PORT_HANDLE_NONE;
audio_io_handle_t io = mId;
@@ -7796,6 +7816,7 @@
ret = AudioSystem::getOutputForAttr(&mAttr, &io,
mSessionId,
&stream,
+ client.clientPid,
client.clientUid,
&config,
flags,
@@ -7811,6 +7832,7 @@
mSessionId,
client.clientPid,
client.clientUid,
+ client.packageName,
&config,
AUDIO_INPUT_FLAG_MMAP_NOIRQ,
&deviceId,
@@ -7827,7 +7849,9 @@
if (isOutput()) {
ret = AudioSystem::startOutput(mId, streamType(), mSessionId);
} else {
- ret = AudioSystem::startInput(mId, mSessionId);
+ // TODO: Block recording for idle UIDs (b/72134552)
+ bool silenced;
+ ret = AudioSystem::startInput(portId, &silenced);
}
// abort if start is rejected by audio policy manager
@@ -7837,7 +7861,7 @@
if (isOutput()) {
AudioSystem::releaseOutput(mId, streamType(), mSessionId);
} else {
- AudioSystem::releaseInput(mId, mSessionId);
+ AudioSystem::releaseInput(portId);
}
} else {
mHalStream->stop();
@@ -7894,8 +7918,8 @@
AudioSystem::stopOutput(mId, streamType(), track->sessionId());
AudioSystem::releaseOutput(mId, streamType(), track->sessionId());
} else {
- AudioSystem::stopInput(mId, track->sessionId());
- AudioSystem::releaseInput(mId, track->sessionId());
+ AudioSystem::stopInput(track->portId());
+ AudioSystem::releaseInput(track->portId());
}
sp<EffectChain> chain = getEffectChain_l(track->sessionId());
diff --git a/services/audioflinger/Threads.h b/services/audioflinger/Threads.h
index 17f26c5..eb29497 100644
--- a/services/audioflinger/Threads.h
+++ b/services/audioflinger/Threads.h
@@ -623,8 +623,7 @@
static const int8_t kMaxTrackRetriesOffload = 20;
static const int8_t kMaxTrackStartupRetriesOffload = 100;
static const int8_t kMaxTrackStopRetriesOffload = 2;
- // 14 tracks max per client allows for 2 misbehaving application leaving 4 available tracks.
- static const uint32_t kMaxTracksPerUid = 14;
+ static constexpr uint32_t kMaxTracksPerUid = 40;
// Maximum delay (in nanoseconds) for upcoming buffers in suspend mode, otherwise
// if delay is greater, the estimated time for timeLoopNextNs is reset.
@@ -1396,6 +1395,9 @@
void checkBtNrec();
+ // Sets the UID records silence
+ void setRecordSilenced(uid_t uid, bool silenced);
+
private:
// Enter standby if not already in standby, and set mStandby flag
void standbyIfNotAlreadyInStandby();
diff --git a/services/audioflinger/Tracks.cpp b/services/audioflinger/Tracks.cpp
index cdd8ca0..06bbf1e 100644
--- a/services/audioflinger/Tracks.cpp
+++ b/services/audioflinger/Tracks.cpp
@@ -1687,7 +1687,7 @@
if (thread != 0) {
RecordThread *recordThread = (RecordThread *)thread.get();
if (recordThread->stop(this) && isExternalTrack()) {
- AudioSystem::stopInput(mThreadIoHandle, mSessionId);
+ AudioSystem::stopInput(mPortId);
}
}
}
@@ -1699,9 +1699,9 @@
{
if (isExternalTrack()) {
if (mState == ACTIVE || mState == RESUMING) {
- AudioSystem::stopInput(mThreadIoHandle, mSessionId);
+ AudioSystem::stopInput(mPortId);
}
- AudioSystem::releaseInput(mThreadIoHandle, mSessionId);
+ AudioSystem::releaseInput(mPortId);
}
sp<ThreadBase> thread = mThread.promote();
if (thread != 0) {
diff --git a/services/audiopolicy/AudioPolicyInterface.h b/services/audiopolicy/AudioPolicyInterface.h
index f2cb25f..7f09e9b 100644
--- a/services/audiopolicy/AudioPolicyInterface.h
+++ b/services/audiopolicy/AudioPolicyInterface.h
@@ -65,15 +65,15 @@
API_INPUT_TELEPHONY_RX, // used for capture from telephony RX path
} input_type_t;
- enum {
+ enum {
API_INPUT_CONCURRENCY_NONE = 0,
API_INPUT_CONCURRENCY_CALL = (1 << 0), // Concurrency with a call
API_INPUT_CONCURRENCY_CAPTURE = (1 << 1), // Concurrency with another capture
API_INPUT_CONCURRENCY_ALL = (API_INPUT_CONCURRENCY_CALL | API_INPUT_CONCURRENCY_CAPTURE),
- };
+ };
- typedef uint32_t concurrency_type__mask_t;
+ typedef uint32_t concurrency_type__mask_t;
public:
virtual ~AudioPolicyInterface() {}
@@ -116,7 +116,7 @@
audio_stream_type_t *stream,
uid_t uid,
const audio_config_t *config,
- audio_output_flags_t flags,
+ audio_output_flags_t *flags,
audio_port_handle_t *selectedDeviceId,
audio_port_handle_t *portId) = 0;
// indicates to the audio policy manager that the output starts being used by corresponding stream.
@@ -145,6 +145,7 @@
// indicates to the audio policy manager that the input starts being used.
virtual status_t startInput(audio_io_handle_t input,
audio_session_t session,
+ bool silenced,
concurrency_type__mask_t *concurrency) = 0;
// indicates to the audio policy manager that the input stops being used.
virtual status_t stopInput(audio_io_handle_t input,
@@ -239,6 +240,8 @@
virtual float getStreamVolumeDB(
audio_stream_type_t stream, int index, audio_devices_t device) = 0;
+
+ virtual void setRecordSilenced(uid_t uid, bool silenced);
};
diff --git a/services/audiopolicy/common/managerdefinitions/include/AudioOutputDescriptor.h b/services/audiopolicy/common/managerdefinitions/include/AudioOutputDescriptor.h
index 2803ec1..cd2174d 100644
--- a/services/audiopolicy/common/managerdefinitions/include/AudioOutputDescriptor.h
+++ b/services/audiopolicy/common/managerdefinitions/include/AudioOutputDescriptor.h
@@ -187,6 +187,15 @@
bool isStreamActiveRemotely(audio_stream_type_t stream, uint32_t inPastMs = 0) const;
/**
+ * return whether a stream is playing, but not on a "remote" device.
+ * Override to change the definition of a local/remote playback.
+ * Used for instance by policy manager to alter the speaker playback ("speaker safe" behavior)
+ * when media plays or not locally.
+ * For the base implementation, "remotely" means playing during screen mirroring.
+ */
+ bool isStreamActiveLocally(audio_stream_type_t stream, uint32_t inPastMs = 0) const;
+
+ /**
* returns the A2DP output handle if it is open or 0 otherwise
*/
audio_io_handle_t getA2dpOutput() const;
diff --git a/services/audiopolicy/common/managerdefinitions/include/AudioSession.h b/services/audiopolicy/common/managerdefinitions/include/AudioSession.h
index 0d19373..dd5247d 100644
--- a/services/audiopolicy/common/managerdefinitions/include/AudioSession.h
+++ b/services/audiopolicy/common/managerdefinitions/include/AudioSession.h
@@ -55,6 +55,8 @@
void setUid(uid_t uid) { mRecordClientInfo.uid = uid; }
bool matches(const sp<AudioSession> &other) const;
bool isSoundTrigger() const { return mIsSoundTrigger; }
+ void setSilenced(bool silenced) { mSilenced = silenced; }
+ bool isSilenced() const { return mSilenced; }
uint32_t openCount() const { return mOpenCount; } ;
uint32_t activeCount() const { return mActiveCount; } ;
@@ -70,6 +72,7 @@
const struct audio_config_base mConfig;
const audio_input_flags_t mFlags;
bool mIsSoundTrigger;
+ bool mSilenced;
uint32_t mOpenCount;
uint32_t mActiveCount;
AudioMix* mPolicyMix; // non NULL when used by a dynamic policy
diff --git a/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp b/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp
index 044d6db..17fc272 100644
--- a/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp
+++ b/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp
@@ -333,6 +333,10 @@
return true;
}
}
+ if (device == AUDIO_DEVICE_OUT_TELEPHONY_TX) {
+ ALOGV("max gain when output device is telephony tx");
+ return true;
+ }
return false;
}
@@ -575,6 +579,19 @@
return false;
}
+bool SwAudioOutputCollection::isStreamActiveLocally(audio_stream_type_t stream, uint32_t inPastMs) const
+{
+ nsecs_t sysTime = systemTime();
+ for (size_t i = 0; i < this->size(); i++) {
+ const sp<SwAudioOutputDescriptor> outputDesc = this->valueAt(i);
+ if (outputDesc->isStreamActive(stream, inPastMs, sysTime)
+ && ((outputDesc->device() & APM_AUDIO_OUT_DEVICE_REMOTE_ALL) == 0)) {
+ return true;
+ }
+ }
+ return false;
+}
+
bool SwAudioOutputCollection::isStreamActiveRemotely(audio_stream_type_t stream,
uint32_t inPastMs) const
{
diff --git a/services/audiopolicy/enginedefault/src/Engine.cpp b/services/audiopolicy/enginedefault/src/Engine.cpp
index 9bdb98c..5ec0475 100644
--- a/services/audiopolicy/enginedefault/src/Engine.cpp
+++ b/services/audiopolicy/enginedefault/src/Engine.cpp
@@ -238,18 +238,19 @@
const SwAudioOutputCollection &outputs = mApmObserver->getOutputs();
return getDeviceForStrategyInt(strategy, availableOutputDevices,
- availableInputDevices, outputs);
+ availableInputDevices, outputs, (uint32_t)AUDIO_DEVICE_NONE);
}
-
audio_devices_t Engine::getDeviceForStrategyInt(routing_strategy strategy,
- DeviceVector availableOutputDevices,
- DeviceVector availableInputDevices,
- const SwAudioOutputCollection &outputs) const
+ DeviceVector availableOutputDevices,
+ DeviceVector availableInputDevices,
+ const SwAudioOutputCollection &outputs,
+ uint32_t outputDeviceTypesToIgnore) const
{
uint32_t device = AUDIO_DEVICE_NONE;
- uint32_t availableOutputDevicesType = availableOutputDevices.types();
+ uint32_t availableOutputDevicesType =
+ availableOutputDevices.types() & ~outputDeviceTypesToIgnore;
switch (strategy) {
@@ -260,38 +261,24 @@
case STRATEGY_SONIFICATION_RESPECTFUL:
if (isInCall()) {
device = getDeviceForStrategyInt(
- STRATEGY_SONIFICATION, availableOutputDevices, availableInputDevices, outputs);
- } else if (outputs.isStreamActiveRemotely(AUDIO_STREAM_MUSIC,
- SONIFICATION_RESPECTFUL_AFTER_MUSIC_DELAY)) {
- // while media is playing on a remote device, use the the sonification behavior.
- // Note that we test this usecase before testing if media is playing because
- // the isStreamActive() method only informs about the activity of a stream, not
- // if it's for local playback. Note also that we use the same delay between both tests
- device = getDeviceForStrategyInt(
- STRATEGY_SONIFICATION, availableOutputDevices, availableInputDevices, outputs);
- //user "safe" speaker if available instead of normal speaker to avoid triggering
- //other acoustic safety mechanisms for notification
- if ((device & AUDIO_DEVICE_OUT_SPEAKER) &&
- (availableOutputDevicesType & AUDIO_DEVICE_OUT_SPEAKER_SAFE)) {
- device |= AUDIO_DEVICE_OUT_SPEAKER_SAFE;
- device &= ~AUDIO_DEVICE_OUT_SPEAKER;
- }
- } else if (outputs.isStreamActive(
- AUDIO_STREAM_MUSIC, SONIFICATION_RESPECTFUL_AFTER_MUSIC_DELAY)
- || outputs.isStreamActive(
- AUDIO_STREAM_ACCESSIBILITY, SONIFICATION_RESPECTFUL_AFTER_MUSIC_DELAY))
- {
- // while media/a11y is playing (or has recently played), use the same device
- device = getDeviceForStrategyInt(
- STRATEGY_MEDIA, availableOutputDevices, availableInputDevices, outputs);
+ STRATEGY_SONIFICATION, availableOutputDevices, availableInputDevices, outputs,
+ outputDeviceTypesToIgnore);
} else {
- // when media is not playing anymore, fall back on the sonification behavior
- device = getDeviceForStrategyInt(
- STRATEGY_SONIFICATION, availableOutputDevices, availableInputDevices, outputs);
- //user "safe" speaker if available instead of normal speaker to avoid triggering
- //other acoustic safety mechanisms for notification
- if ((device & AUDIO_DEVICE_OUT_SPEAKER) &&
- (availableOutputDevicesType & AUDIO_DEVICE_OUT_SPEAKER_SAFE)) {
+ bool media_active_locally =
+ outputs.isStreamActiveLocally(
+ AUDIO_STREAM_MUSIC, SONIFICATION_RESPECTFUL_AFTER_MUSIC_DELAY)
+ || outputs.isStreamActiveLocally(
+ AUDIO_STREAM_ACCESSIBILITY, SONIFICATION_RESPECTFUL_AFTER_MUSIC_DELAY);
+ // routing is same as media without the "remote" device
+ device = getDeviceForStrategyInt(STRATEGY_MEDIA,
+ availableOutputDevices,
+ availableInputDevices, outputs,
+ AUDIO_DEVICE_OUT_REMOTE_SUBMIX | outputDeviceTypesToIgnore);
+ // if no media is playing on the device, check for mandatory use of "safe" speaker
+ // when media would have played on speaker, and the safe speaker path is available
+ if (!media_active_locally
+ && (device & AUDIO_DEVICE_OUT_SPEAKER)
+ && (availableOutputDevicesType & AUDIO_DEVICE_OUT_SPEAKER_SAFE)) {
device |= AUDIO_DEVICE_OUT_SPEAKER_SAFE;
device &= ~AUDIO_DEVICE_OUT_SPEAKER;
}
@@ -302,7 +289,8 @@
if (!isInCall()) {
// when off call, DTMF strategy follows the same rules as MEDIA strategy
device = getDeviceForStrategyInt(
- STRATEGY_MEDIA, availableOutputDevices, availableInputDevices, outputs);
+ STRATEGY_MEDIA, availableOutputDevices, availableInputDevices, outputs,
+ outputDeviceTypesToIgnore);
break;
}
// when in call, DTMF and PHONE strategies follow the same rules
@@ -408,7 +396,8 @@
// handleIncallSonification().
if (isInCall()) {
device = getDeviceForStrategyInt(
- STRATEGY_PHONE, availableOutputDevices, availableInputDevices, outputs);
+ STRATEGY_PHONE, availableOutputDevices, availableInputDevices, outputs,
+ outputDeviceTypesToIgnore);
break;
}
// FALL THROUGH
@@ -463,11 +452,13 @@
if (outputs.isStreamActive(AUDIO_STREAM_RING) ||
outputs.isStreamActive(AUDIO_STREAM_ALARM)) {
return getDeviceForStrategyInt(
- STRATEGY_SONIFICATION, availableOutputDevices, availableInputDevices, outputs);
+ STRATEGY_SONIFICATION, availableOutputDevices, availableInputDevices, outputs,
+ outputDeviceTypesToIgnore);
}
if (isInCall()) {
return getDeviceForStrategyInt(
- STRATEGY_PHONE, availableOutputDevices, availableInputDevices, outputs);
+ STRATEGY_PHONE, availableOutputDevices, availableInputDevices, outputs,
+ outputDeviceTypesToIgnore);
}
}
// For other cases, STRATEGY_ACCESSIBILITY behaves like STRATEGY_MEDIA
@@ -486,7 +477,8 @@
}
if (isInCall() && (strategy == STRATEGY_MEDIA)) {
device = getDeviceForStrategyInt(
- STRATEGY_PHONE, availableOutputDevices, availableInputDevices, outputs);
+ STRATEGY_PHONE, availableOutputDevices, availableInputDevices, outputs,
+ outputDeviceTypesToIgnore);
break;
}
if ((device2 == AUDIO_DEVICE_NONE) &&
diff --git a/services/audiopolicy/enginedefault/src/Engine.h b/services/audiopolicy/enginedefault/src/Engine.h
index 57538c4..06186c1 100644
--- a/services/audiopolicy/enginedefault/src/Engine.h
+++ b/services/audiopolicy/enginedefault/src/Engine.h
@@ -126,9 +126,10 @@
routing_strategy getStrategyForUsage(audio_usage_t usage);
audio_devices_t getDeviceForStrategy(routing_strategy strategy) const;
audio_devices_t getDeviceForStrategyInt(routing_strategy strategy,
- DeviceVector availableOutputDevices,
- DeviceVector availableInputDevices,
- const SwAudioOutputCollection &outputs) const;
+ DeviceVector availableOutputDevices,
+ DeviceVector availableInputDevices,
+ const SwAudioOutputCollection &outputs,
+ uint32_t outputDeviceTypesToIgnore) const;
audio_devices_t getDeviceForInputSource(audio_source_t inputSource) const;
audio_mode_t mPhoneState; /**< current phone state. */
diff --git a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
index 68730a5..7343601 100644
--- a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
+++ b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
@@ -741,7 +741,7 @@
audio_stream_type_t *stream,
uid_t uid,
const audio_config_t *config,
- audio_output_flags_t flags,
+ audio_output_flags_t *flags,
audio_port_handle_t *selectedDeviceId,
audio_port_handle_t *portId)
{
@@ -801,12 +801,12 @@
audio_devices_t device = getDeviceForStrategy(strategy, false /*fromCache*/);
if ((attributes.flags & AUDIO_FLAG_HW_AV_SYNC) != 0) {
- flags = (audio_output_flags_t)(flags | AUDIO_OUTPUT_FLAG_HW_AV_SYNC);
+ *flags = (audio_output_flags_t)(*flags | AUDIO_OUTPUT_FLAG_HW_AV_SYNC);
}
ALOGV("getOutputForAttr() device 0x%x, sampling rate %d, format %#x, channel mask %#x, "
"flags %#x",
- device, config->sample_rate, config->format, config->channel_mask, flags);
+ device, config->sample_rate, config->format, config->channel_mask, *flags);
*output = getOutputForDevice(device, session, *stream, config, flags);
if (*output == AUDIO_IO_HANDLE_NONE) {
@@ -828,7 +828,7 @@
audio_session_t session,
audio_stream_type_t stream,
const audio_config_t *config,
- audio_output_flags_t flags)
+ audio_output_flags_t *flags)
{
audio_io_handle_t output = AUDIO_IO_HANDLE_NONE;
status_t status;
@@ -837,35 +837,41 @@
//force direct flag if offload flag is set: offloading implies a direct output stream
// and all common behaviors are driven by checking only the direct flag
// this should normally be set appropriately in the policy configuration file
- if ((flags & AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD) != 0) {
- flags = (audio_output_flags_t)(flags | AUDIO_OUTPUT_FLAG_DIRECT);
+ if ((*flags & AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD) != 0) {
+ *flags = (audio_output_flags_t)(*flags | AUDIO_OUTPUT_FLAG_DIRECT);
}
- if ((flags & AUDIO_OUTPUT_FLAG_HW_AV_SYNC) != 0) {
- flags = (audio_output_flags_t)(flags | AUDIO_OUTPUT_FLAG_DIRECT);
+ if ((*flags & AUDIO_OUTPUT_FLAG_HW_AV_SYNC) != 0) {
+ *flags = (audio_output_flags_t)(*flags | AUDIO_OUTPUT_FLAG_DIRECT);
}
// only allow deep buffering for music stream type
if (stream != AUDIO_STREAM_MUSIC) {
- flags = (audio_output_flags_t)(flags &~AUDIO_OUTPUT_FLAG_DEEP_BUFFER);
+ *flags = (audio_output_flags_t)(*flags &~AUDIO_OUTPUT_FLAG_DEEP_BUFFER);
} else if (/* stream == AUDIO_STREAM_MUSIC && */
- flags == AUDIO_OUTPUT_FLAG_NONE &&
+ *flags == AUDIO_OUTPUT_FLAG_NONE &&
property_get_bool("audio.deep_buffer.media", false /* default_value */)) {
// use DEEP_BUFFER as default output for music stream type
- flags = (audio_output_flags_t)AUDIO_OUTPUT_FLAG_DEEP_BUFFER;
+ *flags = (audio_output_flags_t)AUDIO_OUTPUT_FLAG_DEEP_BUFFER;
}
if (stream == AUDIO_STREAM_TTS) {
- flags = AUDIO_OUTPUT_FLAG_TTS;
+ *flags = AUDIO_OUTPUT_FLAG_TTS;
} else if (stream == AUDIO_STREAM_VOICE_CALL &&
audio_is_linear_pcm(config->format)) {
- flags = (audio_output_flags_t)(AUDIO_OUTPUT_FLAG_VOIP_RX |
+ *flags = (audio_output_flags_t)(AUDIO_OUTPUT_FLAG_VOIP_RX |
AUDIO_OUTPUT_FLAG_DIRECT);
ALOGV("Set VoIP and Direct output flags for PCM format");
+ } else if (device == AUDIO_DEVICE_OUT_TELEPHONY_TX &&
+ stream == AUDIO_STREAM_MUSIC &&
+ audio_is_linear_pcm(config->format) &&
+ isInCall()) {
+ *flags = (audio_output_flags_t)AUDIO_OUTPUT_FLAG_INCALL_MUSIC;
}
+
sp<IOProfile> profile;
// skip direct output selection if the request can obviously be attached to a mixed output
// and not explicitly requested
- if (((flags & AUDIO_OUTPUT_FLAG_DIRECT) == 0) &&
+ if (((*flags & AUDIO_OUTPUT_FLAG_DIRECT) == 0) &&
audio_is_linear_pcm(config->format) && config->sample_rate <= SAMPLE_RATE_HZ_MAX &&
audio_channel_count_from_out_mask(config->channel_mask) <= 2) {
goto non_direct_output;
@@ -878,13 +884,13 @@
// This may prevent offloading in rare situations where effects are left active by apps
// in the background.
- if (((flags & AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD) == 0) ||
+ if (((*flags & AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD) == 0) ||
!(mEffects.isNonOffloadableEffectEnabled() || mMasterMono)) {
profile = getProfileForDirectOutput(device,
config->sample_rate,
config->format,
config->channel_mask,
- (audio_output_flags_t)flags);
+ (audio_output_flags_t)*flags);
}
if (profile != 0) {
@@ -916,7 +922,7 @@
String8 address = outputDevices.size() > 0 ? outputDevices.itemAt(0)->mAddress
: String8("");
- status = outputDesc->open(config, device, address, stream, flags, &output);
+ status = outputDesc->open(config, device, address, stream, *flags, &output);
// only accept an output with the requested parameters
if (status != NO_ERROR ||
@@ -954,7 +960,7 @@
// A request for HW A/V sync cannot fallback to a mixed output because time
// stamps are embedded in audio data
- if ((flags & AUDIO_OUTPUT_FLAG_HW_AV_SYNC) != 0) {
+ if ((*flags & AUDIO_OUTPUT_FLAG_HW_AV_SYNC) != 0) {
return AUDIO_IO_HANDLE_NONE;
}
@@ -969,12 +975,12 @@
SortedVector<audio_io_handle_t> outputs = getOutputsForDevice(device, mOutputs);
// at this stage we should ignore the DIRECT flag as no direct output could be found earlier
- flags = (audio_output_flags_t)(flags & ~AUDIO_OUTPUT_FLAG_DIRECT);
- output = selectOutput(outputs, flags, config->format);
+ *flags = (audio_output_flags_t)(*flags & ~AUDIO_OUTPUT_FLAG_DIRECT);
+ output = selectOutput(outputs, *flags, config->format);
}
ALOGW_IF((output == 0), "getOutputForDevice() could not find output for stream %d, "
"sampling rate %d, format %#x, channels %#x, flags %#x",
- stream, config->sample_rate, config->format, config->channel_mask, flags);
+ stream, config->sample_rate, config->format, config->channel_mask, *flags);
return output;
}
@@ -1801,10 +1807,15 @@
status_t AudioPolicyManager::startInput(audio_io_handle_t input,
audio_session_t session,
+ bool silenced,
concurrency_type__mask_t *concurrency)
{
- ALOGV("startInput() input %d", input);
+
+ ALOGV("AudioPolicyManager::startInput(input:%d, session:%d, silenced:%d, concurrency:%d)",
+ input, session, silenced, *concurrency);
+
*concurrency = API_INPUT_CONCURRENCY_NONE;
+
ssize_t index = mInputs.indexOfKey(input);
if (index < 0) {
ALOGW("startInput() unknown input %d", input);
@@ -1839,12 +1850,33 @@
return INVALID_OPERATION;
}
- Vector< sp<AudioInputDescriptor> > activeInputs = mInputs.getActiveInputs();
- for (const auto& activeDesc : activeInputs) {
- if (is_virtual_input_device(activeDesc->mDevice)) {
- continue;
- }
+ Vector<sp<AudioInputDescriptor>> activeInputs = mInputs.getActiveInputs();
+ // If a UID is idle and records silence and another not silenced recording starts
+ // from another UID (idle or active) we stop the current idle UID recording in
+ // favor of the new one - "There can be only one" TM
+ if (!silenced) {
+ for (const auto& activeDesc : activeInputs) {
+ if ((audioSession->flags() & AUDIO_INPUT_FLAG_MMAP_NOIRQ) != 0 &&
+ activeDesc->getId() == inputDesc->getId()) {
+ continue;
+ }
+
+ AudioSessionCollection activeSessions = activeDesc->getAudioSessions(
+ true /*activeOnly*/);
+ sp<AudioSession> activeSession = activeSessions.valueAt(0);
+ if (activeSession->isSilenced()) {
+ audio_io_handle_t activeInput = activeDesc->mIoHandle;
+ audio_session_t activeSessionId = activeSession->session();
+ stopInput(activeInput, activeSessionId);
+ releaseInput(activeInput, activeSessionId);
+ ALOGV("startInput(%d) stopping silenced input %d", input, activeInput);
+ activeInputs = mInputs.getActiveInputs();
+ }
+ }
+ }
+
+ for (const auto& activeDesc : activeInputs) {
if ((audioSession->flags() & AUDIO_INPUT_FLAG_MMAP_NOIRQ) != 0 &&
activeDesc->getId() == inputDesc->getId()) {
continue;
@@ -1881,10 +1913,6 @@
// if capture is allowed, preempt currently active HOTWORD captures
for (const auto& activeDesc : activeInputs) {
- if (is_virtual_input_device(activeDesc->mDevice)) {
- continue;
- }
-
if (allowConcurrentWithSoundTrigger && activeDesc->isSoundTrigger()) {
continue;
}
@@ -1907,6 +1935,9 @@
}
#endif
+ // Make sure we start with the correct silence state
+ audioSession->setSilenced(silenced);
+
// increment activity count before calling getNewInputDevice() below as only active sessions
// are considered for device selection
audioSession->changeActiveCount(1);
@@ -2039,7 +2070,6 @@
void AudioPolicyManager::releaseInput(audio_io_handle_t input,
audio_session_t session)
{
-
ALOGV("releaseInput() %d", input);
ssize_t index = mInputs.indexOfKey(input);
if (index < 0) {
@@ -2123,7 +2153,10 @@
audio_devices_t device)
{
- if ((index < mVolumeCurves->getVolumeIndexMin(stream)) ||
+ // VOICE_CALL stream has minVolumeIndex > 0 but can be muted directly by an
+ // app that has MODIFY_PHONE_STATE permission.
+ if (((index < mVolumeCurves->getVolumeIndexMin(stream)) &&
+ !(stream == AUDIO_STREAM_VOICE_CALL && index == 0)) ||
(index > mVolumeCurves->getVolumeIndexMax(stream))) {
return BAD_VALUE;
}
@@ -2906,7 +2939,7 @@
sinkDeviceDesc->toAudioPortConfig(&newPatch.sinks[i], &patch->sinks[i]);
// create a software bridge in PatchPanel if:
- // - source and sink devices are on differnt HW modules OR
+ // - source and sink devices are on different HW modules OR
// - audio HAL version is < 3.0
if (!srcDeviceDesc->hasSameHwModuleAs(sinkDeviceDesc) ||
(srcDeviceDesc->mModule->getHalVersionMajor() < 3)) {
@@ -3391,6 +3424,23 @@
return computeVolume(stream, index, device);
}
+void AudioPolicyManager::setRecordSilenced(uid_t uid, bool silenced)
+{
+ ALOGV("AudioPolicyManager:setRecordSilenced(uid:%d, silenced:%d)", uid, silenced);
+
+ Vector<sp<AudioInputDescriptor> > activeInputs = mInputs.getActiveInputs();
+ for (size_t i = 0; i < activeInputs.size(); i++) {
+ sp<AudioInputDescriptor> activeDesc = activeInputs[i];
+ AudioSessionCollection activeSessions = activeDesc->getAudioSessions(true);
+ for (size_t j = 0; j < activeSessions.size(); j++) {
+ sp<AudioSession> activeSession = activeSessions.valueAt(j);
+ if (activeSession->uid() == uid) {
+ activeSession->setSilenced(silenced);
+ }
+ }
+ }
+}
+
status_t AudioPolicyManager::disconnectAudioSource(const sp<AudioSourceDescriptor>& sourceDesc)
{
ALOGV("%s handle %d", __FUNCTION__, sourceDesc->getHandle());
diff --git a/services/audiopolicy/managerdefault/AudioPolicyManager.h b/services/audiopolicy/managerdefault/AudioPolicyManager.h
index 611edec..2b68882 100644
--- a/services/audiopolicy/managerdefault/AudioPolicyManager.h
+++ b/services/audiopolicy/managerdefault/AudioPolicyManager.h
@@ -110,7 +110,7 @@
audio_stream_type_t *stream,
uid_t uid,
const audio_config_t *config,
- audio_output_flags_t flags,
+ audio_output_flags_t *flags,
audio_port_handle_t *selectedDeviceId,
audio_port_handle_t *portId);
virtual status_t startOutput(audio_io_handle_t output,
@@ -135,6 +135,7 @@
// indicates to the audio policy manager that the input starts being used.
virtual status_t startInput(audio_io_handle_t input,
audio_session_t session,
+ bool silenced,
concurrency_type__mask_t *concurrency);
// indicates to the audio policy manager that the input stops being used.
@@ -235,6 +236,8 @@
// return the strategy corresponding to a given stream type
routing_strategy getStrategy(audio_stream_type_t stream) const;
+ virtual void setRecordSilenced(uid_t uid, bool silenced);
+
protected:
// A constructor that allows more fine-grained control over initialization process,
// used in automatic tests.
@@ -626,7 +629,7 @@
audio_session_t session,
audio_stream_type_t stream,
const audio_config_t *config,
- audio_output_flags_t flags);
+ audio_output_flags_t *flags);
// internal method to return the input handle for the given device and format
audio_io_handle_t getInputForDevice(audio_devices_t device,
String8 address,
diff --git a/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp b/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp
index 7dd6d70..306de3f 100644
--- a/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp
+++ b/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp
@@ -158,6 +158,7 @@
audio_io_handle_t *output,
audio_session_t session,
audio_stream_type_t *stream,
+ pid_t pid,
uid_t uid,
const audio_config_t *config,
audio_output_flags_t flags,
@@ -176,9 +177,26 @@
"%s uid %d tried to pass itself off as %d", __FUNCTION__, callingUid, uid);
uid = callingUid;
}
- return mAudioPolicyManager->getOutputForAttr(attr, output, session, stream, uid,
+ audio_output_flags_t originalFlags = flags;
+ status_t result = mAudioPolicyManager->getOutputForAttr(attr, output, session, stream, uid,
config,
- flags, selectedDeviceId, portId);
+ &flags, selectedDeviceId, portId);
+
+ // FIXME: Introduce a way to check for the the telephony device before opening the output
+ if ((result == NO_ERROR) &&
+ (flags & AUDIO_OUTPUT_FLAG_INCALL_MUSIC) &&
+ !modifyPhoneStateAllowed(pid, uid)) {
+ // If the app tries to play music through the telephony device and doesn't have permission
+ // the fallback to the default output device.
+ mAudioPolicyManager->releaseOutput(*output, *stream, session);
+ flags = originalFlags;
+ *selectedDeviceId = AUDIO_PORT_HANDLE_NONE;
+ *portId = AUDIO_PORT_HANDLE_NONE;
+ result = mAudioPolicyManager->getOutputForAttr(attr, output, session, stream, uid,
+ config,
+ &flags, selectedDeviceId, portId);
+ }
+ return result;
}
status_t AudioPolicyService::startOutput(audio_io_handle_t output,
@@ -269,6 +287,7 @@
audio_session_t session,
pid_t pid,
uid_t uid,
+ const String16& opPackageName,
const audio_config_base_t *config,
audio_input_flags_t flags,
audio_port_handle_t *selectedDeviceId,
@@ -277,6 +296,7 @@
if (mAudioPolicyManager == NULL) {
return NO_INIT;
}
+
// already checked by client, but double-check in case the client wrapper is bypassed
if (attr->source < AUDIO_SOURCE_DEFAULT && attr->source >= AUDIO_SOURCE_CNT &&
attr->source != AUDIO_SOURCE_HOTWORD && attr->source != AUDIO_SOURCE_FM_TUNER) {
@@ -300,6 +320,13 @@
pid = callingPid;
}
+ // check calling permissions
+ if (!recordingAllowed(opPackageName, pid, uid)) {
+ ALOGE("%s permission denied: recording not allowed for uid %d pid %d",
+ __func__, uid, pid);
+ return PERMISSION_DENIED;
+ }
+
if ((attr->source == AUDIO_SOURCE_HOTWORD) && !captureHotwordAllowed(pid, uid)) {
return BAD_VALUE;
}
@@ -349,6 +376,13 @@
}
return status;
}
+
+ sp<AudioRecordClient> client =
+ new AudioRecordClient(*attr, *input, uid, pid, opPackageName, session);
+ client->active = false;
+ client->isConcurrent = false;
+ client->isVirtualDevice = false; //TODO : update from APM->getInputForAttr()
+ mAudioRecordClients.add(*portId, client);
}
if (audioPolicyEffects != 0) {
@@ -361,15 +395,38 @@
return NO_ERROR;
}
-status_t AudioPolicyService::startInput(audio_io_handle_t input,
- audio_session_t session)
+status_t AudioPolicyService::startInput(audio_port_handle_t portId, bool *silenced)
{
if (mAudioPolicyManager == NULL) {
return NO_INIT;
}
+ sp<AudioRecordClient> client;
+ {
+ Mutex::Autolock _l(mLock);
+
+ ssize_t index = mAudioRecordClients.indexOfKey(portId);
+ if (index < 0) {
+ return INVALID_OPERATION;
+ }
+ client = mAudioRecordClients.valueAt(index);
+ }
+
+ // check calling permissions
+ if (!recordingAllowed(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;
+ }
+
+ // If UID inactive it records silence until becoming active
+ *silenced = !mUidPolicy->isUidActive(client->uid) && !client->isVirtualDevice;
+
Mutex::Autolock _l(mLock);
- AudioPolicyInterface::concurrency_type__mask_t concurrency;
- status_t status = mAudioPolicyManager->startInput(input, session, &concurrency);
+ AudioPolicyInterface::concurrency_type__mask_t concurrency =
+ AudioPolicyInterface::API_INPUT_CONCURRENCY_NONE;
+
+ status_t status = mAudioPolicyManager->startInput(
+ client->input, client->session, *silenced, &concurrency);
if (status == NO_ERROR) {
LOG_ALWAYS_FATAL_IF(concurrency & ~AudioPolicyInterface::API_INPUT_CONCURRENCY_ALL,
@@ -387,38 +444,52 @@
return status;
}
-status_t AudioPolicyService::stopInput(audio_io_handle_t input,
- audio_session_t session)
+status_t AudioPolicyService::stopInput(audio_port_handle_t portId)
{
if (mAudioPolicyManager == NULL) {
return NO_INIT;
}
Mutex::Autolock _l(mLock);
- return mAudioPolicyManager->stopInput(input, session);
+ ssize_t index = mAudioRecordClients.indexOfKey(portId);
+ if (index < 0) {
+ return INVALID_OPERATION;
+ }
+ sp<AudioRecordClient> client = mAudioRecordClients.valueAt(index);
+
+ return mAudioPolicyManager->stopInput(client->input, client->session);
}
-void AudioPolicyService::releaseInput(audio_io_handle_t input,
- audio_session_t session)
+void AudioPolicyService::releaseInput(audio_port_handle_t portId)
{
if (mAudioPolicyManager == NULL) {
return;
}
sp<AudioPolicyEffects>audioPolicyEffects;
+ sp<AudioRecordClient> client;
{
Mutex::Autolock _l(mLock);
audioPolicyEffects = mAudioPolicyEffects;
+ ssize_t index = mAudioRecordClients.indexOfKey(portId);
+ if (index < 0) {
+ return;
+ }
+ client = mAudioRecordClients.valueAt(index);
+ mAudioRecordClients.removeItem(portId);
+ }
+ if (client == 0) {
+ return;
}
if (audioPolicyEffects != 0) {
// release audio processors from the input
- status_t status = audioPolicyEffects->releaseInputEffects(input, session);
+ status_t status = audioPolicyEffects->releaseInputEffects(client->input, client->session);
if(status != NO_ERROR) {
- ALOGW("Failed to release effects on input %d", input);
+ ALOGW("Failed to release effects on input %d", client->input);
}
}
{
Mutex::Autolock _l(mLock);
- mAudioPolicyManager->releaseInput(input, session);
+ mAudioPolicyManager->releaseInput(client->input, client->session);
}
}
diff --git a/services/audiopolicy/service/AudioPolicyService.cpp b/services/audiopolicy/service/AudioPolicyService.cpp
index af0c823..e5aed9a 100644
--- a/services/audiopolicy/service/AudioPolicyService.cpp
+++ b/services/audiopolicy/service/AudioPolicyService.cpp
@@ -28,6 +28,9 @@
#include <utils/Log.h>
#include <cutils/properties.h>
#include <binder/IPCThreadState.h>
+#include <binder/ActivityManager.h>
+#include <binder/PermissionController.h>
+#include <binder/IResultReceiver.h>
#include <utils/String16.h>
#include <utils/threads.h>
#include "AudioPolicyService.h"
@@ -39,6 +42,8 @@
#include <system/audio.h>
#include <system/audio_policy.h>
+#include <private/android_filesystem_config.h>
+
namespace android {
static const char kDeadlockedString[] = "AudioPolicyService may be deadlocked\n";
@@ -49,6 +54,7 @@
static const nsecs_t kAudioCommandTimeoutNs = seconds(3); // 3 seconds
+static const String16 sManageAudioPolicyPermission("android.permission.MANAGE_AUDIO_POLICY");
// ----------------------------------------------------------------------------
@@ -79,6 +85,9 @@
Mutex::Autolock _l(mLock);
mAudioPolicyEffects = audioPolicyEffects;
}
+
+ mUidPolicy = new UidPolicy(this);
+ mUidPolicy->registerSelf();
}
AudioPolicyService::~AudioPolicyService()
@@ -92,6 +101,9 @@
mNotificationClients.clear();
mAudioPolicyEffects.clear();
+
+ mUidPolicy->unregisterSelf();
+ mUidPolicy.clear();
}
// A notification client is always registered by AudioSystem when the client process
@@ -318,6 +330,20 @@
return NO_ERROR;
}
+void AudioPolicyService::setRecordSilenced(uid_t uid, bool silenced)
+{
+ {
+ Mutex::Autolock _l(mLock);
+ if (mAudioPolicyManager) {
+ mAudioPolicyManager->setRecordSilenced(uid, silenced);
+ }
+ }
+ sp<IAudioFlinger> af = AudioSystem::get_audio_flinger();
+ if (af) {
+ af->setRecordSilenced(uid, silenced);
+ }
+}
+
status_t AudioPolicyService::dump(int fd, const Vector<String16>& args __unused)
{
if (!dumpAllowed()) {
@@ -361,11 +387,210 @@
}
status_t AudioPolicyService::onTransact(
- uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
-{
+ uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) {
+ switch (code) {
+ case SHELL_COMMAND_TRANSACTION: {
+ int in = data.readFileDescriptor();
+ int out = data.readFileDescriptor();
+ int err = data.readFileDescriptor();
+ int argc = data.readInt32();
+ Vector<String16> args;
+ for (int i = 0; i < argc && data.dataAvail() > 0; i++) {
+ args.add(data.readString16());
+ }
+ sp<IBinder> unusedCallback;
+ sp<IResultReceiver> resultReceiver;
+ status_t status;
+ if ((status = data.readNullableStrongBinder(&unusedCallback)) != NO_ERROR) {
+ return status;
+ }
+ if ((status = data.readNullableStrongBinder(&resultReceiver)) != NO_ERROR) {
+ return status;
+ }
+ status = shellCommand(in, out, err, args);
+ if (resultReceiver != nullptr) {
+ resultReceiver->send(status);
+ }
+ return NO_ERROR;
+ }
+ }
+
return BnAudioPolicyService::onTransact(code, data, reply, flags);
}
+// ------------------- Shell command implementation -------------------
+
+// NOTE: This is a remote API - make sure all args are validated
+status_t AudioPolicyService::shellCommand(int in, int out, int err, Vector<String16>& args) {
+ if (!checkCallingPermission(sManageAudioPolicyPermission, nullptr, nullptr)) {
+ return PERMISSION_DENIED;
+ }
+ if (in == BAD_TYPE || out == BAD_TYPE || err == BAD_TYPE) {
+ return BAD_VALUE;
+ }
+ if (args.size() == 3 && args[0] == String16("set-uid-state")) {
+ return handleSetUidState(args, err);
+ } else if (args.size() == 2 && args[0] == String16("reset-uid-state")) {
+ return handleResetUidState(args, err);
+ } else if (args.size() == 2 && args[0] == String16("get-uid-state")) {
+ return handleGetUidState(args, out, err);
+ } else if (args.size() == 1 && args[0] == String16("help")) {
+ printHelp(out);
+ return NO_ERROR;
+ }
+ printHelp(err);
+ return BAD_VALUE;
+}
+
+status_t AudioPolicyService::handleSetUidState(Vector<String16>& args, int err) {
+ PermissionController pc;
+ int uid = pc.getPackageUid(args[1], 0);
+ if (uid <= 0) {
+ ALOGE("Unknown package: '%s'", String8(args[1]).string());
+ dprintf(err, "Unknown package: '%s'\n", String8(args[1]).string());
+ return BAD_VALUE;
+ }
+ bool active = false;
+ if (args[2] == String16("active")) {
+ active = true;
+ } else if ((args[2] != String16("idle"))) {
+ ALOGE("Expected active or idle but got: '%s'", String8(args[2]).string());
+ return BAD_VALUE;
+ }
+ mUidPolicy->addOverrideUid(uid, active);
+ return NO_ERROR;
+}
+
+status_t AudioPolicyService::handleResetUidState(Vector<String16>& args, int err) {
+ PermissionController pc;
+ int uid = pc.getPackageUid(args[1], 0);
+ if (uid < 0) {
+ ALOGE("Unknown package: '%s'", String8(args[1]).string());
+ dprintf(err, "Unknown package: '%s'\n", String8(args[1]).string());
+ return BAD_VALUE;
+ }
+ mUidPolicy->removeOverrideUid(uid);
+ return NO_ERROR;
+}
+
+status_t AudioPolicyService::handleGetUidState(Vector<String16>& args, int out, int err) {
+ PermissionController pc;
+ int uid = pc.getPackageUid(args[1], 0);
+ if (uid < 0) {
+ ALOGE("Unknown package: '%s'", String8(args[1]).string());
+ dprintf(err, "Unknown package: '%s'\n", String8(args[1]).string());
+ return BAD_VALUE;
+ }
+ if (mUidPolicy->isUidActive(uid)) {
+ return dprintf(out, "active\n");
+ } else {
+ return dprintf(out, "idle\n");
+ }
+}
+
+status_t AudioPolicyService::printHelp(int out) {
+ return dprintf(out, "Audio policy service commands:\n"
+ " get-uid-state <PACKAGE> gets the uid state\n"
+ " set-uid-state <PACKAGE> <active|idle> overrides the uid state\n"
+ " reset-uid-state <PACKAGE> clears the uid state override\n"
+ " help print this message\n");
+}
+
+// ----------- AudioPolicyService::UidPolicy implementation ----------
+
+void AudioPolicyService::UidPolicy::registerSelf() {
+ ActivityManager am;
+ am.registerUidObserver(this, ActivityManager::UID_OBSERVER_GONE
+ | ActivityManager::UID_OBSERVER_IDLE
+ | ActivityManager::UID_OBSERVER_ACTIVE,
+ ActivityManager::PROCESS_STATE_UNKNOWN,
+ String16("audioserver"));
+}
+
+void AudioPolicyService::UidPolicy::unregisterSelf() {
+ ActivityManager am;
+ am.unregisterUidObserver(this);
+}
+
+void AudioPolicyService::UidPolicy::onUidGone(uid_t uid, __unused bool disabled) {
+ onUidIdle(uid, disabled);
+}
+
+void AudioPolicyService::UidPolicy::onUidActive(uid_t uid) {
+ {
+ Mutex::Autolock _l(mUidLock);
+ mActiveUids.insert(uid);
+ }
+ sp<AudioPolicyService> service = mService.promote();
+ if (service != nullptr) {
+ service->setRecordSilenced(uid, false);
+ }
+}
+
+void AudioPolicyService::UidPolicy::onUidIdle(uid_t uid, __unused bool disabled) {
+ bool deleted = false;
+ {
+ Mutex::Autolock _l(mUidLock);
+ if (mActiveUids.erase(uid) > 0) {
+ deleted = true;
+ }
+ }
+ if (deleted) {
+ sp<AudioPolicyService> service = mService.promote();
+ if (service != nullptr) {
+ service->setRecordSilenced(uid, true);
+ }
+ }
+}
+
+void AudioPolicyService::UidPolicy::addOverrideUid(uid_t uid, bool active) {
+ updateOverrideUid(uid, active, true);
+}
+
+void AudioPolicyService::UidPolicy::removeOverrideUid(uid_t uid) {
+ updateOverrideUid(uid, false, false);
+}
+
+void AudioPolicyService::UidPolicy::updateOverrideUid(uid_t uid, bool active, bool insert) {
+ bool wasActive = false;
+ bool isActive = false;
+ {
+ Mutex::Autolock _l(mUidLock);
+ wasActive = isUidActiveLocked(uid);
+ mOverrideUids.erase(uid);
+ if (insert) {
+ mOverrideUids.insert(std::pair<uid_t, bool>(uid, active));
+ }
+ isActive = isUidActiveLocked(uid);
+ }
+ if (wasActive != isActive) {
+ sp<AudioPolicyService> service = mService.promote();
+ if (service != nullptr) {
+ service->setRecordSilenced(uid, !isActive);
+ }
+ }
+}
+
+bool AudioPolicyService::UidPolicy::isUidActive(uid_t uid) {
+ // Non-app UIDs are considered always active
+ if (uid < FIRST_APPLICATION_UID) {
+ return true;
+ }
+ Mutex::Autolock _l(mUidLock);
+ return isUidActiveLocked(uid);
+}
+
+bool AudioPolicyService::UidPolicy::isUidActiveLocked(uid_t uid) {
+ // Non-app UIDs are considered always active
+ if (uid < FIRST_APPLICATION_UID) {
+ return true;
+ }
+ auto it = mOverrideUids.find(uid);
+ if (it != mOverrideUids.end()) {
+ return it->second;
+ }
+ return mActiveUids.find(uid) != mActiveUids.end();
+}
// ----------- AudioPolicyService::AudioCommandThread implementation ----------
diff --git a/services/audiopolicy/service/AudioPolicyService.h b/services/audiopolicy/service/AudioPolicyService.h
index 833a230..bfa3ef4 100644
--- a/services/audiopolicy/service/AudioPolicyService.h
+++ b/services/audiopolicy/service/AudioPolicyService.h
@@ -24,6 +24,7 @@
#include <utils/Vector.h>
#include <utils/SortedVector.h>
#include <binder/BinderService.h>
+#include <binder/IUidObserver.h>
#include <system/audio.h>
#include <system/audio_policy.h>
#include <media/IAudioPolicyService.h>
@@ -33,9 +34,13 @@
#include "AudioPolicyEffects.h"
#include "managerdefault/AudioPolicyManager.h"
+#include <unordered_map>
+#include <unordered_set>
namespace android {
+using namespace std;
+
// ----------------------------------------------------------------------------
class AudioPolicyService :
@@ -73,6 +78,7 @@
audio_io_handle_t *output,
audio_session_t session,
audio_stream_type_t *stream,
+ pid_t pid,
uid_t uid,
const audio_config_t *config,
audio_output_flags_t flags,
@@ -92,16 +98,15 @@
audio_session_t session,
pid_t pid,
uid_t uid,
+ const String16& opPackageName,
const audio_config_base_t *config,
audio_input_flags_t flags,
audio_port_handle_t *selectedDeviceId = NULL,
audio_port_handle_t *portId = NULL);
- virtual status_t startInput(audio_io_handle_t input,
- audio_session_t session);
- virtual status_t stopInput(audio_io_handle_t input,
- audio_session_t session);
- virtual void releaseInput(audio_io_handle_t input,
- audio_session_t session);
+ virtual status_t startInput(audio_port_handle_t portId,
+ bool *silenced);
+ virtual status_t stopInput(audio_port_handle_t portId);
+ virtual void releaseInput(audio_port_handle_t portId);
virtual status_t initStreamVolume(audio_stream_type_t stream,
int indexMin,
int indexMax);
@@ -235,6 +240,57 @@
status_t dumpInternals(int fd);
+ // Handles binder shell commands
+ virtual status_t shellCommand(int in, int out, int err, Vector<String16>& args);
+
+ // Sets whether the given UID records only silence
+ virtual void setRecordSilenced(uid_t uid, bool silenced);
+
+ // Overrides the UID state as if it is idle
+ status_t handleSetUidState(Vector<String16>& args, int err);
+
+ // Clears the override for the UID state
+ status_t handleResetUidState(Vector<String16>& args, int err);
+
+ // Gets the UID state
+ status_t handleGetUidState(Vector<String16>& args, int out, int err);
+
+ // Prints the shell command help
+ status_t printHelp(int out);
+
+ // If recording we need to make sure the UID is allowed to do that. If the UID is idle
+ // then it cannot record and gets buffers with zeros - silence. As soon as the UID
+ // transitions to an active state we will start reporting buffers with data. This approach
+ // transparently handles recording while the UID transitions between idle/active state
+ // avoiding to get stuck in a state receiving non-empty buffers while idle or in a state
+ // receiving empty buffers while active.
+ class UidPolicy : public BnUidObserver {
+ public:
+ explicit UidPolicy(wp<AudioPolicyService> service)
+ : mService(service) {}
+
+ void registerSelf();
+ void unregisterSelf();
+
+ bool isUidActive(uid_t uid);
+
+ void onUidGone(uid_t uid, bool disabled);
+ void onUidActive(uid_t uid);
+ void onUidIdle(uid_t uid, bool disabled);
+
+ void addOverrideUid(uid_t uid, bool active);
+ void removeOverrideUid(uid_t uid);
+
+ private:
+ bool isUidActiveLocked(uid_t uid);
+ void updateOverrideUid(uid_t uid, bool active, bool insert);
+
+ Mutex mUidLock;
+ wp<AudioPolicyService> mService;
+ std::unordered_set<uid_t> mActiveUids;
+ std::unordered_map<uid_t, bool> mOverrideUids;
+ };
+
// Thread used for tone playback and to send audio config commands to audio flinger
// For tone playback, using a separate thread is necessary to avoid deadlock with mLock because
// startTone() and stopTone() are normally called with mLock locked and requesting a tone start
@@ -306,7 +362,6 @@
const audio_config_base_t *deviceConfig,
audio_patch_handle_t patchHandle);
void insertCommand_l(AudioCommand *command, int delayMs = 0);
-
private:
class AudioCommandData;
@@ -552,6 +607,31 @@
bool mAudioPortCallbacksEnabled;
};
+ // --- AudioRecordClient ---
+ // Information about each registered AudioRecord client
+ // (between calls to getInputForAttr() and releaseInput())
+ class AudioRecordClient : public RefBase {
+ public:
+ AudioRecordClient(const audio_attributes_t attributes,
+ const audio_io_handle_t input, uid_t uid, pid_t pid,
+ const String16& opPackageName, const audio_session_t session) :
+ attributes(attributes),
+ input(input), uid(uid), pid(pid),
+ opPackageName(opPackageName), session(session),
+ active(false), isConcurrent(false), isVirtualDevice(false) {}
+ virtual ~AudioRecordClient() {}
+
+ const audio_attributes_t attributes; // source, flags ...
+ const audio_io_handle_t input; // audio HAL input IO handle
+ const uid_t uid; // client UID
+ const pid_t pid; // client PID
+ const String16 opPackageName; // client package name
+ const audio_session_t session; // audio session ID
+ bool active; // Capture is active or inactive
+ bool isConcurrent; // is allowed to concurrent capture
+ bool isVirtualDevice; // uses vitual device: updated by APM::getInputForAttr()
+ };
+
// Internal dump utilities.
status_t dumpPermissionDenial(int fd);
@@ -575,6 +655,9 @@
// Manage all effects configured in audio_effects.conf
sp<AudioPolicyEffects> mAudioPolicyEffects;
audio_mode_t mPhoneState;
+
+ sp<UidPolicy> mUidPolicy;
+ DefaultKeyedVector< audio_port_handle_t, sp<AudioRecordClient> > mAudioRecordClients;
};
} // namespace android
diff --git a/services/camera/libcameraservice/Android.mk b/services/camera/libcameraservice/Android.mk
index aeaca48..7b86180 100644
--- a/services/camera/libcameraservice/Android.mk
+++ b/services/camera/libcameraservice/Android.mk
@@ -60,6 +60,7 @@
LOCAL_SHARED_LIBRARIES:= \
libui \
liblog \
+ libutilscallstack \
libutils \
libbinder \
libcutils \
diff --git a/services/camera/libcameraservice/CameraService.cpp b/services/camera/libcameraservice/CameraService.cpp
index 6abfa81..536b54d 100644
--- a/services/camera/libcameraservice/CameraService.cpp
+++ b/services/camera/libcameraservice/CameraService.cpp
@@ -33,14 +33,18 @@
#include <android-base/macros.h>
#include <android-base/parseint.h>
+#include <binder/ActivityManager.h>
#include <binder/AppOpsManager.h>
#include <binder/IPCThreadState.h>
#include <binder/IServiceManager.h>
#include <binder/MemoryBase.h>
#include <binder/MemoryHeapBase.h>
+#include <binder/PermissionController.h>
#include <binder/ProcessInfoService.h>
+#include <binder/IResultReceiver.h>
#include <cutils/atomic.h>
#include <cutils/properties.h>
+#include <cutils/misc.h>
#include <gui/Surface.h>
#include <hardware/hardware.h>
#include <memunreachable/memunreachable.h>
@@ -165,6 +169,8 @@
// ----------------------------------------------------------------------------
+static const String16 sManageCameraPermission("android.permission.MANAGE_CAMERA");
+
CameraService::CameraService() :
mEventLog(DEFAULT_EVENT_LOG_LENGTH),
mNumberOfCameras(0), mNumberOfNormalCameras(0),
@@ -196,6 +202,9 @@
}
CameraService::pingCameraServiceProxy();
+
+ mUidPolicy = new UidPolicy(this);
+ mUidPolicy->registerSelf();
}
status_t CameraService::enumerateProviders() {
@@ -275,6 +284,7 @@
CameraService::~CameraService() {
VendorTagDescriptor::clearGlobalVendorTagDescriptor();
+ mUidPolicy->unregisterSelf();
}
void CameraService::onNewProviderRegistered() {
@@ -306,6 +316,17 @@
logDeviceAdded(id, "Device added");
}
+void CameraService::removeStates(const String8 id) {
+ if (mFlashlight->hasFlashUnit(id)) {
+ mTorchStatusMap.removeItem(id);
+ }
+
+ {
+ Mutex::Autolock lock(mCameraStatesLock);
+ mCameraStates.erase(id);
+ }
+}
+
void CameraService::onDeviceStatusChanged(const String8& id,
CameraDeviceStatus newHalStatus) {
ALOGI("%s: Status changed for cameraId=%s, newStatus=%d", __FUNCTION__,
@@ -370,6 +391,7 @@
clientToDisconnect->disconnect();
}
+ removeStates(id);
} else {
if (oldStatus == StatusInternal::NOT_PRESENT) {
logDeviceAdded(id, String8::format("Device status changed from %d to %d", oldStatus,
@@ -933,6 +955,15 @@
clientName8.string(), clientUid, clientPid, cameraId.string());
}
+ // Make sure the UID is in an active state to use the camera
+ if (!mUidPolicy->isUidActive(callingUid)) {
+ ALOGE("Access Denial: can't use the camera from an idle UID pid=%d, uid=%d",
+ clientPid, clientUid);
+ return STATUS_ERROR_FMT(ERROR_DISABLED,
+ "Caller \"%s\" (PID %d, UID %d) cannot open camera \"%s\" from background",
+ clientName8.string(), clientUid, clientPid, cameraId.string());
+ }
+
// Only use passed in clientPid to check permission. Use calling PID as the client PID that's
// connected to camera service directly.
originalClientPid = clientPid;
@@ -1969,6 +2000,30 @@
// Permission checks
switch (code) {
+ case SHELL_COMMAND_TRANSACTION: {
+ int in = data.readFileDescriptor();
+ int out = data.readFileDescriptor();
+ int err = data.readFileDescriptor();
+ int argc = data.readInt32();
+ Vector<String16> args;
+ for (int i = 0; i < argc && data.dataAvail() > 0; i++) {
+ args.add(data.readString16());
+ }
+ sp<IBinder> unusedCallback;
+ sp<IResultReceiver> resultReceiver;
+ status_t status;
+ if ((status = data.readNullableStrongBinder(&unusedCallback)) != NO_ERROR) {
+ return status;
+ }
+ if ((status = data.readNullableStrongBinder(&resultReceiver)) != NO_ERROR) {
+ return status;
+ }
+ status = shellCommand(in, out, err, args);
+ if (resultReceiver != nullptr) {
+ resultReceiver->send(status);
+ }
+ return NO_ERROR;
+ }
case BnCameraService::NOTIFYSYSTEMEVENT: {
if (pid != selfPid) {
// Ensure we're being called by system_server, or similar process with
@@ -2286,15 +2341,21 @@
if (res != AppOpsManager::MODE_ALLOWED) {
ALOGI("Camera %s: Access for \"%s\" revoked", mCameraIdStr.string(),
myName.string());
- // Reset the client PID to allow server-initiated disconnect,
- // and to prevent further calls by client.
- mClientPid = getCallingPid();
- CaptureResultExtras resultExtras; // a dummy result (invalid)
- notifyError(hardware::camera2::ICameraDeviceCallbacks::ERROR_CAMERA_SERVICE, resultExtras);
- disconnect();
+ block();
}
}
+void CameraService::BasicClient::block() {
+ ATRACE_CALL();
+
+ // Reset the client PID to allow server-initiated disconnect,
+ // and to prevent further calls by client.
+ mClientPid = getCallingPid();
+ CaptureResultExtras resultExtras; // a dummy result (invalid)
+ notifyError(hardware::camera2::ICameraDeviceCallbacks::ERROR_CAMERA_DISABLED, resultExtras);
+ disconnect();
+}
+
// ----------------------------------------------------------------------------
void CameraService::Client::notifyError(int32_t errorCode,
@@ -2331,6 +2392,98 @@
}
// ----------------------------------------------------------------------------
+// UidPolicy
+// ----------------------------------------------------------------------------
+
+void CameraService::UidPolicy::registerSelf() {
+ ActivityManager am;
+ am.registerUidObserver(this, ActivityManager::UID_OBSERVER_GONE
+ | ActivityManager::UID_OBSERVER_IDLE
+ | ActivityManager::UID_OBSERVER_ACTIVE,
+ ActivityManager::PROCESS_STATE_UNKNOWN,
+ String16("cameraserver"));
+}
+
+void CameraService::UidPolicy::unregisterSelf() {
+ ActivityManager am;
+ am.unregisterUidObserver(this);
+}
+
+void CameraService::UidPolicy::onUidGone(uid_t uid, bool disabled) {
+ onUidIdle(uid, disabled);
+}
+
+void CameraService::UidPolicy::onUidActive(uid_t uid) {
+ Mutex::Autolock _l(mUidLock);
+ mActiveUids.insert(uid);
+}
+
+void CameraService::UidPolicy::onUidIdle(uid_t uid, bool /* disabled */) {
+ bool deleted = false;
+ {
+ Mutex::Autolock _l(mUidLock);
+ if (mActiveUids.erase(uid) > 0) {
+ deleted = true;
+ }
+ }
+ if (deleted) {
+ sp<CameraService> service = mService.promote();
+ if (service != nullptr) {
+ service->blockClientsForUid(uid);
+ }
+ }
+}
+
+bool CameraService::UidPolicy::isUidActive(uid_t uid) {
+ // Non-app UIDs are considered always active
+ if (uid < FIRST_APPLICATION_UID) {
+ return true;
+ }
+ Mutex::Autolock _l(mUidLock);
+ return isUidActiveLocked(uid);
+}
+
+bool CameraService::UidPolicy::isUidActiveLocked(uid_t uid) {
+ // Non-app UIDs are considered always active
+ if (uid < FIRST_APPLICATION_UID) {
+ return true;
+ }
+ auto it = mOverrideUids.find(uid);
+ if (it != mOverrideUids.end()) {
+ return it->second;
+ }
+ return mActiveUids.find(uid) != mActiveUids.end();
+}
+
+void CameraService::UidPolicy::UidPolicy::addOverrideUid(uid_t uid, bool active) {
+ updateOverrideUid(uid, active, true);
+}
+
+void CameraService::UidPolicy::removeOverrideUid(uid_t uid) {
+ updateOverrideUid(uid, false, false);
+}
+
+void CameraService::UidPolicy::updateOverrideUid(uid_t uid, bool active, bool insert) {
+ bool wasActive = false;
+ bool isActive = false;
+ {
+ Mutex::Autolock _l(mUidLock);
+ wasActive = isUidActiveLocked(uid);
+ mOverrideUids.erase(uid);
+ if (insert) {
+ mOverrideUids.insert(std::pair<uid_t, bool>(uid, active));
+ }
+ isActive = isUidActiveLocked(uid);
+ }
+ if (wasActive != isActive && !isActive) {
+ sp<CameraService> service = mService.promote();
+ if (service != nullptr) {
+ service->blockClientsForUid(uid);
+ }
+ }
+}
+
+// ----------------------------------------------------------------------------
// CameraState
// ----------------------------------------------------------------------------
@@ -2791,4 +2944,92 @@
return OK;
}
+void CameraService::blockClientsForUid(uid_t uid) {
+ const auto clients = mActiveClientManager.getAll();
+ for (auto& current : clients) {
+ if (current != nullptr) {
+ const auto basicClient = current->getValue();
+ if (basicClient.get() != nullptr && basicClient->getClientUid() == uid) {
+ basicClient->block();
+ }
+ }
+ }
+}
+
+// NOTE: This is a remote API - make sure all args are validated
+status_t CameraService::shellCommand(int in, int out, int err, const Vector<String16>& args) {
+ if (!checkCallingPermission(sManageCameraPermission, nullptr, nullptr)) {
+ return PERMISSION_DENIED;
+ }
+ if (in == BAD_TYPE || out == BAD_TYPE || err == BAD_TYPE) {
+ return BAD_VALUE;
+ }
+ if (args.size() == 3 && args[0] == String16("set-uid-state")) {
+ return handleSetUidState(args, err);
+ } else if (args.size() == 2 && args[0] == String16("reset-uid-state")) {
+ return handleResetUidState(args, err);
+ } else if (args.size() == 2 && args[0] == String16("get-uid-state")) {
+ return handleGetUidState(args, out, err);
+ } else if (args.size() == 1 && args[0] == String16("help")) {
+ printHelp(out);
+ return NO_ERROR;
+ }
+ printHelp(err);
+ return BAD_VALUE;
+}
+
+status_t CameraService::handleSetUidState(const Vector<String16>& args, int err) {
+ PermissionController pc;
+ int uid = pc.getPackageUid(args[1], 0);
+ if (uid <= 0) {
+ ALOGE("Unknown package: '%s'", String8(args[1]).string());
+ dprintf(err, "Unknown package: '%s'\n", String8(args[1]).string());
+ return BAD_VALUE;
+ }
+ bool active = false;
+ if (args[2] == String16("active")) {
+ active = true;
+ } else if ((args[2] != String16("idle"))) {
+ ALOGE("Expected active or idle but got: '%s'", String8(args[2]).string());
+ return BAD_VALUE;
+ }
+ mUidPolicy->addOverrideUid(uid, active);
+ return NO_ERROR;
+}
+
+status_t CameraService::handleResetUidState(const Vector<String16>& args, int err) {
+ PermissionController pc;
+ int uid = pc.getPackageUid(args[1], 0);
+ if (uid < 0) {
+ ALOGE("Unknown package: '%s'", String8(args[1]).string());
+ dprintf(err, "Unknown package: '%s'\n", String8(args[1]).string());
+ return BAD_VALUE;
+ }
+ mUidPolicy->removeOverrideUid(uid);
+ return NO_ERROR;
+}
+
+status_t CameraService::handleGetUidState(const Vector<String16>& args, int out, int err) {
+ PermissionController pc;
+ int uid = pc.getPackageUid(args[1], 0);
+ if (uid <= 0) {
+ ALOGE("Unknown package: '%s'", String8(args[1]).string());
+ dprintf(err, "Unknown package: '%s'\n", String8(args[1]).string());
+ return BAD_VALUE;
+ }
+ if (mUidPolicy->isUidActive(uid)) {
+ return dprintf(out, "active\n");
+ } else {
+ return dprintf(out, "idle\n");
+ }
+}
+
+status_t CameraService::printHelp(int out) {
+ return dprintf(out, "Camera service commands:\n"
+ " get-uid-state <PACKAGE> gets the uid state\n"
+ " set-uid-state <PACKAGE> <active|idle> overrides the uid state\n"
+ " reset-uid-state <PACKAGE> clears the uid state override\n"
+ " help print this message\n");
+}
+
}; // namespace android
diff --git a/services/camera/libcameraservice/CameraService.h b/services/camera/libcameraservice/CameraService.h
index e9373a6..67db7ec 100644
--- a/services/camera/libcameraservice/CameraService.h
+++ b/services/camera/libcameraservice/CameraService.h
@@ -27,6 +27,7 @@
#include <binder/AppOpsManager.h>
#include <binder/BinderService.h>
#include <binder/IAppOpsCallback.h>
+#include <binder/IUidObserver.h>
#include <hardware/camera.h>
#include <android/hardware/camera/common/1.0/types.h>
@@ -47,6 +48,8 @@
#include <map>
#include <memory>
#include <utility>
+#include <unordered_map>
+#include <unordered_set>
namespace android {
@@ -163,6 +166,8 @@
virtual status_t dump(int fd, const Vector<String16>& args);
+ virtual status_t shellCommand(int in, int out, int err, const Vector<String16>& args);
+
/////////////////////////////////////////////////////////////////////
// Client functionality
@@ -233,6 +238,9 @@
// Check what API level is used for this client. This is used to determine which
// superclass this can be cast to.
virtual bool canCastToApiClient(apiLevel level) const;
+
+ // Block the client form using the camera
+ virtual void block();
protected:
BasicClient(const sp<CameraService>& cameraService,
const sp<IBinder>& remoteCallback,
@@ -506,14 +514,46 @@
CameraParameters mShimParams;
}; // class CameraState
+ // Observer for UID lifecycle enforcing that UIDs in idle
+ // state cannot use the camera to protect user privacy.
+ class UidPolicy : public BnUidObserver {
+ public:
+ explicit UidPolicy(sp<CameraService> service)
+ : mService(service) {}
+
+ void registerSelf();
+ void unregisterSelf();
+
+ bool isUidActive(uid_t uid);
+
+ void onUidGone(uid_t uid, bool disabled);
+ void onUidActive(uid_t uid);
+ void onUidIdle(uid_t uid, bool disabled);
+
+ void addOverrideUid(uid_t uid, bool active);
+ void removeOverrideUid(uid_t uid);
+
+ private:
+ bool isUidActiveLocked(uid_t uid);
+ void updateOverrideUid(uid_t uid, bool active, bool insert);
+
+ Mutex mUidLock;
+ wp<CameraService> mService;
+ std::unordered_set<uid_t> mActiveUids;
+ std::unordered_map<uid_t, bool> mOverrideUids;
+ }; // class UidPolicy
+
+ sp<UidPolicy> mUidPolicy;
+
// Delay-load the Camera HAL module
virtual void onFirstRef();
// Eumerate all camera providers in the system
status_t enumerateProviders();
- // Add a new camera to camera and torch state lists
+ // Add a new camera to camera and torch state lists or remove an unplugged one
void addStates(const String8 id);
+ void removeStates(const String8 id);
// Check if we can connect, before we acquire the service lock.
// The returned originalClientPid is the PID of the original process that wants to connect to
@@ -755,6 +795,21 @@
*/
binder::Status getLegacyParametersLazy(int cameraId, /*out*/CameraParameters* parameters);
+ // Blocks all clients from the UID
+ void blockClientsForUid(uid_t uid);
+
+ // Overrides the UID state as if it is idle
+ status_t handleSetUidState(const Vector<String16>& args, int err);
+
+ // Clears the override for the UID state
+ status_t handleResetUidState(const Vector<String16>& args, int err);
+
+ // Gets the UID state
+ status_t handleGetUidState(const Vector<String16>& args, int out, int err);
+
+ // Prints the shell command help
+ status_t printHelp(int out);
+
static int getCallingPid();
static int getCallingUid();
diff --git a/services/camera/libcameraservice/api1/CameraClient.cpp b/services/camera/libcameraservice/api1/CameraClient.cpp
index 910dd78..e848a3f 100644
--- a/services/camera/libcameraservice/api1/CameraClient.cpp
+++ b/services/camera/libcameraservice/api1/CameraClient.cpp
@@ -508,7 +508,7 @@
void CameraClient::releaseRecordingFrameHandle(native_handle_t *handle) {
if (handle == nullptr) return;
-
+ Mutex::Autolock lock(mLock);
sp<IMemory> dataPtr;
{
Mutex::Autolock l(mAvailableCallbackBuffersLock);
@@ -532,17 +532,22 @@
return;
}
- VideoNativeHandleMetadata *metadata = (VideoNativeHandleMetadata*)(dataPtr->pointer());
- metadata->eType = kMetadataBufferTypeNativeHandleSource;
- metadata->pHandle = handle;
-
- mHardware->releaseRecordingFrame(dataPtr);
+ if (mHardware != nullptr) {
+ VideoNativeHandleMetadata *metadata = (VideoNativeHandleMetadata*)(dataPtr->pointer());
+ metadata->eType = kMetadataBufferTypeNativeHandleSource;
+ metadata->pHandle = handle;
+ mHardware->releaseRecordingFrame(dataPtr);
+ }
}
void CameraClient::releaseRecordingFrameHandleBatch(const std::vector<native_handle_t*>& handles) {
+ Mutex::Autolock lock(mLock);
+ bool disconnected = (mHardware == nullptr);
size_t n = handles.size();
std::vector<sp<IMemory>> frames;
- frames.reserve(n);
+ if (!disconnected) {
+ frames.reserve(n);
+ }
bool error = false;
for (auto& handle : handles) {
sp<IMemory> dataPtr;
@@ -566,10 +571,12 @@
break;
}
- VideoNativeHandleMetadata *metadata = (VideoNativeHandleMetadata*)(dataPtr->pointer());
- metadata->eType = kMetadataBufferTypeNativeHandleSource;
- metadata->pHandle = handle;
- frames.push_back(dataPtr);
+ if (!disconnected) {
+ VideoNativeHandleMetadata *metadata = (VideoNativeHandleMetadata*)(dataPtr->pointer());
+ metadata->eType = kMetadataBufferTypeNativeHandleSource;
+ metadata->pHandle = handle;
+ frames.push_back(dataPtr);
+ }
}
if (error) {
@@ -577,7 +584,7 @@
native_handle_close(handle);
native_handle_delete(handle);
}
- } else {
+ } else if (!disconnected) {
mHardware->releaseRecordingFrameBatch(frames);
}
return;
diff --git a/services/camera/libcameraservice/api1/client2/CallbackProcessor.cpp b/services/camera/libcameraservice/api1/client2/CallbackProcessor.cpp
index 0d2dba1..a71a732 100644
--- a/services/camera/libcameraservice/api1/client2/CallbackProcessor.cpp
+++ b/services/camera/libcameraservice/api1/client2/CallbackProcessor.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2012-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.
@@ -154,7 +154,8 @@
callbackFormat, params.previewFormat);
res = device->createStream(mCallbackWindow,
params.previewWidth, params.previewHeight, callbackFormat,
- HAL_DATASPACE_V0_JFIF, CAMERA3_STREAM_ROTATION_0, &mCallbackStreamId);
+ HAL_DATASPACE_V0_JFIF, CAMERA3_STREAM_ROTATION_0, &mCallbackStreamId,
+ String8());
if (res != OK) {
ALOGE("%s: Camera %d: Can't create output stream for callbacks: "
"%s (%d)", __FUNCTION__, mId,
diff --git a/services/camera/libcameraservice/api1/client2/JpegProcessor.cpp b/services/camera/libcameraservice/api1/client2/JpegProcessor.cpp
index d1bbdaf..cc4249f 100755
--- a/services/camera/libcameraservice/api1/client2/JpegProcessor.cpp
+++ b/services/camera/libcameraservice/api1/client2/JpegProcessor.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2012-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.
@@ -168,7 +168,8 @@
res = device->createStream(mCaptureWindow,
params.pictureWidth, params.pictureHeight,
HAL_PIXEL_FORMAT_BLOB, HAL_DATASPACE_V0_JFIF,
- CAMERA3_STREAM_ROTATION_0, &mCaptureStreamId);
+ CAMERA3_STREAM_ROTATION_0, &mCaptureStreamId,
+ String8());
if (res != OK) {
ALOGE("%s: Camera %d: Can't create output stream for capture: "
"%s (%d)", __FUNCTION__, mId,
diff --git a/services/camera/libcameraservice/api1/client2/StreamingProcessor.cpp b/services/camera/libcameraservice/api1/client2/StreamingProcessor.cpp
index 73dca73..0786f53 100644
--- a/services/camera/libcameraservice/api1/client2/StreamingProcessor.cpp
+++ b/services/camera/libcameraservice/api1/client2/StreamingProcessor.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2012-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.
@@ -194,7 +194,7 @@
res = device->createStream(mPreviewWindow,
params.previewWidth, params.previewHeight,
CAMERA2_HAL_PIXEL_FORMAT_OPAQUE, HAL_DATASPACE_UNKNOWN,
- CAMERA3_STREAM_ROTATION_0, &mPreviewStreamId);
+ CAMERA3_STREAM_ROTATION_0, &mPreviewStreamId, String8());
if (res != OK) {
ALOGE("%s: Camera %d: Unable to create preview stream: %s (%d)",
__FUNCTION__, mId, strerror(-res), res);
@@ -379,7 +379,8 @@
res = device->createStream(mRecordingWindow,
params.videoWidth, params.videoHeight,
params.videoFormat, params.videoDataSpace,
- CAMERA3_STREAM_ROTATION_0, &mRecordingStreamId);
+ CAMERA3_STREAM_ROTATION_0, &mRecordingStreamId,
+ String8());
if (res != OK) {
ALOGE("%s: Camera %d: Can't create output stream for recording: "
"%s (%d)", __FUNCTION__, mId,
diff --git a/services/camera/libcameraservice/api1/client2/ZslProcessor.cpp b/services/camera/libcameraservice/api1/client2/ZslProcessor.cpp
index b0607fb..372a2c5 100644
--- a/services/camera/libcameraservice/api1/client2/ZslProcessor.cpp
+++ b/services/camera/libcameraservice/api1/client2/ZslProcessor.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2013-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.
@@ -311,7 +311,8 @@
res = device->createStream(outSurface, params.fastInfo.arrayWidth,
params.fastInfo.arrayHeight, HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED,
- HAL_DATASPACE_UNKNOWN, CAMERA3_STREAM_ROTATION_0, &mZslStreamId);
+ HAL_DATASPACE_UNKNOWN, CAMERA3_STREAM_ROTATION_0, &mZslStreamId,
+ String8());
if (res != OK) {
ALOGE("%s: Camera %d: Can't create ZSL stream: "
"%s (%d)", __FUNCTION__, client->getCameraId(),
diff --git a/services/camera/libcameraservice/api2/CameraDeviceClient.cpp b/services/camera/libcameraservice/api2/CameraDeviceClient.cpp
index 5cbc158..1ebaea9 100644
--- a/services/camera/libcameraservice/api2/CameraDeviceClient.cpp
+++ b/services/camera/libcameraservice/api2/CameraDeviceClient.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2013-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.
@@ -175,7 +175,7 @@
return STATUS_ERROR(CameraService::ERROR_ILLEGAL_ARGUMENT, "Empty request list");
}
- List<const CameraMetadata> metadataRequestList;
+ List<const CameraDeviceBase::PhysicalCameraSettingsList> metadataRequestList;
std::list<const SurfaceMap> surfaceMapList;
submitInfo->mRequestId = mRequestIdCounter;
uint32_t loopCounter = 0;
@@ -193,28 +193,72 @@
mCameraIdStr.string());
return STATUS_ERROR(CameraService::ERROR_ILLEGAL_ARGUMENT,
"Repeating reprocess requests not supported");
+ } else if (request.mPhysicalCameraSettings.size() > 1) {
+ ALOGE("%s: Camera %s: reprocess requests not supported for "
+ "multiple physical cameras.", __FUNCTION__,
+ mCameraIdStr.string());
+ return STATUS_ERROR(CameraService::ERROR_ILLEGAL_ARGUMENT,
+ "Reprocess requests not supported for multiple cameras");
}
}
- CameraMetadata metadata(request.mMetadata);
- if (metadata.isEmpty()) {
- ALOGE("%s: Camera %s: Sent empty metadata packet. Rejecting request.",
- __FUNCTION__, mCameraIdStr.string());
+ if (request.mPhysicalCameraSettings.empty()) {
+ ALOGE("%s: Camera %s: request doesn't contain any settings.", __FUNCTION__,
+ mCameraIdStr.string());
return STATUS_ERROR(CameraService::ERROR_ILLEGAL_ARGUMENT,
- "Request settings are empty");
- } else if (request.mSurfaceList.isEmpty() && request.mStreamIdxList.size() == 0) {
+ "Request doesn't contain any settings");
+ }
+
+ //The first capture settings should always match the logical camera id
+ String8 logicalId(request.mPhysicalCameraSettings.begin()->id.c_str());
+ if (mDevice->getId() != logicalId) {
+ ALOGE("%s: Camera %s: Invalid camera request settings.", __FUNCTION__,
+ mCameraIdStr.string());
+ return STATUS_ERROR(CameraService::ERROR_ILLEGAL_ARGUMENT,
+ "Invalid camera request settings");
+ }
+
+ CameraDeviceBase::PhysicalCameraSettingsList physicalSettingsList;
+ for (const auto& it : request.mPhysicalCameraSettings) {
+ String8 physicalId(it.id.c_str());
+ if ((physicalId != mDevice->getId()) && !checkPhysicalCameraId(physicalId)) {
+ ALOGE("%s: Camera %s: Physical camera id: %s is invalid.", __FUNCTION__,
+ mCameraIdStr.string(), physicalId.string());
+ return STATUS_ERROR(CameraService::ERROR_ILLEGAL_ARGUMENT,
+ "Invalid physical camera id");
+ }
+
+ CameraMetadata metadata(it.settings);
+ if (metadata.isEmpty()) {
+ ALOGE("%s: Camera %s: Sent empty metadata packet. Rejecting request.",
+ __FUNCTION__, mCameraIdStr.string());
+ return STATUS_ERROR(CameraService::ERROR_ILLEGAL_ARGUMENT,
+ "Request settings are empty");
+ }
+
+ if (!enforceRequestPermissions(metadata)) {
+ // Callee logs
+ return STATUS_ERROR(CameraService::ERROR_PERMISSION_DENIED,
+ "Caller does not have permission to change restricted controls");
+ }
+
+ physicalSettingsList.push_back({it.id, metadata});
+ }
+
+ if (streaming && (physicalSettingsList.size() > 1)) {
+ ALOGE("%s: Camera %s: Individual physical camera settings are not supported in "
+ "streaming requests. Rejecting request.", __FUNCTION__, mCameraIdStr.string());
+ return STATUS_ERROR(CameraService::ERROR_ILLEGAL_ARGUMENT,
+ "Streaming request contains individual physical requests");
+ }
+
+ if (request.mSurfaceList.isEmpty() && request.mStreamIdxList.size() == 0) {
ALOGE("%s: Camera %s: Requests must have at least one surface target. "
"Rejecting request.", __FUNCTION__, mCameraIdStr.string());
return STATUS_ERROR(CameraService::ERROR_ILLEGAL_ARGUMENT,
"Request has no output targets");
}
- if (!enforceRequestPermissions(metadata)) {
- // Callee logs
- return STATUS_ERROR(CameraService::ERROR_PERMISSION_DENIED,
- "Caller does not have permission to change restricted controls");
- }
-
/**
* Write in the output stream IDs and map from stream ID to surface ID
* which we calculate from the capture request's list of surface target
@@ -261,20 +305,22 @@
}
}
- metadata.update(ANDROID_REQUEST_OUTPUT_STREAMS, &outputStreamIds[0],
- outputStreamIds.size());
+ physicalSettingsList.begin()->metadata.update(ANDROID_REQUEST_OUTPUT_STREAMS,
+ &outputStreamIds[0], outputStreamIds.size());
if (request.mIsReprocess) {
- metadata.update(ANDROID_REQUEST_INPUT_STREAMS, &mInputStream.id, 1);
+ physicalSettingsList.begin()->metadata.update(ANDROID_REQUEST_INPUT_STREAMS,
+ &mInputStream.id, 1);
}
- metadata.update(ANDROID_REQUEST_ID, &(submitInfo->mRequestId), /*size*/1);
+ physicalSettingsList.begin()->metadata.update(ANDROID_REQUEST_ID,
+ &(submitInfo->mRequestId), /*size*/1);
loopCounter++; // loopCounter starts from 1
ALOGV("%s: Camera %s: Creating request with ID %d (%d of %zu)",
__FUNCTION__, mCameraIdStr.string(), submitInfo->mRequestId,
loopCounter, requests.size());
- metadataRequestList.push_back(metadata);
+ metadataRequestList.push_back(physicalSettingsList);
surfaceMapList.push_back(surfaceMap);
}
mRequestIdCounter++;
@@ -508,6 +554,7 @@
size_t numBufferProducers = bufferProducers.size();
bool deferredConsumer = outputConfiguration.isDeferred();
bool isShared = outputConfiguration.isShared();
+ String8 physicalCameraId = String8(outputConfiguration.getPhysicalCameraId());
if (numBufferProducers > MAX_SURFACES_PER_STREAM) {
ALOGE("%s: GraphicBufferProducer count %zu for stream exceeds limit of %d",
@@ -529,6 +576,12 @@
return STATUS_ERROR(CameraService::ERROR_DISCONNECTED, "Camera device no longer alive");
}
+ if (!checkPhysicalCameraId(physicalCameraId)) {
+ String8 msg = String8::format("Camera %s: Camera doesn't support physicalCameraId %s.",
+ mCameraIdStr.string(), physicalCameraId.string());
+ ALOGE("%s: %s", __FUNCTION__, msg.string());
+ return STATUS_ERROR(CameraService::ERROR_ILLEGAL_ARGUMENT, msg.string());
+ }
std::vector<sp<Surface>> surfaces;
std::vector<sp<IBinder>> binders;
status_t err;
@@ -578,7 +631,8 @@
err = mDevice->createStream(surfaces, deferredConsumer, streamInfo.width,
streamInfo.height, streamInfo.format, streamInfo.dataSpace,
static_cast<camera3_stream_rotation_t>(outputConfiguration.getRotation()),
- &streamId, &surfaceIds, outputConfiguration.getSurfaceSetID(), isShared);
+ &streamId, physicalCameraId, &surfaceIds, outputConfiguration.getSurfaceSetID(),
+ isShared);
if (err != OK) {
res = STATUS_ERROR_FMT(CameraService::ERROR_INVALID_OPERATION,
@@ -640,10 +694,12 @@
int streamId = camera3::CAMERA3_STREAM_ID_INVALID;
std::vector<sp<Surface>> noSurface;
std::vector<int> surfaceIds;
+ String8 physicalCameraId(outputConfiguration.getPhysicalCameraId());
err = mDevice->createStream(noSurface, /*hasDeferredConsumer*/true, width,
height, format, dataSpace,
static_cast<camera3_stream_rotation_t>(outputConfiguration.getRotation()),
- &streamId, &surfaceIds, outputConfiguration.getSurfaceSetID(), isShared,
+ &streamId, physicalCameraId, &surfaceIds,
+ outputConfiguration.getSurfaceSetID(), isShared,
consumerUsage);
if (err != OK) {
@@ -1059,6 +1115,43 @@
return binder::Status::ok();
}
+bool CameraDeviceClient::checkPhysicalCameraId(const String8& physicalCameraId) {
+ if (0 == physicalCameraId.size()) {
+ return true;
+ }
+
+ CameraMetadata staticInfo = mDevice->info();
+ camera_metadata_entry_t entryCap;
+ bool isLogicalCam = false;
+
+ entryCap = staticInfo.find(ANDROID_REQUEST_AVAILABLE_CAPABILITIES);
+ for (size_t i = 0; i < entryCap.count; ++i) {
+ uint8_t capability = entryCap.data.u8[i];
+ if (capability == ANDROID_REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA) {
+ isLogicalCam = true;
+ }
+ }
+ if (!isLogicalCam) {
+ return false;
+ }
+
+ camera_metadata_entry_t entryIds = staticInfo.find(ANDROID_LOGICAL_MULTI_CAMERA_PHYSICAL_IDS);
+ const uint8_t* ids = entryIds.data.u8;
+ size_t start = 0;
+ for (size_t i = 0; i < entryIds.count; ++i) {
+ if (ids[i] == '\0') {
+ if (start != i) {
+ String8 currentId((const char*)ids+start);
+ if (currentId == physicalCameraId) {
+ return true;
+ }
+ }
+ start = i+1;
+ }
+ }
+ return false;
+}
+
bool CameraDeviceClient::roundBufferDimensionNearest(int32_t width, int32_t height,
int32_t format, android_dataspace dataSpace, const CameraMetadata& info,
/*out*/int32_t* outWidth, /*out*/int32_t* outHeight) {
@@ -1604,7 +1697,8 @@
// Thread-safe. No lock necessary.
sp<hardware::camera2::ICameraDeviceCallbacks> remoteCb = mRemoteCallback;
if (remoteCb != NULL) {
- remoteCb->onResultReceived(result.mMetadata, result.mResultExtras);
+ remoteCb->onResultReceived(result.mMetadata, result.mResultExtras,
+ result.mPhysicalMetadatas);
}
}
diff --git a/services/camera/libcameraservice/api2/CameraDeviceClient.h b/services/camera/libcameraservice/api2/CameraDeviceClient.h
index 4086c72..14aeed0 100644
--- a/services/camera/libcameraservice/api2/CameraDeviceClient.h
+++ b/services/camera/libcameraservice/api2/CameraDeviceClient.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2013-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.
@@ -262,6 +262,10 @@
/*out*/SurfaceMap* surfaceMap,
/*out*/Vector<int32_t>* streamIds);
+ // Check that the physicalCameraId passed in is spported by the camera
+ // device.
+ bool checkPhysicalCameraId(const String8& physicalCameraId);
+
// IGraphicsBufferProducer binder -> Stream ID + Surface ID for output streams
KeyedVector<sp<IBinder>, StreamSurfaceId> mStreamMap;
diff --git a/services/camera/libcameraservice/common/CameraDeviceBase.h b/services/camera/libcameraservice/common/CameraDeviceBase.h
index 3fd6921..7956be5 100644
--- a/services/camera/libcameraservice/common/CameraDeviceBase.h
+++ b/services/camera/libcameraservice/common/CameraDeviceBase.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2013-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.
@@ -65,6 +65,12 @@
*/
virtual const CameraMetadata& info() const = 0;
+ struct PhysicalCameraSettings {
+ std::string cameraId;
+ CameraMetadata metadata;
+ };
+ typedef List<PhysicalCameraSettings> PhysicalCameraSettingsList;
+
/**
* Submit request for capture. The CameraDevice takes ownership of the
* passed-in buffer.
@@ -76,7 +82,7 @@
* Submit a list of requests.
* Output lastFrameNumber is the expected last frame number of the list of requests.
*/
- virtual status_t captureList(const List<const CameraMetadata> &requests,
+ virtual status_t captureList(const List<const PhysicalCameraSettingsList> &requests,
const std::list<const SurfaceMap> &surfaceMaps,
int64_t *lastFrameNumber = NULL) = 0;
@@ -92,7 +98,7 @@
* Submit a list of requests for streaming.
* Output lastFrameNumber is the last frame number of the previous streaming request.
*/
- virtual status_t setStreamingRequestList(const List<const CameraMetadata> &requests,
+ virtual status_t setStreamingRequestList(const List<const PhysicalCameraSettingsList> &requests,
const std::list<const SurfaceMap> &surfaceMaps,
int64_t *lastFrameNumber = NULL) = 0;
@@ -119,6 +125,7 @@
virtual status_t createStream(sp<Surface> consumer,
uint32_t width, uint32_t height, int format,
android_dataspace dataSpace, camera3_stream_rotation_t rotation, int *id,
+ const String8& physicalCameraId,
std::vector<int> *surfaceIds = nullptr,
int streamSetId = camera3::CAMERA3_STREAM_SET_ID_INVALID,
bool isShared = false, uint64_t consumerUsage = 0) = 0;
@@ -133,6 +140,7 @@
virtual status_t createStream(const std::vector<sp<Surface>>& consumers,
bool hasDeferredConsumer, uint32_t width, uint32_t height, int format,
android_dataspace dataSpace, camera3_stream_rotation_t rotation, int *id,
+ const String8& physicalCameraId,
std::vector<int> *surfaceIds = nullptr,
int streamSetId = camera3::CAMERA3_STREAM_SET_ID_INVALID,
bool isShared = false, uint64_t consumerUsage = 0) = 0;
diff --git a/services/camera/libcameraservice/common/CameraProviderManager.cpp b/services/camera/libcameraservice/common/CameraProviderManager.cpp
index 2ff200d..70e7761 100644
--- a/services/camera/libcameraservice/common/CameraProviderManager.cpp
+++ b/services/camera/libcameraservice/common/CameraProviderManager.cpp
@@ -35,6 +35,7 @@
// Hardcoded name for the passthrough HAL implementation, since it can't be discovered via the
// service manager
const std::string kLegacyProviderName("legacy/0");
+const std::string kExternalProviderName("external/0");
// Slash-separated list of provider types to consider for use via the old camera API
const std::string kStandardProviderTypes("internal/legacy");
@@ -69,6 +70,7 @@
// See if there's a passthrough HAL, but let's not complain if there's not
addProviderLocked(kLegacyProviderName, /*expected*/ false);
+ addProviderLocked(kExternalProviderName, /*expected*/ false);
return OK;
}
@@ -601,6 +603,15 @@
return OK;
}
+void CameraProviderManager::ProviderInfo::removeDevice(std::string id) {
+ for (auto it = mDevices.begin(); it != mDevices.end(); it++) {
+ if ((*it)->mId == id) {
+ mDevices.erase(it);
+ break;
+ }
+ }
+}
+
status_t CameraProviderManager::ProviderInfo::dump(int fd, const Vector<String16>&) const {
dprintf(fd, "== Camera Provider HAL %s (v2.4, %s) static info: %zu devices: ==\n",
mProviderName.c_str(), mInterface->isRemote() ? "remote" : "passthrough",
@@ -682,6 +693,8 @@
return hardware::Void();
}
addDevice(cameraDeviceName, newStatus, &id);
+ } else if (newStatus == CameraDeviceStatus::NOT_PRESENT) {
+ removeDevice(id);
}
listener = mManager->getStatusListener();
}
diff --git a/services/camera/libcameraservice/common/CameraProviderManager.h b/services/camera/libcameraservice/common/CameraProviderManager.h
index 0f1f07b..d02abb0 100644
--- a/services/camera/libcameraservice/common/CameraProviderManager.h
+++ b/services/camera/libcameraservice/common/CameraProviderManager.h
@@ -387,6 +387,8 @@
// Generate vendor tag id
static metadata_vendor_id_t generateVendorTagId(const std::string &name);
+
+ void removeDevice(std::string id);
};
// Utility to find a DeviceInfo by ID; pointer is only valid while mInterfaceMutex is held
diff --git a/services/camera/libcameraservice/device3/Camera3Device.cpp b/services/camera/libcameraservice/device3/Camera3Device.cpp
index d99fc1d..b868fa6 100644
--- a/services/camera/libcameraservice/device3/Camera3Device.cpp
+++ b/services/camera/libcameraservice/device3/Camera3Device.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2013-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.
@@ -39,6 +39,8 @@
#include <inttypes.h>
+#include <utility>
+
#include <utils/Log.h>
#include <utils/Trace.h>
#include <utils/Timers.h>
@@ -55,8 +57,6 @@
#include "device3/Camera3SharedOutputStream.h"
#include "CameraService.h"
-#include <android/hardware/camera/device/3.4/ICameraDeviceSession.h>
-
using namespace android::camera3;
using namespace android::hardware::camera;
using namespace android::hardware::camera::device::V3_2;
@@ -194,8 +194,14 @@
mTagMonitor.initialize(mVendorTagId);
+ Vector<int32_t> sessionParamKeys;
+ camera_metadata_entry_t sessionKeysEntry = mDeviceInfo.find(
+ ANDROID_REQUEST_AVAILABLE_SESSION_KEYS);
+ if (sessionKeysEntry.count > 0) {
+ sessionParamKeys.insertArrayAt(sessionKeysEntry.data.i32, 0, sessionKeysEntry.count);
+ }
/** Start up request queue thread */
- mRequestThread = new RequestThread(this, mStatusTracker, mInterface);
+ mRequestThread = new RequestThread(this, mStatusTracker, mInterface, sessionParamKeys);
res = mRequestThread->run(String8::format("C3Dev-%s-ReqQueue", mId.string()).string());
if (res != OK) {
SET_ERR_L("Unable to start request queue thread: %s (%d)",
@@ -663,13 +669,15 @@
}
if (dumpTemplates) {
- const char *templateNames[] = {
+ const char *templateNames[CAMERA3_TEMPLATE_COUNT] = {
"TEMPLATE_PREVIEW",
"TEMPLATE_STILL_CAPTURE",
"TEMPLATE_VIDEO_RECORD",
"TEMPLATE_VIDEO_SNAPSHOT",
"TEMPLATE_ZERO_SHUTTER_LAG",
- "TEMPLATE_MANUAL"
+ "TEMPLATE_MANUAL",
+ "TEMPLATE_MOTION_TRACKING_PREVIEW",
+ "TEMPALTE_MOTION_TRACKING_BEST"
};
for (int i = 1; i < CAMERA3_TEMPLATE_COUNT; i++) {
@@ -735,7 +743,7 @@
}
status_t Camera3Device::convertMetadataListToRequestListLocked(
- const List<const CameraMetadata> &metadataList,
+ const List<const PhysicalCameraSettingsList> &metadataList,
const std::list<const SurfaceMap> &surfaceMaps,
bool repeating,
RequestList *requestList) {
@@ -745,7 +753,7 @@
}
int32_t burstId = 0;
- List<const CameraMetadata>::const_iterator metadataIt = metadataList.begin();
+ List<const PhysicalCameraSettingsList>::const_iterator metadataIt = metadataList.begin();
std::list<const SurfaceMap>::const_iterator surfaceMapIt = surfaceMaps.begin();
for (; metadataIt != metadataList.end() && surfaceMapIt != surfaceMaps.end();
++metadataIt, ++surfaceMapIt) {
@@ -759,12 +767,13 @@
// Setup burst Id and request Id
newRequest->mResultExtras.burstId = burstId++;
- if (metadataIt->exists(ANDROID_REQUEST_ID)) {
- if (metadataIt->find(ANDROID_REQUEST_ID).count == 0) {
+ if (metadataIt->begin()->metadata.exists(ANDROID_REQUEST_ID)) {
+ if (metadataIt->begin()->metadata.find(ANDROID_REQUEST_ID).count == 0) {
CLOGE("RequestID entry exists; but must not be empty in metadata");
return BAD_VALUE;
}
- newRequest->mResultExtras.requestId = metadataIt->find(ANDROID_REQUEST_ID).data.i32[0];
+ newRequest->mResultExtras.requestId = metadataIt->begin()->metadata.find(
+ ANDROID_REQUEST_ID).data.i32[0];
} else {
CLOGE("RequestID does not exist in metadata");
return BAD_VALUE;
@@ -796,17 +805,19 @@
status_t Camera3Device::capture(CameraMetadata &request, int64_t* /*lastFrameNumber*/) {
ATRACE_CALL();
- List<const CameraMetadata> requests;
+ List<const PhysicalCameraSettingsList> requestsList;
std::list<const SurfaceMap> surfaceMaps;
- convertToRequestList(requests, surfaceMaps, request);
+ convertToRequestList(requestsList, surfaceMaps, request);
- return captureList(requests, surfaceMaps, /*lastFrameNumber*/NULL);
+ return captureList(requestsList, surfaceMaps, /*lastFrameNumber*/NULL);
}
-void Camera3Device::convertToRequestList(List<const CameraMetadata>& requests,
+void Camera3Device::convertToRequestList(List<const PhysicalCameraSettingsList>& requestsList,
std::list<const SurfaceMap>& surfaceMaps,
const CameraMetadata& request) {
- requests.push_back(request);
+ PhysicalCameraSettingsList requestList;
+ requestList.push_back({std::string(getId().string()), request});
+ requestsList.push_back(requestList);
SurfaceMap surfaceMap;
camera_metadata_ro_entry streams = request.find(ANDROID_REQUEST_OUTPUT_STREAMS);
@@ -819,7 +830,7 @@
}
status_t Camera3Device::submitRequestsHelper(
- const List<const CameraMetadata> &requests,
+ const List<const PhysicalCameraSettingsList> &requests,
const std::list<const SurfaceMap> &surfaceMaps,
bool repeating,
/*out*/
@@ -865,11 +876,9 @@
return res;
}
-// Only one processCaptureResult should be called at a time, so
-// the locks won't block. The locks are present here simply to enforce this.
-hardware::Return<void> Camera3Device::processCaptureResult(
+hardware::Return<void> Camera3Device::processCaptureResult_3_4(
const hardware::hidl_vec<
- hardware::camera::device::V3_2::CaptureResult>& results) {
+ hardware::camera::device::V3_4::CaptureResult>& results) {
// Ideally we should grab mLock, but that can lead to deadlock, and
// it's not super important to get up to date value of mStatus for this
// warning print, hence skipping the lock here
@@ -893,45 +902,121 @@
}
}
for (const auto& result : results) {
- processOneCaptureResultLocked(result);
+ processOneCaptureResultLocked(result.v3_2, result.physicalCameraMetadata);
}
mProcessCaptureResultLock.unlock();
return hardware::Void();
}
+// Only one processCaptureResult should be called at a time, so
+// the locks won't block. The locks are present here simply to enforce this.
+hardware::Return<void> Camera3Device::processCaptureResult(
+ const hardware::hidl_vec<
+ hardware::camera::device::V3_2::CaptureResult>& results) {
+ hardware::hidl_vec<hardware::camera::device::V3_4::PhysicalCameraMetadata> noPhysMetadata;
+
+ // Ideally we should grab mLock, but that can lead to deadlock, and
+ // it's not super important to get up to date value of mStatus for this
+ // warning print, hence skipping the lock here
+ if (mStatus == STATUS_ERROR) {
+ // Per API contract, HAL should act as closed after device error
+ // But mStatus can be set to error by framework as well, so just log
+ // a warning here.
+ ALOGW("%s: received capture result in error state.", __FUNCTION__);
+ }
+
+ if (mProcessCaptureResultLock.tryLock() != OK) {
+ // This should never happen; it indicates a wrong client implementation
+ // that doesn't follow the contract. But, we can be tolerant here.
+ ALOGE("%s: callback overlapped! waiting 1s...",
+ __FUNCTION__);
+ if (mProcessCaptureResultLock.timedLock(1000000000 /* 1s */) != OK) {
+ ALOGE("%s: cannot acquire lock in 1s, dropping results",
+ __FUNCTION__);
+ // really don't know what to do, so bail out.
+ return hardware::Void();
+ }
+ }
+ for (const auto& result : results) {
+ processOneCaptureResultLocked(result, noPhysMetadata);
+ }
+ mProcessCaptureResultLock.unlock();
+ return hardware::Void();
+}
+
+status_t Camera3Device::readOneCameraMetadataLocked(
+ uint64_t fmqResultSize, hardware::camera::device::V3_2::CameraMetadata& resultMetadata,
+ const hardware::camera::device::V3_2::CameraMetadata& result) {
+ if (fmqResultSize > 0) {
+ resultMetadata.resize(fmqResultSize);
+ if (mResultMetadataQueue == nullptr) {
+ return NO_MEMORY; // logged in initialize()
+ }
+ if (!mResultMetadataQueue->read(resultMetadata.data(), fmqResultSize)) {
+ ALOGE("%s: Cannot read camera metadata from fmq, size = %" PRIu64,
+ __FUNCTION__, fmqResultSize);
+ return INVALID_OPERATION;
+ }
+ } else {
+ resultMetadata.setToExternal(const_cast<uint8_t *>(result.data()),
+ result.size());
+ }
+
+ if (resultMetadata.size() != 0) {
+ status_t res;
+ const camera_metadata_t* metadata =
+ reinterpret_cast<const camera_metadata_t*>(resultMetadata.data());
+ size_t expected_metadata_size = resultMetadata.size();
+ if ((res = validate_camera_metadata_structure(metadata, &expected_metadata_size)) != OK) {
+ ALOGE("%s: Invalid camera metadata received by camera service from HAL: %s (%d)",
+ __FUNCTION__, strerror(-res), res);
+ return INVALID_OPERATION;
+ }
+ }
+
+ return OK;
+}
+
void Camera3Device::processOneCaptureResultLocked(
- const hardware::camera::device::V3_2::CaptureResult& result) {
+ const hardware::camera::device::V3_2::CaptureResult& result,
+ const hardware::hidl_vec<
+ hardware::camera::device::V3_4::PhysicalCameraMetadata> physicalCameraMetadatas) {
camera3_capture_result r;
status_t res;
r.frame_number = result.frameNumber;
+ // Read and validate the result metadata.
hardware::camera::device::V3_2::CameraMetadata resultMetadata;
- if (result.fmqResultSize > 0) {
- resultMetadata.resize(result.fmqResultSize);
- if (mResultMetadataQueue == nullptr) {
- return; // logged in initialize()
- }
- if (!mResultMetadataQueue->read(resultMetadata.data(), result.fmqResultSize)) {
- ALOGE("%s: Frame %d: Cannot read camera metadata from fmq, size = %" PRIu64,
- __FUNCTION__, result.frameNumber, result.fmqResultSize);
- return;
- }
- } else {
- resultMetadata.setToExternal(const_cast<uint8_t *>(result.result.data()),
- result.result.size());
+ res = readOneCameraMetadataLocked(result.fmqResultSize, resultMetadata, result.result);
+ if (res != OK) {
+ ALOGE("%s: Frame %d: Failed to read capture result metadata",
+ __FUNCTION__, result.frameNumber);
+ return;
}
+ r.result = reinterpret_cast<const camera_metadata_t*>(resultMetadata.data());
- if (resultMetadata.size() != 0) {
- r.result = reinterpret_cast<const camera_metadata_t*>(resultMetadata.data());
- size_t expected_metadata_size = resultMetadata.size();
- if ((res = validate_camera_metadata_structure(r.result, &expected_metadata_size)) != OK) {
- ALOGE("%s: Frame %d: Invalid camera metadata received by camera service from HAL: %s (%d)",
- __FUNCTION__, result.frameNumber, strerror(-res), res);
+ // Read and validate physical camera metadata
+ size_t physResultCount = physicalCameraMetadatas.size();
+ std::vector<const char*> physCamIds(physResultCount);
+ std::vector<const camera_metadata_t *> phyCamMetadatas(physResultCount);
+ std::vector<hardware::camera::device::V3_2::CameraMetadata> physResultMetadata;
+ physResultMetadata.resize(physResultCount);
+ for (size_t i = 0; i < physicalCameraMetadatas.size(); i++) {
+ res = readOneCameraMetadataLocked(physicalCameraMetadatas[i].fmqMetadataSize,
+ physResultMetadata[i], physicalCameraMetadatas[i].metadata);
+ if (res != OK) {
+ ALOGE("%s: Frame %d: Failed to read capture result metadata for camera %s",
+ __FUNCTION__, result.frameNumber,
+ physicalCameraMetadatas[i].physicalCameraId.c_str());
return;
}
- } else {
- r.result = nullptr;
+ physCamIds[i] = physicalCameraMetadatas[i].physicalCameraId.c_str();
+ phyCamMetadatas[i] = reinterpret_cast<const camera_metadata_t*>(
+ physResultMetadata[i].data());
}
+ r.num_physcam_metadata = physResultCount;
+ r.physcam_ids = physCamIds.data();
+ r.physcam_metadata = phyCamMetadatas.data();
std::vector<camera3_stream_buffer_t> outputBuffers(result.outputBuffers.size());
std::vector<buffer_handle_t> outputBufferHandles(result.outputBuffers.size());
@@ -1069,42 +1154,43 @@
notify(&m);
}
-status_t Camera3Device::captureList(const List<const CameraMetadata> &requests,
+status_t Camera3Device::captureList(const List<const PhysicalCameraSettingsList> &requestsList,
const std::list<const SurfaceMap> &surfaceMaps,
int64_t *lastFrameNumber) {
ATRACE_CALL();
- return submitRequestsHelper(requests, surfaceMaps, /*repeating*/false, lastFrameNumber);
+ return submitRequestsHelper(requestsList, surfaceMaps, /*repeating*/false, lastFrameNumber);
}
status_t Camera3Device::setStreamingRequest(const CameraMetadata &request,
int64_t* /*lastFrameNumber*/) {
ATRACE_CALL();
- List<const CameraMetadata> requests;
+ List<const PhysicalCameraSettingsList> requestsList;
std::list<const SurfaceMap> surfaceMaps;
- convertToRequestList(requests, surfaceMaps, request);
+ convertToRequestList(requestsList, surfaceMaps, request);
- return setStreamingRequestList(requests, /*surfaceMap*/surfaceMaps,
+ return setStreamingRequestList(requestsList, /*surfaceMap*/surfaceMaps,
/*lastFrameNumber*/NULL);
}
-status_t Camera3Device::setStreamingRequestList(const List<const CameraMetadata> &requests,
- const std::list<const SurfaceMap> &surfaceMaps,
- int64_t *lastFrameNumber) {
+status_t Camera3Device::setStreamingRequestList(
+ const List<const PhysicalCameraSettingsList> &requestsList,
+ const std::list<const SurfaceMap> &surfaceMaps, int64_t *lastFrameNumber) {
ATRACE_CALL();
- return submitRequestsHelper(requests, surfaceMaps, /*repeating*/true, lastFrameNumber);
+ return submitRequestsHelper(requestsList, surfaceMaps, /*repeating*/true, lastFrameNumber);
}
sp<Camera3Device::CaptureRequest> Camera3Device::setUpRequestLocked(
- const CameraMetadata &request, const SurfaceMap &surfaceMap) {
+ const PhysicalCameraSettingsList &request, const SurfaceMap &surfaceMap) {
status_t res;
if (mStatus == STATUS_UNCONFIGURED || mNeedConfig) {
// This point should only be reached via API1 (API2 must explicitly call configureStreams)
// so unilaterally select normal operating mode.
- res = configureStreamsLocked(CAMERA3_STREAM_CONFIGURATION_NORMAL_MODE, mSessionParams);
+ res = filterParamsAndConfigureLocked(request.begin()->metadata,
+ CAMERA3_STREAM_CONFIGURATION_NORMAL_MODE);
// Stream configuration failed. Client might try other configuraitons.
if (res != OK) {
CLOGE("Can't set up streams: %s (%d)", strerror(-res), res);
@@ -1224,6 +1310,7 @@
status_t Camera3Device::createStream(sp<Surface> consumer,
uint32_t width, uint32_t height, int format,
android_dataspace dataSpace, camera3_stream_rotation_t rotation, int *id,
+ const String8& physicalCameraId,
std::vector<int> *surfaceIds, int streamSetId, bool isShared, uint64_t consumerUsage) {
ATRACE_CALL();
@@ -1236,12 +1323,14 @@
consumers.push_back(consumer);
return createStream(consumers, /*hasDeferredConsumer*/ false, width, height,
- format, dataSpace, rotation, id, surfaceIds, streamSetId, isShared, consumerUsage);
+ format, dataSpace, rotation, id, physicalCameraId, surfaceIds, streamSetId,
+ isShared, consumerUsage);
}
status_t Camera3Device::createStream(const std::vector<sp<Surface>>& consumers,
bool hasDeferredConsumer, uint32_t width, uint32_t height, int format,
android_dataspace dataSpace, camera3_stream_rotation_t rotation, int *id,
+ const String8& physicalCameraId,
std::vector<int> *surfaceIds, int streamSetId, bool isShared, uint64_t consumerUsage) {
ATRACE_CALL();
@@ -1249,8 +1338,9 @@
nsecs_t maxExpectedDuration = getExpectedInFlightDuration();
Mutex::Autolock l(mLock);
ALOGV("Camera %s: Creating new stream %d: %d x %d, format %d, dataspace %d rotation %d"
- " consumer usage %" PRIu64 ", isShared %d", mId.string(), mNextStreamId, width, height, format,
- dataSpace, rotation, consumerUsage, isShared);
+ " consumer usage %" PRIu64 ", isShared %d, physicalCameraId %s", mId.string(),
+ mNextStreamId, width, height, format, dataSpace, rotation, consumerUsage, isShared,
+ physicalCameraId.string());
status_t res;
bool wasActive = false;
@@ -1310,7 +1400,7 @@
}
newStream = new Camera3OutputStream(mNextStreamId, consumers[0],
width, height, blobBufferSize, format, dataSpace, rotation,
- mTimestampOffset, streamSetId);
+ mTimestampOffset, physicalCameraId, streamSetId);
} else if (format == HAL_PIXEL_FORMAT_RAW_OPAQUE) {
ssize_t rawOpaqueBufferSize = getRawOpaqueBufferSize(width, height);
if (rawOpaqueBufferSize <= 0) {
@@ -1319,19 +1409,19 @@
}
newStream = new Camera3OutputStream(mNextStreamId, consumers[0],
width, height, rawOpaqueBufferSize, format, dataSpace, rotation,
- mTimestampOffset, streamSetId);
+ mTimestampOffset, physicalCameraId, streamSetId);
} else if (isShared) {
newStream = new Camera3SharedOutputStream(mNextStreamId, consumers,
width, height, format, consumerUsage, dataSpace, rotation,
- mTimestampOffset, streamSetId);
+ mTimestampOffset, physicalCameraId, streamSetId);
} else if (consumers.size() == 0 && hasDeferredConsumer) {
newStream = new Camera3OutputStream(mNextStreamId,
width, height, format, consumerUsage, dataSpace, rotation,
- mTimestampOffset, streamSetId);
+ mTimestampOffset, physicalCameraId, streamSetId);
} else {
newStream = new Camera3OutputStream(mNextStreamId, consumers[0],
width, height, format, dataSpace, rotation,
- mTimestampOffset, streamSetId);
+ mTimestampOffset, physicalCameraId, streamSetId);
}
size_t consumerCount = consumers.size();
@@ -1508,11 +1598,20 @@
Mutex::Autolock il(mInterfaceLock);
Mutex::Autolock l(mLock);
+ return filterParamsAndConfigureLocked(sessionParams, operatingMode);
+}
+
+status_t Camera3Device::filterParamsAndConfigureLocked(const CameraMetadata& sessionParams,
+ int operatingMode) {
//Filter out any incoming session parameters
const CameraMetadata params(sessionParams);
- CameraMetadata filteredParams;
camera_metadata_entry_t availableSessionKeys = mDeviceInfo.find(
ANDROID_REQUEST_AVAILABLE_SESSION_KEYS);
+ CameraMetadata filteredParams(availableSessionKeys.count);
+ camera_metadata_t *meta = const_cast<camera_metadata_t *>(
+ filteredParams.getAndLock());
+ set_camera_metadata_vendor_id(meta, mVendorTagId);
+ filteredParams.unlock(meta);
if (availableSessionKeys.count > 0) {
for (size_t i = 0; i < availableSessionKeys.count; i++) {
camera_metadata_ro_entry entry = params.find(
@@ -1645,10 +1744,16 @@
mStatusChanged.broadcast();
}
+void Camera3Device::pauseStateNotify(bool enable) {
+ Mutex::Autolock il(mInterfaceLock);
+ Mutex::Autolock l(mLock);
+
+ mPauseStateNotify = enable;
+}
+
// Pause to reconfigure
status_t Camera3Device::internalPauseAndWaitLocked(nsecs_t maxExpectedDuration) {
mRequestThread->setPaused(true);
- mPauseStateNotify = true;
ALOGV("%s: Camera %s: Internal wait until idle (% " PRIi64 " ns)", __FUNCTION__, mId.string(),
maxExpectedDuration);
@@ -1667,6 +1772,8 @@
mRequestThread->setPaused(false);
+ ALOGV("%s: Camera %s: Internal wait until active (% " PRIi64 " ns)", __FUNCTION__, mId.string(),
+ kActiveTimeout);
res = waitUntilStateThenRelock(/*active*/ true, kActiveTimeout);
if (res != OK) {
SET_ERR_L("Can't transition to active in %f seconds!",
@@ -1775,6 +1882,7 @@
CaptureResult &result = *(mResultQueue.begin());
frame->mResultExtras = result.mResultExtras;
frame->mMetadata.acquire(result.mMetadata);
+ frame->mPhysicalMetadatas = std::move(result.mPhysicalMetadatas);
mResultQueue.erase(mResultQueue.begin());
return OK;
@@ -1947,8 +2055,8 @@
if (mStatus != STATUS_ACTIVE && mStatus != STATUS_CONFIGURED) {
return;
}
- ALOGV("%s: Camera %s: Now %s", __FUNCTION__, mId.string(),
- idle ? "idle" : "active");
+ ALOGV("%s: Camera %s: Now %s, pauseState: %s", __FUNCTION__, mId.string(),
+ idle ? "idle" : "active", mPauseStateNotify ? "true" : "false");
internalUpdateStatusLocked(idle ? STATUS_CONFIGURED : STATUS_ACTIVE);
// Skip notifying listener if we're doing some user-transparent
@@ -2075,15 +2183,15 @@
*/
sp<Camera3Device::CaptureRequest> Camera3Device::createCaptureRequest(
- const CameraMetadata &request, const SurfaceMap &surfaceMap) {
+ const PhysicalCameraSettingsList &request, const SurfaceMap &surfaceMap) {
ATRACE_CALL();
status_t res;
sp<CaptureRequest> newRequest = new CaptureRequest;
- newRequest->mSettings = request;
+ newRequest->mSettingsList = request;
camera_metadata_entry_t inputStreams =
- newRequest->mSettings.find(ANDROID_REQUEST_INPUT_STREAMS);
+ newRequest->mSettingsList.begin()->metadata.find(ANDROID_REQUEST_INPUT_STREAMS);
if (inputStreams.count > 0) {
if (mInputStream == NULL ||
mInputStream->getId() != inputStreams.data.i32[0]) {
@@ -2109,11 +2217,11 @@
}
newRequest->mInputStream = mInputStream;
- newRequest->mSettings.erase(ANDROID_REQUEST_INPUT_STREAMS);
+ newRequest->mSettingsList.begin()->metadata.erase(ANDROID_REQUEST_INPUT_STREAMS);
}
camera_metadata_entry_t streams =
- newRequest->mSettings.find(ANDROID_REQUEST_OUTPUT_STREAMS);
+ newRequest->mSettingsList.begin()->metadata.find(ANDROID_REQUEST_OUTPUT_STREAMS);
if (streams.count == 0) {
CLOGE("Zero output streams specified!");
return NULL;
@@ -2161,7 +2269,7 @@
newRequest->mOutputStreams.push(stream);
}
- newRequest->mSettings.erase(ANDROID_REQUEST_OUTPUT_STREAMS);
+ newRequest->mSettingsList.begin()->metadata.erase(ANDROID_REQUEST_OUTPUT_STREAMS);
newRequest->mBatchSize = 1;
return newRequest;
@@ -2203,10 +2311,47 @@
// properly clean things up
internalUpdateStatusLocked(STATUS_UNCONFIGURED);
mNeedConfig = true;
+
+ res = mPreparerThread->resume();
+ if (res != OK) {
+ ALOGE("%s: Camera %s: Preparer thread failed to resume!", __FUNCTION__, mId.string());
+ }
+}
+
+bool Camera3Device::reconfigureCamera(const CameraMetadata& sessionParams) {
+ ATRACE_CALL();
+ bool ret = false;
+
+ Mutex::Autolock il(mInterfaceLock);
+ nsecs_t maxExpectedDuration = getExpectedInFlightDuration();
+
+ Mutex::Autolock l(mLock);
+ auto rc = internalPauseAndWaitLocked(maxExpectedDuration);
+ if (rc == NO_ERROR) {
+ mNeedConfig = true;
+ rc = configureStreamsLocked(mOperatingMode, sessionParams, /*notifyRequestThread*/ false);
+ if (rc == NO_ERROR) {
+ ret = true;
+ mPauseStateNotify = false;
+ //Moving to active state while holding 'mLock' is important.
+ //There could be pending calls to 'create-/deleteStream' which
+ //will trigger another stream configuration while the already
+ //present streams end up with outstanding buffers that will
+ //not get drained.
+ internalUpdateStatusLocked(STATUS_ACTIVE);
+ } else {
+ setErrorStateLocked("%s: Failed to re-configure camera: %d",
+ __FUNCTION__, rc);
+ }
+ } else {
+ ALOGE("%s: Failed to pause streaming: %d", __FUNCTION__, rc);
+ }
+
+ return ret;
}
status_t Camera3Device::configureStreamsLocked(int operatingMode,
- const CameraMetadata& sessionParams) {
+ const CameraMetadata& sessionParams, bool notifyRequestThread) {
ATRACE_CALL();
status_t res;
@@ -2247,6 +2392,8 @@
// Start configuring the streams
ALOGV("%s: Camera %s: Starting stream configuration", __FUNCTION__, mId.string());
+ mPreparerThread->pause();
+
camera3_stream_configuration config;
config.operation_mode = mOperatingMode;
config.num_streams = (mInputStream != NULL) + mOutputStreams.size();
@@ -2338,7 +2485,9 @@
// Request thread needs to know to avoid using repeat-last-settings protocol
// across configure_streams() calls
- mRequestThread->configurationComplete(mIsConstrainedHighSpeedConfiguration);
+ if (notifyRequestThread) {
+ mRequestThread->configurationComplete(mIsConstrainedHighSpeedConfiguration, sessionParams);
+ }
char value[PROPERTY_VALUE_MAX];
property_get("camera.fifo.disable", value, "0");
@@ -2376,6 +2525,12 @@
// tear down the deleted streams after configure streams.
mDeletedStreams.clear();
+ auto rc = mPreparerThread->resume();
+ if (rc != OK) {
+ SET_ERR_L("%s: Camera %s: Preparer thread failed to resume!", __FUNCTION__, mId.string());
+ return rc;
+ }
+
return OK;
}
@@ -2501,13 +2656,14 @@
status_t Camera3Device::registerInFlight(uint32_t frameNumber,
int32_t numBuffers, CaptureResultExtras resultExtras, bool hasInput,
- bool hasAppCallback, nsecs_t maxExpectedDuration) {
+ bool hasAppCallback, nsecs_t maxExpectedDuration,
+ std::set<String8>& physicalCameraIds) {
ATRACE_CALL();
Mutex::Autolock l(mInFlightLock);
ssize_t res;
res = mInFlightMap.add(frameNumber, InFlightRequest(numBuffers, resultExtras, hasInput,
- hasAppCallback, maxExpectedDuration));
+ hasAppCallback, maxExpectedDuration, physicalCameraIds));
if (res < 0) return res;
if (mInFlightMap.size() == 1) {
@@ -2747,7 +2903,8 @@
CaptureResultExtras &resultExtras,
CameraMetadata &collectedPartialResult,
uint32_t frameNumber,
- bool reprocess) {
+ bool reprocess,
+ const std::vector<PhysicalCaptureResultInfo>& physicalMetadatas) {
ATRACE_CALL();
if (pendingMetadata.isEmpty())
return;
@@ -2776,6 +2933,7 @@
CaptureResult captureResult;
captureResult.mResultExtras = resultExtras;
captureResult.mMetadata = pendingMetadata;
+ captureResult.mPhysicalMetadatas = physicalMetadatas;
// Append any previous partials to form a complete result
if (mUsePartialResult && !collectedPartialResult.isEmpty()) {
@@ -2791,6 +2949,15 @@
frameNumber);
return;
}
+ for (auto& physicalMetadata : captureResult.mPhysicalMetadatas) {
+ camera_metadata_entry timestamp =
+ physicalMetadata.mPhysicalCameraMetadata.find(ANDROID_SENSOR_TIMESTAMP);
+ if (timestamp.count == 0) {
+ SET_ERR("No timestamp provided by HAL for physical camera %s frame %d!",
+ String8(physicalMetadata.mPhysicalCameraId).c_str(), frameNumber);
+ return;
+ }
+ }
mTagMonitor.monitorMetadata(TagMonitor::RESULT,
frameNumber, timestamp.data.i64[0], captureResult.mMetadata);
@@ -2826,7 +2993,6 @@
bool isPartialResult = false;
CameraMetadata collectedPartialResult;
- CaptureResultExtras resultExtras;
bool hasInputBufferInRequest = false;
// Get shutter timestamp and resultExtras from list of in-flight requests,
@@ -2867,6 +3033,11 @@
return;
}
isPartialResult = (result->partial_result < mNumPartialResults);
+ if (isPartialResult && result->num_physcam_metadata) {
+ SET_ERR("Result is malformed for frame %d: partial_result not allowed for"
+ " physical camera result", frameNumber);
+ return;
+ }
if (isPartialResult) {
request.collectedPartialResult.append(result->result);
}
@@ -2883,11 +3054,28 @@
// Did we get the (final) result metadata for this capture?
if (result->result != NULL && !isPartialResult) {
+ if (request.physicalCameraIds.size() != result->num_physcam_metadata) {
+ SET_ERR("Requested physical Camera Ids %d not equal to number of metadata %d",
+ request.physicalCameraIds.size(), result->num_physcam_metadata);
+ return;
+ }
if (request.haveResultMetadata) {
SET_ERR("Called multiple times with metadata for frame %d",
frameNumber);
return;
}
+ for (uint32_t i = 0; i < result->num_physcam_metadata; i++) {
+ String8 physicalId(result->physcam_ids[i]);
+ std::set<String8>::iterator cameraIdIter =
+ request.physicalCameraIds.find(physicalId);
+ if (cameraIdIter != request.physicalCameraIds.end()) {
+ request.physicalCameraIds.erase(cameraIdIter);
+ } else {
+ SET_ERR("Total result for frame %d has already returned for camera %s",
+ frameNumber, physicalId.c_str());
+ return;
+ }
+ }
if (mUsePartialResult &&
!request.collectedPartialResult.isEmpty()) {
collectedPartialResult.acquire(
@@ -2932,15 +3120,21 @@
}
if (result->result != NULL && !isPartialResult) {
+ for (uint32_t i = 0; i < result->num_physcam_metadata; i++) {
+ CameraMetadata physicalMetadata;
+ physicalMetadata.append(result->physcam_metadata[i]);
+ request.physicalMetadatas.push_back({String16(result->physcam_ids[i]),
+ physicalMetadata});
+ }
if (shutterTimestamp == 0) {
request.pendingMetadata = result->result;
request.collectedPartialResult = collectedPartialResult;
- } else if (request.hasCallback) {
+ } else if (request.hasCallback) {
CameraMetadata metadata;
metadata = result->result;
sendCaptureResult(metadata, request.resultExtras,
collectedPartialResult, frameNumber,
- hasInputBufferInRequest);
+ hasInputBufferInRequest, request.physicalMetadatas);
}
}
@@ -3126,7 +3320,7 @@
// send pending result and buffers
sendCaptureResult(r.pendingMetadata, r.resultExtras,
r.collectedPartialResult, msg.frame_number,
- r.hasInputBuffer);
+ r.hasInputBuffer, r.physicalMetadatas);
}
returnOutputBuffers(r.pendingOutputBuffers.array(),
r.pendingOutputBuffers.size(), r.shutterTimestamp);
@@ -3141,7 +3335,6 @@
}
}
-
CameraMetadata Camera3Device::getLatestRequestLocked() {
ALOGV("%s", __FUNCTION__);
@@ -3168,7 +3361,18 @@
sp<ICameraDeviceSession> &session,
std::shared_ptr<RequestMetadataQueue> queue) :
mHidlSession(session),
- mRequestMetadataQueue(queue) {}
+ mRequestMetadataQueue(queue) {
+ // Check with hardware service manager if we can downcast these interfaces
+ // Somewhat expensive, so cache the results at startup
+ auto castResult_3_4 = device::V3_4::ICameraDeviceSession::castFrom(mHidlSession);
+ if (castResult_3_4.isOk()) {
+ mHidlSession_3_4 = castResult_3_4;
+ }
+ auto castResult_3_3 = device::V3_3::ICameraDeviceSession::castFrom(mHidlSession);
+ if (castResult_3_3.isOk()) {
+ mHidlSession_3_3 = castResult_3_3;
+ }
+}
Camera3Device::HalInterface::HalInterface() {}
@@ -3196,52 +3400,89 @@
status_t res = OK;
common::V1_0::Status status;
- RequestTemplate id;
- switch (templateId) {
- case CAMERA3_TEMPLATE_PREVIEW:
- id = RequestTemplate::PREVIEW;
- break;
- case CAMERA3_TEMPLATE_STILL_CAPTURE:
- id = RequestTemplate::STILL_CAPTURE;
- break;
- case CAMERA3_TEMPLATE_VIDEO_RECORD:
- id = RequestTemplate::VIDEO_RECORD;
- break;
- case CAMERA3_TEMPLATE_VIDEO_SNAPSHOT:
- id = RequestTemplate::VIDEO_SNAPSHOT;
- break;
- case CAMERA3_TEMPLATE_ZERO_SHUTTER_LAG:
- id = RequestTemplate::ZERO_SHUTTER_LAG;
- break;
- case CAMERA3_TEMPLATE_MANUAL:
- id = RequestTemplate::MANUAL;
- break;
- default:
- // Unknown template ID
- return BAD_VALUE;
- }
- auto err = mHidlSession->constructDefaultRequestSettings(id,
- [&status, &requestTemplate]
+
+ auto requestCallback = [&status, &requestTemplate]
(common::V1_0::Status s, const device::V3_2::CameraMetadata& request) {
- status = s;
- if (status == common::V1_0::Status::OK) {
- const camera_metadata *r =
- reinterpret_cast<const camera_metadata_t*>(request.data());
- size_t expectedSize = request.size();
- int ret = validate_camera_metadata_structure(r, &expectedSize);
- if (ret == OK || ret == CAMERA_METADATA_VALIDATION_SHIFTED) {
- *requestTemplate = clone_camera_metadata(r);
- if (*requestTemplate == nullptr) {
- ALOGE("%s: Unable to clone camera metadata received from HAL",
- __FUNCTION__);
- status = common::V1_0::Status::INTERNAL_ERROR;
- }
- } else {
- ALOGE("%s: Malformed camera metadata received from HAL", __FUNCTION__);
+ status = s;
+ if (status == common::V1_0::Status::OK) {
+ const camera_metadata *r =
+ reinterpret_cast<const camera_metadata_t*>(request.data());
+ size_t expectedSize = request.size();
+ int ret = validate_camera_metadata_structure(r, &expectedSize);
+ if (ret == OK || ret == CAMERA_METADATA_VALIDATION_SHIFTED) {
+ *requestTemplate = clone_camera_metadata(r);
+ if (*requestTemplate == nullptr) {
+ ALOGE("%s: Unable to clone camera metadata received from HAL",
+ __FUNCTION__);
status = common::V1_0::Status::INTERNAL_ERROR;
}
+ } else {
+ ALOGE("%s: Malformed camera metadata received from HAL", __FUNCTION__);
+ status = common::V1_0::Status::INTERNAL_ERROR;
}
- });
+ }
+ };
+ hardware::Return<void> err;
+ if (mHidlSession_3_4 != nullptr) {
+ device::V3_4::RequestTemplate id;
+ switch (templateId) {
+ case CAMERA3_TEMPLATE_PREVIEW:
+ id = device::V3_4::RequestTemplate::PREVIEW;
+ break;
+ case CAMERA3_TEMPLATE_STILL_CAPTURE:
+ id = device::V3_4::RequestTemplate::STILL_CAPTURE;
+ break;
+ case CAMERA3_TEMPLATE_VIDEO_RECORD:
+ id = device::V3_4::RequestTemplate::VIDEO_RECORD;
+ break;
+ case CAMERA3_TEMPLATE_VIDEO_SNAPSHOT:
+ id = device::V3_4::RequestTemplate::VIDEO_SNAPSHOT;
+ break;
+ case CAMERA3_TEMPLATE_ZERO_SHUTTER_LAG:
+ id = device::V3_4::RequestTemplate::ZERO_SHUTTER_LAG;
+ break;
+ case CAMERA3_TEMPLATE_MANUAL:
+ id = device::V3_4::RequestTemplate::MANUAL;
+ break;
+ case CAMERA3_TEMPLATE_MOTION_TRACKING_PREVIEW:
+ id = device::V3_4::RequestTemplate::MOTION_TRACKING_PREVIEW;
+ break;
+ case CAMERA3_TEMPLATE_MOTION_TRACKING_BEST:
+ id = device::V3_4::RequestTemplate::MOTION_TRACKING_BEST;
+ break;
+ default:
+ // Unknown template ID
+ return BAD_VALUE;
+ }
+ err = mHidlSession_3_4->constructDefaultRequestSettings_3_4(id, requestCallback);
+ } else {
+ RequestTemplate id;
+ switch (templateId) {
+ case CAMERA3_TEMPLATE_PREVIEW:
+ id = RequestTemplate::PREVIEW;
+ break;
+ case CAMERA3_TEMPLATE_STILL_CAPTURE:
+ id = RequestTemplate::STILL_CAPTURE;
+ break;
+ case CAMERA3_TEMPLATE_VIDEO_RECORD:
+ id = RequestTemplate::VIDEO_RECORD;
+ break;
+ case CAMERA3_TEMPLATE_VIDEO_SNAPSHOT:
+ id = RequestTemplate::VIDEO_SNAPSHOT;
+ break;
+ case CAMERA3_TEMPLATE_ZERO_SHUTTER_LAG:
+ id = RequestTemplate::ZERO_SHUTTER_LAG;
+ break;
+ case CAMERA3_TEMPLATE_MANUAL:
+ id = RequestTemplate::MANUAL;
+ break;
+ default:
+ // Unknown template ID, or this HAL is too old to support it
+ return BAD_VALUE;
+ }
+ err = mHidlSession->constructDefaultRequestSettings(id, requestCallback);
+ }
+
if (!err.isOk()) {
ALOGE("%s: Transaction error: %s", __FUNCTION__, err.description().c_str());
res = DEAD_OBJECT;
@@ -3260,10 +3501,13 @@
// Convert stream config to HIDL
std::set<int> activeStreams;
- device::V3_4::StreamConfiguration requestedConfiguration;
- requestedConfiguration.v3_2.streams.resize(config->num_streams);
+ device::V3_2::StreamConfiguration requestedConfiguration3_2;
+ device::V3_4::StreamConfiguration requestedConfiguration3_4;
+ requestedConfiguration3_2.streams.resize(config->num_streams);
+ requestedConfiguration3_4.streams.resize(config->num_streams);
for (size_t i = 0; i < config->num_streams; i++) {
- Stream &dst = requestedConfiguration.v3_2.streams[i];
+ device::V3_2::Stream &dst3_2 = requestedConfiguration3_2.streams[i];
+ device::V3_4::Stream &dst3_4 = requestedConfiguration3_4.streams[i];
camera3_stream_t *src = config->streams[i];
Camera3Stream* cam3stream = Camera3Stream::cast(src);
@@ -3282,14 +3526,18 @@
__FUNCTION__, streamId, config->streams[i]->stream_type);
return BAD_VALUE;
}
- dst.id = streamId;
- dst.streamType = streamType;
- dst.width = src->width;
- dst.height = src->height;
- dst.format = mapToPixelFormat(src->format);
- dst.usage = mapToConsumerUsage(cam3stream->getUsage());
- dst.dataSpace = mapToHidlDataspace(src->data_space);
- dst.rotation = mapToStreamRotation((camera3_stream_rotation_t) src->rotation);
+ dst3_2.id = streamId;
+ dst3_2.streamType = streamType;
+ dst3_2.width = src->width;
+ dst3_2.height = src->height;
+ dst3_2.format = mapToPixelFormat(src->format);
+ dst3_2.usage = mapToConsumerUsage(cam3stream->getUsage());
+ dst3_2.dataSpace = mapToHidlDataspace(src->data_space);
+ dst3_2.rotation = mapToStreamRotation((camera3_stream_rotation_t) src->rotation);
+ dst3_4.v3_2 = dst3_2;
+ if (src->physical_camera_id != nullptr) {
+ dst3_4.physicalCameraId = src->physical_camera_id;
+ }
activeStreams.insert(streamId);
// Create Buffer ID map if necessary
@@ -3308,52 +3556,46 @@
}
}
+ StreamConfigurationMode operationMode;
res = mapToStreamConfigurationMode(
(camera3_stream_configuration_mode_t) config->operation_mode,
- /*out*/ &requestedConfiguration.v3_2.operationMode);
+ /*out*/ &operationMode);
if (res != OK) {
return res;
}
-
- requestedConfiguration.sessionParams.setToExternal(
+ requestedConfiguration3_2.operationMode = operationMode;
+ requestedConfiguration3_4.operationMode = operationMode;
+ requestedConfiguration3_4.sessionParams.setToExternal(
reinterpret_cast<uint8_t*>(const_cast<camera_metadata_t*>(sessionParams)),
get_camera_metadata_size(sessionParams));
// Invoke configureStreams
-
device::V3_3::HalStreamConfiguration finalConfiguration;
common::V1_0::Status status;
// See if we have v3.4 or v3.3 HAL
- sp<device::V3_4::ICameraDeviceSession> hidlSession_3_4;
- sp<device::V3_3::ICameraDeviceSession> hidlSession_3_3;
- auto castResult_3_4 = device::V3_4::ICameraDeviceSession::castFrom(mHidlSession);
- if (castResult_3_4.isOk()) {
- hidlSession_3_4 = castResult_3_4;
- } else {
- auto castResult_3_3 = device::V3_3::ICameraDeviceSession::castFrom(mHidlSession);
- if (castResult_3_3.isOk()) {
- hidlSession_3_3 = castResult_3_3;
- }
- }
-
- if (hidlSession_3_4 != nullptr) {
+ if (mHidlSession_3_4 != nullptr) {
// We do; use v3.4 for the call
ALOGV("%s: v3.4 device found", __FUNCTION__);
- auto err = hidlSession_3_4->configureStreams_3_4(requestedConfiguration,
- [&status, &finalConfiguration]
- (common::V1_0::Status s, const device::V3_3::HalStreamConfiguration& halConfiguration) {
- finalConfiguration = halConfiguration;
+ device::V3_4::HalStreamConfiguration finalConfiguration3_4;
+ auto err = mHidlSession_3_4->configureStreams_3_4(requestedConfiguration3_4,
+ [&status, &finalConfiguration3_4]
+ (common::V1_0::Status s, const device::V3_4::HalStreamConfiguration& halConfiguration) {
+ finalConfiguration3_4 = halConfiguration;
status = s;
});
if (!err.isOk()) {
ALOGE("%s: Transaction error: %s", __FUNCTION__, err.description().c_str());
return DEAD_OBJECT;
}
- } else if (hidlSession_3_3 != nullptr) {
+ finalConfiguration.streams.resize(finalConfiguration3_4.streams.size());
+ for (size_t i = 0; i < finalConfiguration3_4.streams.size(); i++) {
+ finalConfiguration.streams[i] = finalConfiguration3_4.streams[i].v3_3;
+ }
+ } else if (mHidlSession_3_3 != nullptr) {
// We do; use v3.3 for the call
ALOGV("%s: v3.3 device found", __FUNCTION__);
- auto err = hidlSession_3_3->configureStreams_3_3(requestedConfiguration.v3_2,
+ auto err = mHidlSession_3_3->configureStreams_3_3(requestedConfiguration3_2,
[&status, &finalConfiguration]
(common::V1_0::Status s, const device::V3_3::HalStreamConfiguration& halConfiguration) {
finalConfiguration = halConfiguration;
@@ -3367,7 +3609,7 @@
// We don't; use v3.2 call and construct a v3.3 HalStreamConfiguration
ALOGV("%s: v3.2 device found", __FUNCTION__);
HalStreamConfiguration finalConfiguration_3_2;
- auto err = mHidlSession->configureStreams(requestedConfiguration.v3_2,
+ auto err = mHidlSession->configureStreams(requestedConfiguration3_2,
[&status, &finalConfiguration_3_2]
(common::V1_0::Status s, const HalStreamConfiguration& halConfiguration) {
finalConfiguration_3_2 = halConfiguration;
@@ -3381,7 +3623,7 @@
for (size_t i = 0; i < finalConfiguration_3_2.streams.size(); i++) {
finalConfiguration.streams[i].v3_2 = finalConfiguration_3_2.streams[i];
finalConfiguration.streams[i].overrideDataSpace =
- requestedConfiguration.v3_2.streams[i].dataSpace;
+ requestedConfiguration3_2.streams[i].dataSpace;
}
}
@@ -3535,13 +3777,29 @@
ATRACE_NAME("CameraHal::processBatchCaptureRequests");
if (!valid()) return INVALID_OPERATION;
+ sp<device::V3_4::ICameraDeviceSession> hidlSession_3_4;
+ auto castResult_3_4 = device::V3_4::ICameraDeviceSession::castFrom(mHidlSession);
+ if (castResult_3_4.isOk()) {
+ hidlSession_3_4 = castResult_3_4;
+ }
+
hardware::hidl_vec<device::V3_2::CaptureRequest> captureRequests;
+ hardware::hidl_vec<device::V3_4::CaptureRequest> captureRequests_3_4;
size_t batchSize = requests.size();
- captureRequests.resize(batchSize);
+ if (hidlSession_3_4 != nullptr) {
+ captureRequests_3_4.resize(batchSize);
+ } else {
+ captureRequests.resize(batchSize);
+ }
std::vector<native_handle_t*> handlesCreated;
for (size_t i = 0; i < batchSize; i++) {
- wrapAsHidlRequest(requests[i], /*out*/&captureRequests[i], /*out*/&handlesCreated);
+ if (hidlSession_3_4 != nullptr) {
+ wrapAsHidlRequest(requests[i], /*out*/&captureRequests_3_4[i].v3_2,
+ /*out*/&handlesCreated);
+ } else {
+ wrapAsHidlRequest(requests[i], /*out*/&captureRequests[i], /*out*/&handlesCreated);
+ }
}
std::vector<device::V3_2::BufferCache> cachesToRemove;
@@ -3562,7 +3820,12 @@
// Write metadata to FMQ.
for (size_t i = 0; i < batchSize; i++) {
camera3_capture_request_t* request = requests[i];
- device::V3_2::CaptureRequest* captureRequest = &captureRequests[i];
+ device::V3_2::CaptureRequest* captureRequest;
+ if (hidlSession_3_4 != nullptr) {
+ captureRequest = &captureRequests_3_4[i].v3_2;
+ } else {
+ captureRequest = &captureRequests[i];
+ }
if (request->settings != nullptr) {
size_t settingsSize = get_camera_metadata_size(request->settings);
@@ -3584,12 +3847,46 @@
captureRequest->settings.resize(0);
captureRequest->fmqSettingsSize = 0u;
}
+
+ if (hidlSession_3_4 != nullptr) {
+ captureRequests_3_4[i].physicalCameraSettings.resize(request->num_physcam_settings);
+ for (size_t j = 0; j < request->num_physcam_settings; j++) {
+ size_t settingsSize = get_camera_metadata_size(request->physcam_settings[j]);
+ if (mRequestMetadataQueue != nullptr && mRequestMetadataQueue->write(
+ reinterpret_cast<const uint8_t*>(request->physcam_settings[j]),
+ settingsSize)) {
+ captureRequests_3_4[i].physicalCameraSettings[j].settings.resize(0);
+ captureRequests_3_4[i].physicalCameraSettings[j].fmqSettingsSize = settingsSize;
+ } else {
+ if (mRequestMetadataQueue != nullptr) {
+ ALOGW("%s: couldn't utilize fmq, fallback to hwbinder", __FUNCTION__);
+ }
+ captureRequests_3_4[i].physicalCameraSettings[j].settings.setToExternal(
+ reinterpret_cast<uint8_t*>(const_cast<camera_metadata_t*>(
+ request->physcam_settings[j])),
+ get_camera_metadata_size(request->physcam_settings[j]));
+ captureRequests_3_4[i].physicalCameraSettings[j].fmqSettingsSize = 0u;
+ }
+ captureRequests_3_4[i].physicalCameraSettings[j].physicalCameraId =
+ request->physcam_id[j];
+ }
+ }
}
- auto err = mHidlSession->processCaptureRequest(captureRequests, cachesToRemove,
+
+ hardware::details::return_status err;
+ if (hidlSession_3_4 != nullptr) {
+ err = hidlSession_3_4->processCaptureRequest_3_4(captureRequests_3_4, cachesToRemove,
[&status, &numRequestProcessed] (auto s, uint32_t n) {
status = s;
*numRequestProcessed = n;
});
+ } else {
+ err = mHidlSession->processCaptureRequest(captureRequests, cachesToRemove,
+ [&status, &numRequestProcessed] (auto s, uint32_t n) {
+ status = s;
+ *numRequestProcessed = n;
+ });
+ }
if (!err.isOk()) {
ALOGE("%s: Transaction error: %s", __FUNCTION__, err.description().c_str());
return DEAD_OBJECT;
@@ -3747,7 +4044,7 @@
Camera3Device::RequestThread::RequestThread(wp<Camera3Device> parent,
sp<StatusTracker> statusTracker,
- sp<HalInterface> interface) :
+ sp<HalInterface> interface, const Vector<int32_t>& sessionParamKeys) :
Thread(/*canCallJava*/false),
mParent(parent),
mStatusTracker(statusTracker),
@@ -3764,7 +4061,9 @@
mRepeatingLastFrameNumber(
hardware::camera2::ICameraDeviceUser::NO_IN_FLIGHT_REPEATING_FRAMES),
mPrepareVideoStream(false),
- mRequestLatency(kRequestLatencyBinSize) {
+ mRequestLatency(kRequestLatencyBinSize),
+ mSessionParamKeys(sessionParamKeys),
+ mLatestSessionParams(sessionParamKeys.size()) {
mStatusId = statusTracker->addComponent();
}
@@ -3777,10 +4076,12 @@
mListener = listener;
}
-void Camera3Device::RequestThread::configurationComplete(bool isConstrainedHighSpeed) {
+void Camera3Device::RequestThread::configurationComplete(bool isConstrainedHighSpeed,
+ const CameraMetadata& sessionParams) {
ATRACE_CALL();
Mutex::Autolock l(mRequestLock);
mReconfigured = true;
+ mLatestSessionParams = sessionParams;
// Prepare video stream for high speed recording.
mPrepareVideoStream = isConstrainedHighSpeed;
}
@@ -4068,9 +4369,12 @@
}
if (nextRequest.halRequest.settings != NULL) {
- nextRequest.captureRequest->mSettings.unlock(nextRequest.halRequest.settings);
+ nextRequest.captureRequest->mSettingsList.begin()->metadata.unlock(
+ nextRequest.halRequest.settings);
}
+ cleanupPhysicalSettings(nextRequest.captureRequest, &nextRequest.halRequest);
+
if (!triggerRemoveFailed) {
// Remove any previously queued triggers (after unlock)
status_t removeTriggerRes = removeTriggers(mPrevRequest);
@@ -4139,9 +4443,12 @@
}
if (nextRequest.halRequest.settings != NULL) {
- nextRequest.captureRequest->mSettings.unlock(nextRequest.halRequest.settings);
+ nextRequest.captureRequest->mSettingsList.begin()->metadata.unlock(
+ nextRequest.halRequest.settings);
}
+ cleanupPhysicalSettings(nextRequest.captureRequest, &nextRequest.halRequest);
+
// Remove any previously queued triggers (after unlock)
res = removeTriggers(mPrevRequest);
if (res != OK) {
@@ -4191,6 +4498,52 @@
return maxExpectedDuration;
}
+bool Camera3Device::RequestThread::updateSessionParameters(const CameraMetadata& settings) {
+ ATRACE_CALL();
+ bool updatesDetected = false;
+
+ for (auto tag : mSessionParamKeys) {
+ camera_metadata_ro_entry entry = settings.find(tag);
+ camera_metadata_entry lastEntry = mLatestSessionParams.find(tag);
+
+ if (entry.count > 0) {
+ bool isDifferent = false;
+ if (lastEntry.count > 0) {
+ // Have a last value, compare to see if changed
+ if (lastEntry.type == entry.type &&
+ lastEntry.count == entry.count) {
+ // Same type and count, compare values
+ size_t bytesPerValue = camera_metadata_type_size[lastEntry.type];
+ size_t entryBytes = bytesPerValue * lastEntry.count;
+ int cmp = memcmp(entry.data.u8, lastEntry.data.u8, entryBytes);
+ if (cmp != 0) {
+ isDifferent = true;
+ }
+ } else {
+ // Count or type has changed
+ isDifferent = true;
+ }
+ } else {
+ // No last entry, so always consider to be different
+ isDifferent = true;
+ }
+
+ if (isDifferent) {
+ ALOGV("%s: Session parameter tag id %d changed", __FUNCTION__, tag);
+ mLatestSessionParams.update(entry);
+ updatesDetected = true;
+ }
+ } else if (lastEntry.count > 0) {
+ // Value has been removed
+ ALOGV("%s: Session parameter tag id %d removed", __FUNCTION__, tag);
+ mLatestSessionParams.erase(tag);
+ updatesDetected = true;
+ }
+ }
+
+ return updatesDetected;
+}
+
bool Camera3Device::RequestThread::threadLoop() {
ATRACE_CALL();
status_t res;
@@ -4209,7 +4562,7 @@
// Get the latest request ID, if any
int latestRequestId;
camera_metadata_entry_t requestIdEntry = mNextRequests[mNextRequests.size() - 1].
- captureRequest->mSettings.find(ANDROID_REQUEST_ID);
+ captureRequest->mSettingsList.begin()->metadata.find(ANDROID_REQUEST_ID);
if (requestIdEntry.count > 0) {
latestRequestId = requestIdEntry.data.i32[0];
} else {
@@ -4217,6 +4570,53 @@
latestRequestId = NAME_NOT_FOUND;
}
+ // 'mNextRequests' will at this point contain either a set of HFR batched requests
+ // or a single request from streaming or burst. In either case the first element
+ // should contain the latest camera settings that we need to check for any session
+ // parameter updates.
+ if (updateSessionParameters(mNextRequests[0].captureRequest->mSettingsList.begin()->metadata)) {
+ res = OK;
+
+ //Input stream buffers are already acquired at this point so an input stream
+ //will not be able to move to idle state unless we force it.
+ if (mNextRequests[0].captureRequest->mInputStream != nullptr) {
+ res = mNextRequests[0].captureRequest->mInputStream->forceToIdle();
+ if (res != OK) {
+ ALOGE("%s: Failed to force idle input stream: %d", __FUNCTION__, res);
+ cleanUpFailedRequests(/*sendRequestError*/ false);
+ return false;
+ }
+ }
+
+ if (res == OK) {
+ sp<StatusTracker> statusTracker = mStatusTracker.promote();
+ if (statusTracker != 0) {
+ sp<Camera3Device> parent = mParent.promote();
+ if (parent != nullptr) {
+ parent->pauseStateNotify(true);
+ }
+
+ statusTracker->markComponentIdle(mStatusId, Fence::NO_FENCE);
+
+ if (parent != nullptr) {
+ mReconfigured |= parent->reconfigureCamera(mLatestSessionParams);
+ }
+
+ statusTracker->markComponentActive(mStatusId);
+ setPaused(false);
+ }
+
+ if (mNextRequests[0].captureRequest->mInputStream != nullptr) {
+ mNextRequests[0].captureRequest->mInputStream->restoreConfiguredState();
+ if (res != OK) {
+ ALOGE("%s: Failed to restore configured input stream: %d", __FUNCTION__, res);
+ cleanUpFailedRequests(/*sendRequestError*/ false);
+ return false;
+ }
+ }
+ }
+ }
+
// Prepare a batch of HAL requests and output buffers.
res = prepareHalRequests();
if (res == TIMED_OUT) {
@@ -4320,8 +4720,8 @@
* The request should be presorted so accesses in HAL
* are O(logn). Sidenote, sorting a sorted metadata is nop.
*/
- captureRequest->mSettings.sort();
- halRequest->settings = captureRequest->mSettings.getAndLock();
+ captureRequest->mSettingsList.begin()->metadata.sort();
+ halRequest->settings = captureRequest->mSettingsList.begin()->metadata.getAndLock();
mPrevRequest = captureRequest;
ALOGVV("%s: Request settings are NEW", __FUNCTION__);
@@ -4345,6 +4745,20 @@
__FUNCTION__);
}
+ if (captureRequest->mSettingsList.size() > 1) {
+ halRequest->num_physcam_settings = captureRequest->mSettingsList.size() - 1;
+ halRequest->physcam_id = new const char* [halRequest->num_physcam_settings];
+ halRequest->physcam_settings =
+ new const camera_metadata* [halRequest->num_physcam_settings];
+ auto it = ++captureRequest->mSettingsList.begin();
+ size_t i = 0;
+ for (; it != captureRequest->mSettingsList.end(); it++, i++) {
+ halRequest->physcam_id[i] = it->cameraId.c_str();
+ it->metadata.sort();
+ halRequest->physcam_settings[i] = it->metadata.getAndLock();
+ }
+ }
+
uint32_t totalNumBuffers = 0;
// Fill in buffers
@@ -4358,6 +4772,7 @@
outputBuffers->insertAt(camera3_stream_buffer_t(), 0,
captureRequest->mOutputStreams.size());
halRequest->output_buffers = outputBuffers->array();
+ std::set<String8> requestedPhysicalCameras;
for (size_t j = 0; j < captureRequest->mOutputStreams.size(); j++) {
sp<Camera3OutputStreamInterface> outputStream = captureRequest->mOutputStreams.editItemAt(j);
@@ -4388,8 +4803,18 @@
return TIMED_OUT;
}
- halRequest->num_output_buffers++;
+ String8 physicalCameraId = outputStream->getPhysicalCameraId();
+
+ if (!physicalCameraId.isEmpty()) {
+ // Physical stream isn't supported for input request.
+ if (halRequest->input_buffer) {
+ CLOGE("Physical stream is not supported for input request");
+ return INVALID_OPERATION;
+ }
+ requestedPhysicalCameras.insert(physicalCameraId);
+ }
+ halRequest->num_output_buffers++;
}
totalNumBuffers += halRequest->num_output_buffers;
@@ -4412,7 +4837,8 @@
totalNumBuffers, captureRequest->mResultExtras,
/*hasInput*/halRequest->input_buffer != NULL,
hasCallback,
- calculateMaxExpectedDuration(halRequest->settings));
+ calculateMaxExpectedDuration(halRequest->settings),
+ requestedPhysicalCameras);
ALOGVV("%s: registered in flight requestId = %" PRId32 ", frameNumber = %" PRId64
", burstId = %" PRId32 ".",
__FUNCTION__,
@@ -4515,6 +4941,30 @@
mExpectedInflightDuration : kMinInflightDuration;
}
+void Camera3Device::RequestThread::cleanupPhysicalSettings(sp<CaptureRequest> request,
+ camera3_capture_request_t *halRequest) {
+ if ((request == nullptr) || (halRequest == nullptr)) {
+ ALOGE("%s: Invalid request!", __FUNCTION__);
+ return;
+ }
+
+ if (halRequest->num_physcam_settings > 0) {
+ if (halRequest->physcam_id != nullptr) {
+ delete [] halRequest->physcam_id;
+ halRequest->physcam_id = nullptr;
+ }
+ if (halRequest->physcam_settings != nullptr) {
+ auto it = ++(request->mSettingsList.begin());
+ size_t i = 0;
+ for (; it != request->mSettingsList.end(); it++, i++) {
+ it->metadata.unlock(halRequest->physcam_settings[i]);
+ }
+ delete [] halRequest->physcam_settings;
+ halRequest->physcam_settings = nullptr;
+ }
+ }
+}
+
void Camera3Device::RequestThread::cleanUpFailedRequests(bool sendRequestError) {
if (mNextRequests.empty()) {
return;
@@ -4531,9 +4981,11 @@
Vector<camera3_stream_buffer_t>* outputBuffers = &nextRequest.outputBuffers;
if (halRequest->settings != NULL) {
- captureRequest->mSettings.unlock(halRequest->settings);
+ captureRequest->mSettingsList.begin()->metadata.unlock(halRequest->settings);
}
+ cleanupPhysicalSettings(captureRequest, halRequest);
+
if (captureRequest->mInputStream != NULL) {
captureRequest->mInputBuffer.status = CAMERA3_BUFFER_STATUS_ERROR;
captureRequest->mInputStream->returnInputBuffer(captureRequest->mInputBuffer);
@@ -4797,7 +5249,7 @@
return DEAD_OBJECT;
}
- CameraMetadata &metadata = request->mSettings;
+ CameraMetadata &metadata = request->mSettingsList.begin()->metadata;
size_t count = mTriggerMap.size();
for (size_t i = 0; i < count; ++i) {
@@ -4880,7 +5332,7 @@
ATRACE_CALL();
Mutex::Autolock al(mTriggerMutex);
- CameraMetadata &metadata = request->mSettings;
+ CameraMetadata &metadata = request->mSettingsList.begin()->metadata;
/**
* Replace all old entries with their old values.
@@ -4945,7 +5397,7 @@
static const int32_t dummyTriggerId = 1;
status_t res;
- CameraMetadata &metadata = request->mSettings;
+ CameraMetadata &metadata = request->mSettingsList.begin()->metadata;
// If AF trigger is active, insert a dummy AF trigger ID if none already
// exists
@@ -4980,7 +5432,7 @@
Camera3Device::PreparerThread::PreparerThread() :
Thread(/*canCallJava*/false), mListener(nullptr),
- mActive(false), mCancelNow(false) {
+ mActive(false), mCancelNow(false), mCurrentMaxCount(0), mCurrentPrepareComplete(false) {
}
Camera3Device::PreparerThread::~PreparerThread() {
@@ -5031,18 +5483,101 @@
}
// queue up the work
- mPendingStreams.push_back(stream);
+ mPendingStreams.emplace(maxCount, stream);
ALOGV("%s: Stream %d queued for preparing", __FUNCTION__, stream->getId());
return OK;
}
+void Camera3Device::PreparerThread::pause() {
+ ATRACE_CALL();
+
+ Mutex::Autolock l(mLock);
+
+ std::unordered_map<int, sp<camera3::Camera3StreamInterface> > pendingStreams;
+ pendingStreams.insert(mPendingStreams.begin(), mPendingStreams.end());
+ sp<camera3::Camera3StreamInterface> currentStream = mCurrentStream;
+ int currentMaxCount = mCurrentMaxCount;
+ mPendingStreams.clear();
+ mCancelNow = true;
+ while (mActive) {
+ auto res = mThreadActiveSignal.waitRelative(mLock, kActiveTimeout);
+ if (res == TIMED_OUT) {
+ ALOGE("%s: Timed out waiting on prepare thread!", __FUNCTION__);
+ return;
+ } else if (res != OK) {
+ ALOGE("%s: Encountered an error: %d waiting on prepare thread!", __FUNCTION__, res);
+ return;
+ }
+ }
+
+ //Check whether the prepare thread was able to complete the current
+ //stream. In case work is still pending emplace it along with the rest
+ //of the streams in the pending list.
+ if (currentStream != nullptr) {
+ if (!mCurrentPrepareComplete) {
+ pendingStreams.emplace(currentMaxCount, currentStream);
+ }
+ }
+
+ mPendingStreams.insert(pendingStreams.begin(), pendingStreams.end());
+ for (const auto& it : mPendingStreams) {
+ it.second->cancelPrepare();
+ }
+}
+
+status_t Camera3Device::PreparerThread::resume() {
+ ATRACE_CALL();
+ status_t res;
+
+ Mutex::Autolock l(mLock);
+ sp<NotificationListener> listener = mListener.promote();
+
+ if (mActive) {
+ ALOGE("%s: Trying to resume an already active prepare thread!", __FUNCTION__);
+ return NO_INIT;
+ }
+
+ auto it = mPendingStreams.begin();
+ for (; it != mPendingStreams.end();) {
+ res = it->second->startPrepare(it->first);
+ if (res == OK) {
+ if (listener != NULL) {
+ listener->notifyPrepared(it->second->getId());
+ }
+ it = mPendingStreams.erase(it);
+ } else if (res != NOT_ENOUGH_DATA) {
+ ALOGE("%s: Unable to start preparer stream: %d (%s)", __FUNCTION__,
+ res, strerror(-res));
+ it = mPendingStreams.erase(it);
+ } else {
+ it++;
+ }
+ }
+
+ if (mPendingStreams.empty()) {
+ return OK;
+ }
+
+ res = Thread::run("C3PrepThread", PRIORITY_BACKGROUND);
+ if (res != OK) {
+ ALOGE("%s: Unable to start preparer stream: %d (%s)",
+ __FUNCTION__, res, strerror(-res));
+ return res;
+ }
+ mCancelNow = false;
+ mActive = true;
+ ALOGV("%s: Preparer stream started", __FUNCTION__);
+
+ return OK;
+}
+
status_t Camera3Device::PreparerThread::clear() {
ATRACE_CALL();
Mutex::Autolock l(mLock);
- for (const auto& stream : mPendingStreams) {
- stream->cancelPrepare();
+ for (const auto& it : mPendingStreams) {
+ it.second->cancelPrepare();
}
mPendingStreams.clear();
mCancelNow = true;
@@ -5067,12 +5602,15 @@
// threadLoop _must not_ re-acquire mLock after it sets mActive to false; would
// cause deadlock with prepare()'s requestExitAndWait triggered by !mActive.
mActive = false;
+ mThreadActiveSignal.signal();
return false;
}
// Get next stream to prepare
auto it = mPendingStreams.begin();
- mCurrentStream = *it;
+ mCurrentStream = it->second;
+ mCurrentMaxCount = it->first;
+ mCurrentPrepareComplete = false;
mPendingStreams.erase(it);
ATRACE_ASYNC_BEGIN("stream prepare", mCurrentStream->getId());
ALOGV("%s: Preparing stream %d", __FUNCTION__, mCurrentStream->getId());
@@ -5107,6 +5645,7 @@
ATRACE_ASYNC_END("stream prepare", mCurrentStream->getId());
mCurrentStream.clear();
+ mCurrentPrepareComplete = true;
return true;
}
diff --git a/services/camera/libcameraservice/device3/Camera3Device.h b/services/camera/libcameraservice/device3/Camera3Device.h
index cc7eb35..bc97510 100644
--- a/services/camera/libcameraservice/device3/Camera3Device.h
+++ b/services/camera/libcameraservice/device3/Camera3Device.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2013-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.
@@ -19,6 +19,7 @@
#include <utility>
#include <unordered_map>
+#include <set>
#include <utils/Condition.h>
#include <utils/Errors.h>
@@ -31,7 +32,9 @@
#include <android/hardware/camera/device/3.2/ICameraDevice.h>
#include <android/hardware/camera/device/3.2/ICameraDeviceSession.h>
#include <android/hardware/camera/device/3.3/ICameraDeviceSession.h>
+#include <android/hardware/camera/device/3.4/ICameraDeviceSession.h>
#include <android/hardware/camera/device/3.2/ICameraDeviceCallback.h>
+#include <android/hardware/camera/device/3.4/ICameraDeviceCallback.h>
#include <fmq/MessageQueue.h>
#include <hardware/camera3.h>
@@ -76,7 +79,7 @@
*/
class Camera3Device :
public CameraDeviceBase,
- virtual public hardware::camera::device::V3_2::ICameraDeviceCallback,
+ virtual public hardware::camera::device::V3_4::ICameraDeviceCallback,
private camera3_callback_ops {
public:
@@ -99,12 +102,12 @@
// Capture and setStreamingRequest will configure streams if currently in
// idle state
status_t capture(CameraMetadata &request, int64_t *lastFrameNumber = NULL) override;
- status_t captureList(const List<const CameraMetadata> &requests,
+ status_t captureList(const List<const PhysicalCameraSettingsList> &requestsList,
const std::list<const SurfaceMap> &surfaceMaps,
int64_t *lastFrameNumber = NULL) override;
status_t setStreamingRequest(const CameraMetadata &request,
int64_t *lastFrameNumber = NULL) override;
- status_t setStreamingRequestList(const List<const CameraMetadata> &requests,
+ status_t setStreamingRequestList(const List<const PhysicalCameraSettingsList> &requestsList,
const std::list<const SurfaceMap> &surfaceMaps,
int64_t *lastFrameNumber = NULL) override;
status_t clearStreamingRequest(int64_t *lastFrameNumber = NULL) override;
@@ -119,12 +122,14 @@
status_t createStream(sp<Surface> consumer,
uint32_t width, uint32_t height, int format,
android_dataspace dataSpace, camera3_stream_rotation_t rotation, int *id,
+ const String8& physicalCameraId,
std::vector<int> *surfaceIds = nullptr,
int streamSetId = camera3::CAMERA3_STREAM_SET_ID_INVALID,
bool isShared = false, uint64_t consumerUsage = 0) override;
status_t createStream(const std::vector<sp<Surface>>& consumers,
bool hasDeferredConsumer, uint32_t width, uint32_t height, int format,
android_dataspace dataSpace, camera3_stream_rotation_t rotation, int *id,
+ const String8& physicalCameraId,
std::vector<int> *surfaceIds = nullptr,
int streamSetId = camera3::CAMERA3_STREAM_SET_ID_INVALID,
bool isShared = false, uint64_t consumerUsage = 0) override;
@@ -295,7 +300,13 @@
void getInflightBufferKeys(std::vector<std::pair<int32_t, int32_t>>* out);
private:
+ // Always valid
sp<hardware::camera::device::V3_2::ICameraDeviceSession> mHidlSession;
+ // Valid if ICameraDeviceSession is @3.3 or newer
+ sp<hardware::camera::device::V3_3::ICameraDeviceSession> mHidlSession_3_3;
+ // Valid if ICameraDeviceSession is @3.4 or newer
+ sp<hardware::camera::device::V3_4::ICameraDeviceSession> mHidlSession_3_4;
+
std::shared_ptr<RequestMetadataQueue> mRequestMetadataQueue;
std::mutex mInflightLock;
@@ -426,7 +437,7 @@
class CaptureRequest : public LightRefBase<CaptureRequest> {
public:
- CameraMetadata mSettings;
+ PhysicalCameraSettingsList mSettingsList;
sp<camera3::Camera3Stream> mInputStream;
camera3_stream_buffer_t mInputBuffer;
Vector<sp<camera3::Camera3OutputStreamInterface> >
@@ -446,26 +457,28 @@
status_t checkStatusOkToCaptureLocked();
status_t convertMetadataListToRequestListLocked(
- const List<const CameraMetadata> &metadataList,
+ const List<const PhysicalCameraSettingsList> &metadataList,
const std::list<const SurfaceMap> &surfaceMaps,
bool repeating,
/*out*/
RequestList *requestList);
- void convertToRequestList(List<const CameraMetadata>& requests,
+ void convertToRequestList(List<const PhysicalCameraSettingsList>& requestsList,
std::list<const SurfaceMap>& surfaceMaps,
const CameraMetadata& request);
- status_t submitRequestsHelper(const List<const CameraMetadata> &requests,
+ status_t submitRequestsHelper(const List<const PhysicalCameraSettingsList> &requestsList,
const std::list<const SurfaceMap> &surfaceMaps,
bool repeating,
int64_t *lastFrameNumber = NULL);
/**
- * Implementation of android::hardware::camera::device::V3_2::ICameraDeviceCallback
+ * Implementation of android::hardware::camera::device::V3_4::ICameraDeviceCallback
*/
-
+ hardware::Return<void> processCaptureResult_3_4(
+ const hardware::hidl_vec<
+ hardware::camera::device::V3_4::CaptureResult>& results) override;
hardware::Return<void> processCaptureResult(
const hardware::hidl_vec<
hardware::camera::device::V3_2::CaptureResult>& results) override;
@@ -475,7 +488,13 @@
// Handle one capture result. Assume that mProcessCaptureResultLock is held.
void processOneCaptureResultLocked(
- const hardware::camera::device::V3_2::CaptureResult& results);
+ const hardware::camera::device::V3_2::CaptureResult& result,
+ const hardware::hidl_vec<
+ hardware::camera::device::V3_4::PhysicalCameraMetadata> physicalCameraMetadatas);
+ status_t readOneCameraMetadataLocked(uint64_t fmqResultSize,
+ hardware::camera::device::V3_2::CameraMetadata& resultMetadata,
+ const hardware::camera::device::V3_2::CameraMetadata& result);
+
// Handle one notify message
void notify(const hardware::camera::device::V3_2::NotifyMsg& msg);
@@ -541,22 +560,41 @@
* Do common work for setting up a streaming or single capture request.
* On success, will transition to ACTIVE if in IDLE.
*/
- sp<CaptureRequest> setUpRequestLocked(const CameraMetadata &request,
+ sp<CaptureRequest> setUpRequestLocked(const PhysicalCameraSettingsList &request,
const SurfaceMap &surfaceMap);
/**
* Build a CaptureRequest request from the CameraDeviceBase request
* settings.
*/
- sp<CaptureRequest> createCaptureRequest(const CameraMetadata &request,
+ sp<CaptureRequest> createCaptureRequest(const PhysicalCameraSettingsList &request,
const SurfaceMap &surfaceMap);
/**
+ * Pause state updates to the client application. Needed to mask out idle/active
+ * transitions during internal reconfigure
+ */
+ void pauseStateNotify(bool enable);
+
+ /**
+ * Internally re-configure camera device using new session parameters.
+ * This will get triggered by the request thread. Be sure to call
+ * pauseStateNotify(true) before going idle in the requesting location.
+ */
+ bool reconfigureCamera(const CameraMetadata& sessionParams);
+
+ /**
+ * Filter stream session parameters and configure camera HAL.
+ */
+ status_t filterParamsAndConfigureLocked(const CameraMetadata& sessionParams,
+ int operatingMode);
+
+ /**
* Take the currently-defined set of streams and configure the HAL to use
* them. This is a long-running operation (may be several hundered ms).
*/
status_t configureStreamsLocked(int operatingMode,
- const CameraMetadata& sessionParams);
+ const CameraMetadata& sessionParams, bool notifyRequestThread = true);
/**
* Cancel stream configuration that did not finish successfully.
@@ -655,7 +693,7 @@
RequestThread(wp<Camera3Device> parent,
sp<camera3::StatusTracker> statusTracker,
- sp<HalInterface> interface);
+ sp<HalInterface> interface, const Vector<int32_t>& sessionParamKeys);
~RequestThread();
void setNotificationListener(wp<NotificationListener> listener);
@@ -663,7 +701,8 @@
/**
* Call after stream (re)-configuration is completed.
*/
- void configurationComplete(bool isConstrainedHighSpeed);
+ void configurationComplete(bool isConstrainedHighSpeed,
+ const CameraMetadata& sessionParams);
/**
* Set or clear the list of repeating requests. Does not block
@@ -790,6 +829,10 @@
// Stop the repeating request if any of its output streams is abandoned.
void checkAndStopRepeatingRequest();
+ // Release physical camera settings and camera id resources.
+ void cleanupPhysicalSettings(sp<CaptureRequest> request,
+ /*out*/camera3_capture_request_t *halRequest);
+
// Pause handling
bool waitIfPaused();
void unpauseForNewRequests();
@@ -812,6 +855,12 @@
// Calculate the expected maximum duration for a request
nsecs_t calculateMaxExpectedDuration(const camera_metadata_t *request);
+ // Check and update latest session parameters based on the current request settings.
+ bool updateSessionParameters(const CameraMetadata& settings);
+
+ // Re-configure camera using the latest session parameters.
+ bool reconfigureCamera();
+
wp<Camera3Device> mParent;
wp<camera3::StatusTracker> mStatusTracker;
sp<HalInterface> mInterface;
@@ -869,6 +918,9 @@
static const int32_t kRequestLatencyBinSize = 40; // in ms
CameraLatencyHistogram mRequestLatency;
+
+ Vector<int32_t> mSessionParamKeys;
+ CameraMetadata mLatestSessionParams;
};
sp<RequestThread> mRequestThread;
@@ -923,6 +975,12 @@
// REQUEST/RESULT error.
bool skipResultMetadata;
+ // The physical camera ids being requested.
+ std::set<String8> physicalCameraIds;
+
+ // Map of physicalCameraId <-> Metadata
+ std::vector<PhysicalCaptureResultInfo> physicalMetadatas;
+
// Default constructor needed by KeyedVector
InFlightRequest() :
shutterTimestamp(0),
@@ -937,7 +995,8 @@
}
InFlightRequest(int numBuffers, CaptureResultExtras extras, bool hasInput,
- bool hasAppCallback, nsecs_t maxDuration) :
+ bool hasAppCallback, nsecs_t maxDuration,
+ const std::set<String8>& physicalCameraIdSet) :
shutterTimestamp(0),
sensorTimestamp(0),
requestStatus(OK),
@@ -947,7 +1006,8 @@
hasInputBuffer(hasInput),
hasCallback(hasAppCallback),
maxExpectedDuration(maxDuration),
- skipResultMetadata(false) {
+ skipResultMetadata(false),
+ physicalCameraIds(physicalCameraIdSet) {
}
};
@@ -964,7 +1024,7 @@
status_t registerInFlight(uint32_t frameNumber,
int32_t numBuffers, CaptureResultExtras resultExtras, bool hasInput,
- bool callback, nsecs_t maxExpectedDuration);
+ bool callback, nsecs_t maxExpectedDuration, std::set<String8>& physicalCameraIds);
/**
* Returns the maximum expected time it'll take for all currently in-flight
@@ -1006,21 +1066,34 @@
*/
status_t clear();
+ /**
+ * Pause all preparation activities
+ */
+ void pause();
+
+ /**
+ * Resume preparation activities
+ */
+ status_t resume();
+
private:
Mutex mLock;
+ Condition mThreadActiveSignal;
virtual bool threadLoop();
// Guarded by mLock
wp<NotificationListener> mListener;
- List<sp<camera3::Camera3StreamInterface> > mPendingStreams;
+ std::unordered_map<int, sp<camera3::Camera3StreamInterface> > mPendingStreams;
bool mActive;
bool mCancelNow;
// Only accessed by threadLoop and the destructor
sp<camera3::Camera3StreamInterface> mCurrentStream;
+ int mCurrentMaxCount;
+ bool mCurrentPrepareComplete;
};
sp<PreparerThread> mPreparerThread;
@@ -1072,7 +1145,9 @@
void sendCaptureResult(CameraMetadata &pendingMetadata,
CaptureResultExtras &resultExtras,
CameraMetadata &collectedPartialResult, uint32_t frameNumber,
- bool reprocess);
+ bool reprocess, const std::vector<PhysicalCaptureResultInfo>& physicalMetadatas);
+
+ bool isLastFullResult(const InFlightRequest& inFlightRequest);
// Insert the result to the result queue after updating frame number and overriding AE
// trigger cancel.
diff --git a/services/camera/libcameraservice/device3/Camera3DummyStream.cpp b/services/camera/libcameraservice/device3/Camera3DummyStream.cpp
index 0a245c4..fb1ff77 100644
--- a/services/camera/libcameraservice/device3/Camera3DummyStream.cpp
+++ b/services/camera/libcameraservice/device3/Camera3DummyStream.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2014-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.
@@ -26,9 +26,12 @@
namespace camera3 {
+const String8 Camera3DummyStream::DUMMY_ID;
+
Camera3DummyStream::Camera3DummyStream(int id) :
Camera3IOStreamBase(id, CAMERA3_STREAM_OUTPUT, DUMMY_WIDTH, DUMMY_HEIGHT,
- /*maxSize*/0, DUMMY_FORMAT, DUMMY_DATASPACE, DUMMY_ROTATION) {
+ /*maxSize*/0, DUMMY_FORMAT, DUMMY_DATASPACE, DUMMY_ROTATION,
+ DUMMY_ID) {
}
@@ -112,6 +115,10 @@
return OK;
}
+const String8& Camera3DummyStream::getPhysicalCameraId() const {
+ return DUMMY_ID;
+}
+
status_t Camera3DummyStream::setConsumers(const std::vector<sp<Surface>>& /*consumers*/) {
ALOGE("%s: Stream %d: Dummy stream doesn't support set consumer surface!",
__FUNCTION__, mId);
diff --git a/services/camera/libcameraservice/device3/Camera3DummyStream.h b/services/camera/libcameraservice/device3/Camera3DummyStream.h
index 684f4b0..4627548 100644
--- a/services/camera/libcameraservice/device3/Camera3DummyStream.h
+++ b/services/camera/libcameraservice/device3/Camera3DummyStream.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2014-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.
@@ -63,6 +63,11 @@
virtual status_t dropBuffers(bool /*dropping*/) override;
/**
+ * Query the physical camera id for the output stream.
+ */
+ virtual const String8& getPhysicalCameraId() const override;
+
+ /**
* Return if this output stream is for video encoding.
*/
bool isVideoStream() const;
@@ -114,6 +119,7 @@
static const android_dataspace DUMMY_DATASPACE = HAL_DATASPACE_UNKNOWN;
static const camera3_stream_rotation_t DUMMY_ROTATION = CAMERA3_STREAM_ROTATION_0;
static const uint64_t DUMMY_USAGE = GRALLOC_USAGE_HW_COMPOSER;
+ static const String8 DUMMY_ID;
/**
* Internal Camera3Stream interface
diff --git a/services/camera/libcameraservice/device3/Camera3IOStreamBase.cpp b/services/camera/libcameraservice/device3/Camera3IOStreamBase.cpp
index a52422d..3c1e43d 100644
--- a/services/camera/libcameraservice/device3/Camera3IOStreamBase.cpp
+++ b/services/camera/libcameraservice/device3/Camera3IOStreamBase.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2013-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.
@@ -31,9 +31,11 @@
Camera3IOStreamBase::Camera3IOStreamBase(int id, camera3_stream_type_t type,
uint32_t width, uint32_t height, size_t maxSize, int format,
- android_dataspace dataSpace, camera3_stream_rotation_t rotation, int setId) :
+ android_dataspace dataSpace, camera3_stream_rotation_t rotation,
+ const String8& physicalCameraId, int setId) :
Camera3Stream(id, type,
- width, height, maxSize, format, dataSpace, rotation, setId),
+ width, height, maxSize, format, dataSpace, rotation,
+ physicalCameraId, setId),
mTotalBufferCount(0),
mHandoutTotalBufferCount(0),
mHandoutOutputBufferCount(0),
diff --git a/services/camera/libcameraservice/device3/Camera3IOStreamBase.h b/services/camera/libcameraservice/device3/Camera3IOStreamBase.h
index 2376058..0a31d44 100644
--- a/services/camera/libcameraservice/device3/Camera3IOStreamBase.h
+++ b/services/camera/libcameraservice/device3/Camera3IOStreamBase.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2013-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.
@@ -35,6 +35,7 @@
Camera3IOStreamBase(int id, camera3_stream_type_t type,
uint32_t width, uint32_t height, size_t maxSize, int format,
android_dataspace dataSpace, camera3_stream_rotation_t rotation,
+ const String8& physicalCameraId,
int setId = CAMERA3_STREAM_SET_ID_INVALID);
public:
diff --git a/services/camera/libcameraservice/device3/Camera3InputStream.cpp b/services/camera/libcameraservice/device3/Camera3InputStream.cpp
index 2cb1ea7..017d7be 100644
--- a/services/camera/libcameraservice/device3/Camera3InputStream.cpp
+++ b/services/camera/libcameraservice/device3/Camera3InputStream.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2013-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.
@@ -27,10 +27,13 @@
namespace camera3 {
+const String8 Camera3InputStream::DUMMY_ID;
+
Camera3InputStream::Camera3InputStream(int id,
uint32_t width, uint32_t height, int format) :
Camera3IOStreamBase(id, CAMERA3_STREAM_INPUT, width, height, /*maxSize*/0,
- format, HAL_DATASPACE_UNKNOWN, CAMERA3_STREAM_ROTATION_0) {
+ format, HAL_DATASPACE_UNKNOWN, CAMERA3_STREAM_ROTATION_0,
+ DUMMY_ID) {
if (format == HAL_PIXEL_FORMAT_BLOB) {
ALOGE("%s: Bad format, BLOB not supported", __FUNCTION__);
diff --git a/services/camera/libcameraservice/device3/Camera3InputStream.h b/services/camera/libcameraservice/device3/Camera3InputStream.h
index 81226f8..0732464 100644
--- a/services/camera/libcameraservice/device3/Camera3InputStream.h
+++ b/services/camera/libcameraservice/device3/Camera3InputStream.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2013-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.
@@ -53,6 +53,8 @@
sp<IGraphicBufferProducer> mProducer;
Vector<BufferItem> mBuffersInFlight;
+ static const String8 DUMMY_ID;
+
/**
* Camera3IOStreamBase
*/
diff --git a/services/camera/libcameraservice/device3/Camera3OutputStream.cpp b/services/camera/libcameraservice/device3/Camera3OutputStream.cpp
index e79eecc..b3c3717 100644
--- a/services/camera/libcameraservice/device3/Camera3OutputStream.cpp
+++ b/services/camera/libcameraservice/device3/Camera3OutputStream.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2013-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.
@@ -35,9 +35,11 @@
sp<Surface> consumer,
uint32_t width, uint32_t height, int format,
android_dataspace dataSpace, camera3_stream_rotation_t rotation,
- nsecs_t timestampOffset, int setId) :
+ nsecs_t timestampOffset, const String8& physicalCameraId,
+ int setId) :
Camera3IOStreamBase(id, CAMERA3_STREAM_OUTPUT, width, height,
- /*maxSize*/0, format, dataSpace, rotation, setId),
+ /*maxSize*/0, format, dataSpace, rotation,
+ physicalCameraId, setId),
mConsumer(consumer),
mTransform(0),
mTraceFirstBuffer(true),
@@ -61,9 +63,9 @@
sp<Surface> consumer,
uint32_t width, uint32_t height, size_t maxSize, int format,
android_dataspace dataSpace, camera3_stream_rotation_t rotation,
- nsecs_t timestampOffset, int setId) :
+ nsecs_t timestampOffset, const String8& physicalCameraId, int setId) :
Camera3IOStreamBase(id, CAMERA3_STREAM_OUTPUT, width, height, maxSize,
- format, dataSpace, rotation, setId),
+ format, dataSpace, rotation, physicalCameraId, setId),
mConsumer(consumer),
mTransform(0),
mTraceFirstBuffer(true),
@@ -93,9 +95,11 @@
Camera3OutputStream::Camera3OutputStream(int id,
uint32_t width, uint32_t height, int format,
uint64_t consumerUsage, android_dataspace dataSpace,
- camera3_stream_rotation_t rotation, nsecs_t timestampOffset, int setId) :
+ camera3_stream_rotation_t rotation, nsecs_t timestampOffset,
+ const String8& physicalCameraId, int setId) :
Camera3IOStreamBase(id, CAMERA3_STREAM_OUTPUT, width, height,
- /*maxSize*/0, format, dataSpace, rotation, setId),
+ /*maxSize*/0, format, dataSpace, rotation,
+ physicalCameraId, setId),
mConsumer(nullptr),
mTransform(0),
mTraceFirstBuffer(true),
@@ -131,11 +135,13 @@
int format,
android_dataspace dataSpace,
camera3_stream_rotation_t rotation,
+ const String8& physicalCameraId,
uint64_t consumerUsage, nsecs_t timestampOffset,
int setId) :
Camera3IOStreamBase(id, type, width, height,
/*maxSize*/0,
- format, dataSpace, rotation, setId),
+ format, dataSpace, rotation,
+ physicalCameraId, setId),
mTransform(0),
mTraceFirstBuffer(true),
mUseMonoTimestamp(false),
@@ -800,6 +806,11 @@
return OK;
}
+const String8& Camera3OutputStream::getPhysicalCameraId() const {
+ Mutex::Autolock l(mLock);
+ return physicalCameraId();
+}
+
status_t Camera3OutputStream::notifyBufferReleased(ANativeWindowBuffer* /*anwBuffer*/) {
return OK;
}
diff --git a/services/camera/libcameraservice/device3/Camera3OutputStream.h b/services/camera/libcameraservice/device3/Camera3OutputStream.h
index 18b1901..6f36f92 100644
--- a/services/camera/libcameraservice/device3/Camera3OutputStream.h
+++ b/services/camera/libcameraservice/device3/Camera3OutputStream.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2013-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.
@@ -82,7 +82,8 @@
Camera3OutputStream(int id, sp<Surface> consumer,
uint32_t width, uint32_t height, int format,
android_dataspace dataSpace, camera3_stream_rotation_t rotation,
- nsecs_t timestampOffset, int setId = CAMERA3_STREAM_SET_ID_INVALID);
+ nsecs_t timestampOffset, const String8& physicalCameraId,
+ int setId = CAMERA3_STREAM_SET_ID_INVALID);
/**
* Set up a stream for formats that have a variable buffer size for the same
@@ -93,7 +94,8 @@
Camera3OutputStream(int id, sp<Surface> consumer,
uint32_t width, uint32_t height, size_t maxSize, int format,
android_dataspace dataSpace, camera3_stream_rotation_t rotation,
- nsecs_t timestampOffset, int setId = CAMERA3_STREAM_SET_ID_INVALID);
+ nsecs_t timestampOffset, const String8& physicalCameraId,
+ int setId = CAMERA3_STREAM_SET_ID_INVALID);
/**
* Set up a stream with deferred consumer for formats that have 2 dimensions, such as
@@ -103,6 +105,7 @@
Camera3OutputStream(int id, uint32_t width, uint32_t height, int format,
uint64_t consumerUsage, android_dataspace dataSpace,
camera3_stream_rotation_t rotation, nsecs_t timestampOffset,
+ const String8& physicalCameraId,
int setId = CAMERA3_STREAM_SET_ID_INVALID);
virtual ~Camera3OutputStream();
@@ -171,6 +174,11 @@
virtual status_t dropBuffers(bool dropping) override;
/**
+ * Query the physical camera id for the output stream.
+ */
+ virtual const String8& getPhysicalCameraId() const override;
+
+ /**
* Set the graphic buffer manager to get/return the stream buffers.
*
* It is only legal to call this method when stream is in STATE_CONSTRUCTED state.
@@ -194,6 +202,7 @@
Camera3OutputStream(int id, camera3_stream_type_t type,
uint32_t width, uint32_t height, int format,
android_dataspace dataSpace, camera3_stream_rotation_t rotation,
+ const String8& physicalCameraId,
uint64_t consumerUsage = 0, nsecs_t timestampOffset = 0,
int setId = CAMERA3_STREAM_SET_ID_INVALID);
diff --git a/services/camera/libcameraservice/device3/Camera3OutputStreamInterface.h b/services/camera/libcameraservice/device3/Camera3OutputStreamInterface.h
index 08fcf38..a711a6d 100644
--- a/services/camera/libcameraservice/device3/Camera3OutputStreamInterface.h
+++ b/services/camera/libcameraservice/device3/Camera3OutputStreamInterface.h
@@ -78,6 +78,11 @@
* Drop buffers if dropping is true. If dropping is false, do not drop buffers.
*/
virtual status_t dropBuffers(bool /*dropping*/) = 0;
+
+ /**
+ * Query the physical camera id for the output stream.
+ */
+ virtual const String8& getPhysicalCameraId() const = 0;
};
} // namespace camera3
diff --git a/services/camera/libcameraservice/device3/Camera3SharedOutputStream.cpp b/services/camera/libcameraservice/device3/Camera3SharedOutputStream.cpp
index 1c9417b..2bb9ff7 100644
--- a/services/camera/libcameraservice/device3/Camera3SharedOutputStream.cpp
+++ b/services/camera/libcameraservice/device3/Camera3SharedOutputStream.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2016-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.
@@ -27,10 +27,11 @@
uint32_t width, uint32_t height, int format,
uint64_t consumerUsage, android_dataspace dataSpace,
camera3_stream_rotation_t rotation,
- nsecs_t timestampOffset, int setId) :
+ nsecs_t timestampOffset, const String8& physicalCameraId,
+ int setId) :
Camera3OutputStream(id, CAMERA3_STREAM_OUTPUT, width, height,
- format, dataSpace, rotation, consumerUsage,
- timestampOffset, setId) {
+ format, dataSpace, rotation, physicalCameraId,
+ consumerUsage, timestampOffset, setId) {
size_t consumerCount = std::min(surfaces.size(), kMaxOutputs);
if (surfaces.size() > consumerCount) {
ALOGE("%s: Trying to add more consumers than the maximum ", __func__);
diff --git a/services/camera/libcameraservice/device3/Camera3SharedOutputStream.h b/services/camera/libcameraservice/device3/Camera3SharedOutputStream.h
index 6eab8bd..02b1c09 100644
--- a/services/camera/libcameraservice/device3/Camera3SharedOutputStream.h
+++ b/services/camera/libcameraservice/device3/Camera3SharedOutputStream.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2016-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.
@@ -36,6 +36,7 @@
uint32_t width, uint32_t height, int format,
uint64_t consumerUsage, android_dataspace dataSpace,
camera3_stream_rotation_t rotation, nsecs_t timestampOffset,
+ const String8& physicalCameraId,
int setId = CAMERA3_STREAM_SET_ID_INVALID);
virtual ~Camera3SharedOutputStream();
diff --git a/services/camera/libcameraservice/device3/Camera3Stream.cpp b/services/camera/libcameraservice/device3/Camera3Stream.cpp
index fbe8f4f..1105b75 100644
--- a/services/camera/libcameraservice/device3/Camera3Stream.cpp
+++ b/services/camera/libcameraservice/device3/Camera3Stream.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2013-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.
@@ -47,7 +47,8 @@
Camera3Stream::Camera3Stream(int id,
camera3_stream_type type,
uint32_t width, uint32_t height, size_t maxSize, int format,
- android_dataspace dataSpace, camera3_stream_rotation_t rotation, int setId) :
+ android_dataspace dataSpace, camera3_stream_rotation_t rotation,
+ const String8& physicalCameraId, int setId) :
camera3_stream(),
mId(id),
mSetId(setId),
@@ -64,7 +65,8 @@
mLastMaxCount(Camera3StreamInterface::ALLOCATE_PIPELINE_MAX),
mBufferLimitLatency(kBufferLimitLatencyBinSize),
mFormatOverridden(false),
- mOriginalFormat(-1) {
+ mOriginalFormat(-1),
+ mPhysicalCameraId(physicalCameraId) {
camera3_stream::stream_type = type;
camera3_stream::width = width;
@@ -74,6 +76,7 @@
camera3_stream::rotation = rotation;
camera3_stream::max_buffers = 0;
camera3_stream::priv = NULL;
+ camera3_stream::physical_camera_id = mPhysicalCameraId.string();
if ((format == HAL_PIXEL_FORMAT_BLOB || format == HAL_PIXEL_FORMAT_RAW_OPAQUE) &&
maxSize == 0) {
@@ -140,6 +143,79 @@
return mOriginalDataSpace;
}
+const String8& Camera3Stream::physicalCameraId() const {
+ return mPhysicalCameraId;
+}
+
+status_t Camera3Stream::forceToIdle() {
+ ATRACE_CALL();
+ Mutex::Autolock l(mLock);
+ status_t res;
+
+ switch (mState) {
+ case STATE_ERROR:
+ case STATE_CONSTRUCTED:
+ case STATE_IN_CONFIG:
+ case STATE_PREPARING:
+ case STATE_IN_RECONFIG:
+ ALOGE("%s: Invalid state: %d", __FUNCTION__, mState);
+ res = NO_INIT;
+ break;
+ case STATE_CONFIGURED:
+ if (hasOutstandingBuffersLocked()) {
+ sp<StatusTracker> statusTracker = mStatusTracker.promote();
+ if (statusTracker != 0) {
+ statusTracker->markComponentIdle(mStatusId, Fence::NO_FENCE);
+ }
+ }
+
+ mState = STATE_IN_IDLE;
+ res = OK;
+
+ break;
+ default:
+ ALOGE("%s: Unknown state %d", __FUNCTION__, mState);
+ res = NO_INIT;
+ }
+
+ return res;
+}
+
+status_t Camera3Stream::restoreConfiguredState() {
+ ATRACE_CALL();
+ Mutex::Autolock l(mLock);
+ status_t res;
+
+ switch (mState) {
+ case STATE_ERROR:
+ case STATE_CONSTRUCTED:
+ case STATE_IN_CONFIG:
+ case STATE_PREPARING:
+ case STATE_IN_RECONFIG:
+ case STATE_CONFIGURED:
+ ALOGE("%s: Invalid state: %d", __FUNCTION__, mState);
+ res = NO_INIT;
+ break;
+ case STATE_IN_IDLE:
+ if (hasOutstandingBuffersLocked()) {
+ sp<StatusTracker> statusTracker = mStatusTracker.promote();
+ if (statusTracker != 0) {
+ statusTracker->markComponentActive(mStatusId);
+ }
+ }
+
+ mState = STATE_CONFIGURED;
+ res = OK;
+
+ break;
+ default:
+ ALOGE("%s: Unknown state %d", __FUNCTION__, mState);
+ res = NO_INIT;
+ }
+
+ return res;
+}
+
camera3_stream* Camera3Stream::startConfiguration() {
ATRACE_CALL();
Mutex::Autolock l(mLock);
@@ -150,6 +226,7 @@
ALOGE("%s: In error state", __FUNCTION__);
return NULL;
case STATE_CONSTRUCTED:
+ case STATE_IN_IDLE:
// OK
break;
case STATE_IN_CONFIG:
@@ -179,6 +256,11 @@
return NULL;
}
+ if (mState == STATE_IN_IDLE) {
+ // Skip configuration.
+ return this;
+ }
+
// Stop tracking if currently doing so
if (mStatusId != StatusTracker::NO_STATUS_ID) {
sp<StatusTracker> statusTracker = mStatusTracker.promote();
@@ -219,6 +301,9 @@
ALOGE("%s: Cannot finish configuration that hasn't been started",
__FUNCTION__);
return INVALID_OPERATION;
+ case STATE_IN_IDLE:
+ //Skip configuration in this state
+ return OK;
default:
ALOGE("%s: Unknown state", __FUNCTION__);
return INVALID_OPERATION;
@@ -267,6 +352,7 @@
return INVALID_OPERATION;
case STATE_IN_CONFIG:
case STATE_IN_RECONFIG:
+ case STATE_IN_IDLE:
// OK
break;
case STATE_CONSTRUCTED:
@@ -282,7 +368,9 @@
mUsage = mOldUsage;
camera3_stream::max_buffers = mOldMaxBuffers;
- mState = (mState == STATE_IN_RECONFIG) ? STATE_CONFIGURED : STATE_CONSTRUCTED;
+ mState = ((mState == STATE_IN_RECONFIG) || (mState == STATE_IN_IDLE)) ? STATE_CONFIGURED :
+ STATE_CONSTRUCTED;
+
return OK;
}
diff --git a/services/camera/libcameraservice/device3/Camera3Stream.h b/services/camera/libcameraservice/device3/Camera3Stream.h
index 6e7912e..a60cb56 100644
--- a/services/camera/libcameraservice/device3/Camera3Stream.h
+++ b/services/camera/libcameraservice/device3/Camera3Stream.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2013-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.
@@ -68,6 +68,12 @@
* duration. In this state, only prepareNextBuffer() and cancelPrepare()
* may be called.
*
+ * STATE_IN_IDLE: This is a temporary state only intended to be used for input
+ * streams and only for the case where we need to re-configure the camera device
+ * while the input stream has an outstanding buffer. All other streams should not
+ * be able to switch to this state. For them this is invalid and should be handled
+ * as an unknown state.
+ *
* Transition table:
*
* <none> => STATE_CONSTRUCTED:
@@ -98,6 +104,11 @@
* all stream buffers, or cancelPrepare is called.
* STATE_CONFIGURED => STATE_ABANDONED:
* When the buffer queue of the stream is abandoned.
+ * STATE_CONFIGURED => STATE_IN_IDLE:
+ * Only for an input stream which has an outstanding buffer.
+ * STATE_IN_IDLE => STATE_CONFIGURED:
+ * After the internal re-configuration, the input should revert back to
+ * the configured state.
*
* Status Tracking:
* Each stream is tracked by StatusTracker as a separate component,
@@ -108,7 +119,9 @@
*
* - ACTIVE: One or more buffers have been handed out (with #getBuffer).
* - IDLE: All buffers have been returned (with #returnBuffer), and their
- * respective release_fence(s) have been signaled.
+ * respective release_fence(s) have been signaled. The only exception to this
+ * rule is an input stream that moves to "STATE_IN_IDLE" during internal
+ * re-configuration.
*
* A typical use case is output streams. When the HAL has any buffers
* dequeued, the stream is marked ACTIVE. When the HAL returns all buffers
@@ -152,6 +165,7 @@
void setDataSpaceOverride(bool dataSpaceOverriden);
bool isDataSpaceOverridden() const;
android_dataspace getOriginalDataSpace() const;
+ const String8& physicalCameraId() const;
camera3_stream* asHalStream() override {
return this;
@@ -386,6 +400,19 @@
*/
bool isAbandoned() const;
+ /**
+ * Switch a configured stream with possibly outstanding buffers in idle
+ * state. Configuration for such streams will be skipped assuming there
+ * are no changes to the stream parameters.
+ */
+ status_t forceToIdle();
+
+ /**
+ * Restore a forced idle stream to configured state, marking it active
+ * in case it contains outstanding buffers.
+ */
+ status_t restoreConfiguredState();
+
protected:
const int mId;
/**
@@ -414,7 +441,8 @@
STATE_IN_RECONFIG,
STATE_CONFIGURED,
STATE_PREPARING,
- STATE_ABANDONED
+ STATE_ABANDONED,
+ STATE_IN_IDLE
} mState;
mutable Mutex mLock;
@@ -422,7 +450,7 @@
Camera3Stream(int id, camera3_stream_type type,
uint32_t width, uint32_t height, size_t maxSize, int format,
android_dataspace dataSpace, camera3_stream_rotation_t rotation,
- int setId);
+ const String8& physicalCameraId, int setId);
wp<Camera3StreamBufferFreedListener> mBufferFreedListener;
@@ -529,6 +557,7 @@
bool mDataSpaceOverridden;
android_dataspace mOriginalDataSpace;
+ String8 mPhysicalCameraId;
}; // class Camera3Stream
}; // namespace camera3
diff --git a/services/mediaanalytics/Android.mk b/services/mediaanalytics/Android.mk
index 9e2813e..2eeb7fa 100644
--- a/services/mediaanalytics/Android.mk
+++ b/services/mediaanalytics/Android.mk
@@ -6,11 +6,6 @@
LOCAL_SRC_FILES:= \
main_mediametrics.cpp \
- MetricsSummarizerCodec.cpp \
- MetricsSummarizerExtractor.cpp \
- MetricsSummarizerPlayer.cpp \
- MetricsSummarizerRecorder.cpp \
- MetricsSummarizer.cpp \
MediaAnalyticsService.cpp
LOCAL_SHARED_LIBRARIES := \
diff --git a/services/mediaanalytics/MediaAnalyticsService.cpp b/services/mediaanalytics/MediaAnalyticsService.cpp
index 2954b3b..a132e25 100644
--- a/services/mediaanalytics/MediaAnalyticsService.cpp
+++ b/services/mediaanalytics/MediaAnalyticsService.cpp
@@ -74,26 +74,11 @@
#include "MediaAnalyticsService.h"
-#include "MetricsSummarizer.h"
-#include "MetricsSummarizerCodec.h"
-#include "MetricsSummarizerExtractor.h"
-#include "MetricsSummarizerPlayer.h"
-#include "MetricsSummarizerRecorder.h"
-
-
namespace android {
using namespace android::base;
using namespace android::content::pm;
-
-
-// summarized records
-// up to 36 sets, each covering an hour -- so at least 1.5 days
-// (will be longer if there are hours without any media action)
-static const nsecs_t kNewSetIntervalNs = 3600*(1000*1000*1000ll);
-static const int kMaxRecordSets = 36;
-
// individual records kept in memory: age or count
// age: <= 36 hours (1.5 days)
// count: hard limit of # records
@@ -108,57 +93,9 @@
String16(kServiceName), new MediaAnalyticsService());
}
-// handle sets of summarizers
-MediaAnalyticsService::SummarizerSet::SummarizerSet() {
- mSummarizers = new List<MetricsSummarizer *>();
-}
-
-MediaAnalyticsService::SummarizerSet::~SummarizerSet() {
- // empty the list
- List<MetricsSummarizer *> *l = mSummarizers;
- while (l->size() > 0) {
- MetricsSummarizer *summarizer = *(l->begin());
- l->erase(l->begin());
- delete summarizer;
- }
-}
-
-void MediaAnalyticsService::newSummarizerSet() {
- ALOGD("MediaAnalyticsService::newSummarizerSet");
- MediaAnalyticsService::SummarizerSet *set = new MediaAnalyticsService::SummarizerSet();
- nsecs_t now = systemTime(SYSTEM_TIME_REALTIME);
- set->setStarted(now);
-
- set->appendSummarizer(new MetricsSummarizerExtractor("extractor"));
- set->appendSummarizer(new MetricsSummarizerCodec("codec"));
- set->appendSummarizer(new MetricsSummarizerPlayer("nuplayer"));
- set->appendSummarizer(new MetricsSummarizerRecorder("recorder"));
-
- // ALWAYS at the end, since it catches everything
- set->appendSummarizer(new MetricsSummarizer(NULL));
-
- // inject this set at the BACK of the list.
- mSummarizerSets->push_back(set);
- mCurrentSet = set;
-
- // limit the # that we have
- if (mMaxRecordSets > 0) {
- List<SummarizerSet *> *l = mSummarizerSets;
- while (l->size() > (size_t) mMaxRecordSets) {
- ALOGD("Deleting oldest record set....");
- MediaAnalyticsService::SummarizerSet *oset = *(l->begin());
- l->erase(l->begin());
- delete oset;
- mSetsDiscarded++;
- }
- }
-}
-
MediaAnalyticsService::MediaAnalyticsService()
: mMaxRecords(kMaxRecords),
mMaxRecordAgeNs(kMaxRecordAgeNs),
- mMaxRecordSets(kMaxRecordSets),
- mNewSetInterval(kNewSetIntervalNs),
mDumpProto(MediaAnalyticsItem::PROTO_V1),
mDumpProtoDefault(MediaAnalyticsItem::PROTO_V1) {
@@ -167,9 +104,6 @@
mOpen = new List<MediaAnalyticsItem *>();
mFinalized = new List<MediaAnalyticsItem *>();
- mSummarizerSets = new List<MediaAnalyticsService::SummarizerSet *>();
- newSummarizerSet();
-
mItemsSubmitted = 0;
mItemsFinalized = 0;
mItemsDiscarded = 0;
@@ -204,8 +138,6 @@
}
delete mFinalized;
mFinalized = NULL;
-
- // XXX: clean out the summaries
}
@@ -315,13 +247,11 @@
oitem = NULL;
} else {
oitem->setFinalized(true);
- summarize(oitem);
saveItem(mFinalized, oitem, 0);
}
// new record could itself be marked finalized...
id = item->getSessionID();
if (finalizing) {
- summarize(item);
saveItem(mFinalized, item, 0);
mItemsFinalized++;
} else {
@@ -332,7 +262,6 @@
oitem->merge(item);
id = oitem->getSessionID();
if (finalizing) {
- summarize(oitem);
saveItem(mFinalized, oitem, 0);
mItemsFinalized++;
}
@@ -350,7 +279,6 @@
delete item;
item = NULL;
} else {
- summarize(item);
saveItem(mFinalized, item, 0);
mItemsFinalized++;
}
@@ -379,8 +307,6 @@
}
// crack any parameters
- String16 summaryOption("-summary");
- bool summary = false;
String16 protoOption("-proto");
int chosenProto = mDumpProtoDefault;
String16 clearOption("-clear");
@@ -396,8 +322,6 @@
String8 myarg(args[i]);
if (args[i] == clearOption) {
clear = true;
- } else if (args[i] == summaryOption) {
- summary = true;
} else if (args[i] == protoOption) {
i++;
if (i < n) {
@@ -444,7 +368,6 @@
result.append("Recognized parameters:\n");
result.append("-help this help message\n");
result.append("-proto # dump using protocol #");
- result.append("-summary show summary info\n");
result.append("-clear clears out saved records\n");
result.append("-only X process records for component X\n");
result.append("-since X include records since X\n");
@@ -464,12 +387,7 @@
dumpHeaders(result, ts_since);
- // want exactly 1, to avoid confusing folks that parse the output
- if (summary) {
- dumpSummaries(result, ts_since, only.c_str());
- } else {
- dumpRecent(result, ts_since, only.c_str());
- }
+ dumpRecent(result, ts_since, only.c_str());
if (clear) {
@@ -526,40 +444,6 @@
}
}
-// dump summary info
-void MediaAnalyticsService::dumpSummaries(String8 &result, nsecs_t ts_since, const char *only) {
- const size_t SIZE = 512;
- char buffer[SIZE];
- int slot = 0;
-
- snprintf(buffer, SIZE, "\nSummarized Metrics:\n");
- result.append(buffer);
-
- if (only != NULL && *only == '\0') {
- only = NULL;
- }
-
- // have each of the distillers dump records
- if (mSummarizerSets != NULL) {
- List<SummarizerSet *>::iterator itSet = mSummarizerSets->begin();
- for (; itSet != mSummarizerSets->end(); itSet++) {
- nsecs_t when = (*itSet)->getStarted();
- if (when < ts_since) {
- continue;
- }
- List<MetricsSummarizer *> *list = (*itSet)->getSummarizers();
- List<MetricsSummarizer *>::iterator it = list->begin();
- for (; it != list->end(); it++) {
- if (only != NULL && strcmp(only, (*it)->getKey()) != 0) {
- ALOGV("Told to omit '%s'", (*it)->getKey());
- }
- std::string distilled = (*it)->dumpSummary(slot, only);
- result.append(distilled.c_str());
- }
- }
- }
-}
-
// the recent, detailed queues
void MediaAnalyticsService::dumpRecent(String8 &result, nsecs_t ts_since, const char * only) {
const size_t SIZE = 512;
@@ -748,8 +632,11 @@
static std::string allowedKeys[] =
{
+ "audiorecord",
+ "audiotrack",
"codec",
- "extractor"
+ "extractor",
+ "nuplayer",
};
static const int nAllowedKeys = sizeof(allowedKeys) / sizeof(allowedKeys[0]);
@@ -785,45 +672,6 @@
return false;
}
-// insert into the appropriate summarizer.
-// we make our own copy to save/summarize
-void MediaAnalyticsService::summarize(MediaAnalyticsItem *item) {
-
- ALOGV("MediaAnalyticsService::summarize()");
-
- if (item == NULL) {
- return;
- }
-
- nsecs_t now = systemTime(SYSTEM_TIME_REALTIME);
- if (mCurrentSet == NULL
- || (mCurrentSet->getStarted() + mNewSetInterval < now)) {
- newSummarizerSet();
- }
-
- if (mCurrentSet == NULL) {
- return;
- }
-
- List<MetricsSummarizer *> *summarizers = mCurrentSet->getSummarizers();
- List<MetricsSummarizer *>::iterator it = summarizers->begin();
- for (; it != summarizers->end(); it++) {
- if ((*it)->isMine(*item)) {
- break;
- }
- }
- if (it == summarizers->end()) {
- ALOGD("no handler for type %s", item->getKey().c_str());
- return; // no handler
- }
-
- // invoke the summarizer. summarizer will make whatever copies
- // it wants; the caller retains ownership of item.
-
- (*it)->handleRecord(item);
-
-}
-
// how long we hold package info before we re-fetch it
#define PKG_EXPIRATION_NS (30*60*1000000000ll) // 30 minutes, in nsecs
diff --git a/services/mediaanalytics/MediaAnalyticsService.h b/services/mediaanalytics/MediaAnalyticsService.h
index 1287835..484339c 100644
--- a/services/mediaanalytics/MediaAnalyticsService.h
+++ b/services/mediaanalytics/MediaAnalyticsService.h
@@ -28,9 +28,6 @@
#include <media/IMediaAnalyticsService.h>
-#include "MetricsSummarizer.h"
-
-
namespace android {
class MediaAnalyticsService : public BnMediaAnalyticsService
@@ -89,36 +86,6 @@
MediaAnalyticsItem *findItem(List<MediaAnalyticsItem *> *,
MediaAnalyticsItem *, bool removeit);
- // summarizers
- void summarize(MediaAnalyticsItem *item);
- class SummarizerSet {
- nsecs_t mStarted;
- List<MetricsSummarizer *> *mSummarizers;
-
- public:
- void appendSummarizer(MetricsSummarizer *s) {
- if (s) {
- mSummarizers->push_back(s);
- }
- };
- nsecs_t getStarted() { return mStarted;}
- void setStarted(nsecs_t started) {mStarted = started;}
- List<MetricsSummarizer *> *getSummarizers() { return mSummarizers;}
-
- SummarizerSet();
- ~SummarizerSet();
- };
- void newSummarizerSet();
- List<SummarizerSet *> *mSummarizerSets;
- SummarizerSet *mCurrentSet;
- List<MetricsSummarizer *> *getFirstSet() {
- List<SummarizerSet *>::iterator first = mSummarizerSets->begin();
- if (first != mSummarizerSets->end()) {
- return (*first)->getSummarizers();
- }
- return NULL;
- }
-
void saveItem(MediaAnalyticsItem);
void saveItem(List<MediaAnalyticsItem *> *, MediaAnalyticsItem *, int);
void deleteItem(List<MediaAnalyticsItem *> *, MediaAnalyticsItem *);
diff --git a/services/mediaanalytics/MetricsSummarizer.cpp b/services/mediaanalytics/MetricsSummarizer.cpp
deleted file mode 100644
index e7c26e3..0000000
--- a/services/mediaanalytics/MetricsSummarizer.cpp
+++ /dev/null
@@ -1,348 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "MetricsSummarizer"
-#include <utils/Log.h>
-
-#include <stdlib.h>
-#include <stdint.h>
-#include <string>
-#include <inttypes.h>
-
-#include <utils/threads.h>
-#include <utils/Errors.h>
-#include <utils/KeyedVector.h>
-#include <utils/String8.h>
-#include <utils/List.h>
-
-#include <media/IMediaAnalyticsService.h>
-
-#include "MetricsSummarizer.h"
-
-
-namespace android {
-
-#define DEBUG_SORT 0
-#define DEBUG_QUEUE 0
-
-
-MetricsSummarizer::MetricsSummarizer(const char *key)
- : mIgnorables(NULL)
-{
- ALOGV("MetricsSummarizer::MetricsSummarizer");
-
- if (key == NULL) {
- mKey = key;
- } else {
- mKey = strdup(key);
- }
-
- mSummaries = new List<MediaAnalyticsItem *>();
-}
-
-MetricsSummarizer::~MetricsSummarizer()
-{
- ALOGV("MetricsSummarizer::~MetricsSummarizer");
- if (mKey) {
- free((void *)mKey);
- mKey = NULL;
- }
-
- // clear the list of items we have saved
- while (mSummaries->size() > 0) {
- MediaAnalyticsItem * oitem = *(mSummaries->begin());
- if (DEBUG_QUEUE) {
- ALOGD("zap old record: key %s sessionID %" PRId64 " ts %" PRId64 "",
- oitem->getKey().c_str(), oitem->getSessionID(),
- oitem->getTimestamp());
- }
- mSummaries->erase(mSummaries->begin());
- delete oitem;
- }
-}
-
-// so we know what summarizer we were using
-const char *MetricsSummarizer::getKey() {
- const char *value = mKey;
- if (value == NULL) {
- value = "unknown";
- }
- return value;
-}
-
-// should the record be given to this summarizer
-bool MetricsSummarizer::isMine(MediaAnalyticsItem &item)
-{
- if (mKey == NULL)
- return true;
- std::string itemKey = item.getKey();
- if (strcmp(mKey, itemKey.c_str()) != 0) {
- return false;
- }
- return true;
-}
-
-std::string MetricsSummarizer::dumpSummary(int &slot)
-{
- return dumpSummary(slot, NULL);
-}
-
-std::string MetricsSummarizer::dumpSummary(int &slot, const char *only)
-{
- std::string value;
-
- List<MediaAnalyticsItem *>::iterator it = mSummaries->begin();
- if (it != mSummaries->end()) {
- char buf[16]; // enough for "#####: "
- for (; it != mSummaries->end(); it++) {
- if (only != NULL && strcmp(only, (*it)->getKey().c_str()) != 0) {
- continue;
- }
- std::string entry = (*it)->toString();
- snprintf(buf, sizeof(buf), "%5d: ", slot);
- value.append(buf);
- value.append(entry.c_str());
- value.append("\n");
- slot++;
- }
- }
- return value;
-}
-
-void MetricsSummarizer::setIgnorables(const char **ignorables) {
- mIgnorables = ignorables;
-}
-
-const char **MetricsSummarizer::getIgnorables() {
- return mIgnorables;
-}
-
-void MetricsSummarizer::handleRecord(MediaAnalyticsItem *item) {
-
- ALOGV("MetricsSummarizer::handleRecord() for %s",
- item == NULL ? "<nothing>" : item->toString().c_str());
-
- if (item == NULL) {
- return;
- }
-
- List<MediaAnalyticsItem *>::iterator it = mSummaries->begin();
- for (; it != mSummaries->end(); it++) {
- bool good = sameAttributes((*it), item, getIgnorables());
- ALOGV("Match against %s says %d", (*it)->toString().c_str(), good);
- if (good)
- break;
- }
- if (it == mSummaries->end()) {
- ALOGV("save new record");
- MediaAnalyticsItem *nitem = item->dup();
- if (nitem == NULL) {
- ALOGE("unable to save MediaMetrics record");
- }
- sortProps(nitem);
- nitem->setInt32("aggregated",1);
- mergeRecord(*nitem, *item);
- mSummaries->push_back(nitem);
- } else {
- ALOGV("increment existing record");
- (*it)->addInt32("aggregated",1);
- mergeRecord(*(*it), *item);
- }
-}
-
-void MetricsSummarizer::mergeRecord(MediaAnalyticsItem &/*have*/, MediaAnalyticsItem &/*item*/) {
- // default is no further massaging.
- ALOGV("MetricsSummarizer::mergeRecord() [default]");
- return;
-}
-
-// keep some stats for things: sums, counts, standard deviation
-// the integer version -- all of these pieces are in 64 bits
-void MetricsSummarizer::minMaxVar64(MediaAnalyticsItem &summation, const char *key, int64_t value) {
- if (key == NULL)
- return;
- int len = strlen(key) + 32;
- char *tmpKey = (char *)malloc(len);
-
- if (tmpKey == NULL) {
- return;
- }
-
- // N - count of samples
- snprintf(tmpKey, len, "%s.n", key);
- summation.addInt64(tmpKey, 1);
-
- // zero - count of samples that are zero
- if (value == 0) {
- snprintf(tmpKey, len, "%s.zero", key);
- int64_t zero = 0;
- (void) summation.getInt64(tmpKey,&zero);
- zero++;
- summation.setInt64(tmpKey, zero);
- }
-
- // min
- snprintf(tmpKey, len, "%s.min", key);
- int64_t min = value;
- if (summation.getInt64(tmpKey,&min)) {
- if (min > value) {
- summation.setInt64(tmpKey, value);
- }
- } else {
- summation.setInt64(tmpKey, value);
- }
-
- // max
- snprintf(tmpKey, len, "%s.max", key);
- int64_t max = value;
- if (summation.getInt64(tmpKey,&max)) {
- if (max < value) {
- summation.setInt64(tmpKey, value);
- }
- } else {
- summation.setInt64(tmpKey, value);
- }
-
- // components for mean, stddev;
- // stddev = sqrt(1/4*(sumx2 - (2*sumx*sumx/n) + ((sumx/n)^2)))
- // sum x
- snprintf(tmpKey, len, "%s.sumX", key);
- summation.addInt64(tmpKey, value);
- // sum x^2
- snprintf(tmpKey, len, "%s.sumX2", key);
- summation.addInt64(tmpKey, value*value);
-
-
- // last thing we do -- remove the base key from the summation
- // record so we won't get confused about it having both individual
- // and summary information in there.
- summation.removeProp(key);
-
- free(tmpKey);
-}
-
-
-//
-// Comparators
-//
-
-// testing that all of 'single' is in 'summ'
-// and that the values match.
-// 'summ' may have extra fields.
-// 'ignorable' is a set of things that we don't worry about matching up
-// (usually time- or count-based values we'll sum elsewhere)
-bool MetricsSummarizer::sameAttributes(MediaAnalyticsItem *summ, MediaAnalyticsItem *single, const char **ignorable) {
-
- if (single == NULL || summ == NULL) {
- return false;
- }
- ALOGV("MetricsSummarizer::sameAttributes(): summ %s", summ->toString().c_str());
- ALOGV("MetricsSummarizer::sameAttributes(): single %s", single->toString().c_str());
-
- // keep different sources/users separate
- if (single->mUid != summ->mUid) {
- return false;
- }
-
- // this can be made better.
- for(size_t i=0;i<single->mPropCount;i++) {
- MediaAnalyticsItem::Prop *prop1 = &(single->mProps[i]);
- const char *attrName = prop1->mName;
-
- // is it something we should ignore
- if (ignorable != NULL) {
- const char **ig = ignorable;
- for (;*ig; ig++) {
- if (strcmp(*ig, attrName) == 0) {
- break;
- }
- }
- if (*ig) {
- ALOGV("we don't mind that it has attr '%s'", attrName);
- continue;
- }
- }
-
- MediaAnalyticsItem::Prop *prop2 = summ->findProp(attrName);
- if (prop2 == NULL) {
- ALOGV("summ doesn't have this attr");
- return false;
- }
- if (prop1->mType != prop2->mType) {
- ALOGV("mismatched attr types");
- return false;
- }
- switch (prop1->mType) {
- case MediaAnalyticsItem::kTypeInt32:
- if (prop1->u.int32Value != prop2->u.int32Value) {
- ALOGV("mismatch values");
- return false;
- }
- break;
- case MediaAnalyticsItem::kTypeInt64:
- if (prop1->u.int64Value != prop2->u.int64Value) {
- ALOGV("mismatch values");
- return false;
- }
- break;
- case MediaAnalyticsItem::kTypeDouble:
- // XXX: watch out for floating point comparisons!
- if (prop1->u.doubleValue != prop2->u.doubleValue) {
- ALOGV("mismatch values");
- return false;
- }
- break;
- case MediaAnalyticsItem::kTypeCString:
- if (strcmp(prop1->u.CStringValue, prop2->u.CStringValue) != 0) {
- ALOGV("mismatch values");
- return false;
- }
- break;
- case MediaAnalyticsItem::kTypeRate:
- if (prop1->u.rate.count != prop2->u.rate.count) {
- ALOGV("mismatch values");
- return false;
- }
- if (prop1->u.rate.duration != prop2->u.rate.duration) {
- ALOGV("mismatch values");
- return false;
- }
- break;
- default:
- ALOGV("mismatch values in default type");
- return false;
- }
- }
-
- return true;
-}
-
-
-int MetricsSummarizer::PropSorter(const void *a, const void *b) {
- MediaAnalyticsItem::Prop *ai = (MediaAnalyticsItem::Prop *)a;
- MediaAnalyticsItem::Prop *bi = (MediaAnalyticsItem::Prop *)b;
- return strcmp(ai->mName, bi->mName);
-}
-
-// we sort in the summaries so that it looks pretty in the dumpsys
-void MetricsSummarizer::sortProps(MediaAnalyticsItem *item) {
- if (item->mPropCount != 0) {
- qsort(item->mProps, item->mPropCount,
- sizeof(MediaAnalyticsItem::Prop), MetricsSummarizer::PropSorter);
- }
-}
-
-} // namespace android
diff --git a/services/mediaanalytics/MetricsSummarizer.h b/services/mediaanalytics/MetricsSummarizer.h
deleted file mode 100644
index a16c7bc..0000000
--- a/services/mediaanalytics/MetricsSummarizer.h
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-#ifndef ANDROID_METRICSSUMMARIZER_H
-#define ANDROID_METRICSSUMMARIZER_H
-
-#include <string>
-#include <utils/threads.h>
-#include <utils/Errors.h>
-#include <utils/KeyedVector.h>
-#include <utils/List.h>
-
-#include <media/IMediaAnalyticsService.h>
-
-
-namespace android {
-
-class MetricsSummarizer
-{
-
- public:
-
- MetricsSummarizer(const char *key);
- virtual ~MetricsSummarizer();
-
- // show the key
- const char * getKey();
-
- // should the record be given to this summarizer
- bool isMine(MediaAnalyticsItem &item);
-
- // hand the record to this summarizer
- void handleRecord(MediaAnalyticsItem *item);
-
- virtual void mergeRecord(MediaAnalyticsItem &have, MediaAnalyticsItem &incoming);
-
- // dump the summarized records (for dumpsys)
- std::string dumpSummary(int &slot);
- std::string dumpSummary(int &slot, const char *only);
-
- void setIgnorables(const char **);
- const char **getIgnorables();
-
- protected:
-
- // various comparators
- // "do these records have same attributes and values in those attrs"
- bool sameAttributes(MediaAnalyticsItem *summ, MediaAnalyticsItem *single, const char **ignoreables);
-
- void minMaxVar64(MediaAnalyticsItem &summ, const char *key, int64_t value);
-
- static int PropSorter(const void *a, const void *b);
- void sortProps(MediaAnalyticsItem *item);
-
- private:
- const char *mKey;
- const char **mIgnorables;
- List<MediaAnalyticsItem *> *mSummaries;
-
-
-};
-
-// ----------------------------------------------------------------------------
-
-}; // namespace android
-
-#endif // ANDROID_METRICSSUMMARIZER_H
diff --git a/services/mediaanalytics/MetricsSummarizerCodec.cpp b/services/mediaanalytics/MetricsSummarizerCodec.cpp
deleted file mode 100644
index 6af3c9a..0000000
--- a/services/mediaanalytics/MetricsSummarizerCodec.cpp
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "MetricsSummarizerCodec"
-#include <utils/Log.h>
-
-#include <stdint.h>
-#include <inttypes.h>
-
-#include <utils/threads.h>
-#include <utils/Errors.h>
-#include <utils/KeyedVector.h>
-#include <utils/String8.h>
-#include <utils/List.h>
-
-#include <media/IMediaAnalyticsService.h>
-
-#include "MetricsSummarizer.h"
-#include "MetricsSummarizerCodec.h"
-
-
-
-
-namespace android {
-
-MetricsSummarizerCodec::MetricsSummarizerCodec(const char *key)
- : MetricsSummarizer(key)
-{
- ALOGV("MetricsSummarizerCodec::MetricsSummarizerCodec");
-}
-
-} // namespace android
diff --git a/services/mediaanalytics/MetricsSummarizerCodec.h b/services/mediaanalytics/MetricsSummarizerCodec.h
deleted file mode 100644
index c01196f..0000000
--- a/services/mediaanalytics/MetricsSummarizerCodec.h
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-#ifndef ANDROID_METRICSSUMMARIZERCODEC_H
-#define ANDROID_METRICSSUMMARIZERCODEC_H
-
-#include <utils/threads.h>
-#include <utils/Errors.h>
-#include <utils/KeyedVector.h>
-#include <utils/String8.h>
-#include <utils/List.h>
-
-#include <media/IMediaAnalyticsService.h>
-#include "MetricsSummarizer.h"
-
-
-namespace android {
-
-class MetricsSummarizerCodec : public MetricsSummarizer
-{
-
- public:
-
- MetricsSummarizerCodec(const char *key);
- virtual ~MetricsSummarizerCodec() {};
-
-};
-
-// ----------------------------------------------------------------------------
-
-}; // namespace android
-
-#endif // ANDROID_METRICSSUMMARIZERCODEC_H
diff --git a/services/mediaanalytics/MetricsSummarizerExtractor.cpp b/services/mediaanalytics/MetricsSummarizerExtractor.cpp
deleted file mode 100644
index 190f87d..0000000
--- a/services/mediaanalytics/MetricsSummarizerExtractor.cpp
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "MetricsSummarizerExtractor"
-#include <utils/Log.h>
-
-#include <utils/threads.h>
-#include <utils/Errors.h>
-#include <utils/KeyedVector.h>
-#include <utils/String8.h>
-#include <utils/List.h>
-
-#include <media/IMediaAnalyticsService.h>
-
-#include "MetricsSummarizer.h"
-#include "MetricsSummarizerExtractor.h"
-
-
-
-
-namespace android {
-
-MetricsSummarizerExtractor::MetricsSummarizerExtractor(const char *key)
- : MetricsSummarizer(key)
-{
- ALOGV("MetricsSummarizerExtractor::MetricsSummarizerExtractor");
-}
-
-} // namespace android
diff --git a/services/mediaanalytics/MetricsSummarizerExtractor.h b/services/mediaanalytics/MetricsSummarizerExtractor.h
deleted file mode 100644
index eee052b..0000000
--- a/services/mediaanalytics/MetricsSummarizerExtractor.h
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-#ifndef ANDROID_METRICSSUMMARIZEREXTRACTOR_H
-#define ANDROID_METRICSSUMMARIZEREXTRACTOR_H
-
-#include <utils/threads.h>
-#include <utils/Errors.h>
-#include <utils/KeyedVector.h>
-#include <utils/String8.h>
-#include <utils/List.h>
-
-#include <media/IMediaAnalyticsService.h>
-#include "MetricsSummarizer.h"
-
-
-namespace android {
-
-class MetricsSummarizerExtractor : public MetricsSummarizer
-{
-
- public:
-
- MetricsSummarizerExtractor(const char *key);
- virtual ~MetricsSummarizerExtractor() {};
-
-};
-
-// ----------------------------------------------------------------------------
-
-}; // namespace android
-
-#endif // ANDROID_METRICSSUMMARIZEREXTRACTOR_H
diff --git a/services/mediaanalytics/MetricsSummarizerPlayer.cpp b/services/mediaanalytics/MetricsSummarizerPlayer.cpp
deleted file mode 100644
index f882cb9..0000000
--- a/services/mediaanalytics/MetricsSummarizerPlayer.cpp
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "MetricsSummarizerPlayer"
-#include <utils/Log.h>
-
-#include <stdint.h>
-#include <inttypes.h>
-
-#include <utils/threads.h>
-#include <utils/Errors.h>
-#include <utils/KeyedVector.h>
-#include <utils/String8.h>
-#include <utils/List.h>
-
-#include <media/IMediaAnalyticsService.h>
-
-#include "MetricsSummarizer.h"
-#include "MetricsSummarizerPlayer.h"
-
-
-
-
-namespace android {
-
-static const char *player_ignorable[] = {
- "android.media.mediaplayer.durationMs",
- "android.media.mediaplayer.playingMs",
- "android.media.mediaplayer.frames",
- "android.media.mediaplayer.dropped",
- 0
-};
-
-MetricsSummarizerPlayer::MetricsSummarizerPlayer(const char *key)
- : MetricsSummarizer(key)
-{
- ALOGV("MetricsSummarizerPlayer::MetricsSummarizerPlayer");
- setIgnorables(player_ignorable);
-}
-
-// NB: this is also called for the first time -- so summation == item
-// Not sure if we need a flag for that or not.
-// In this particular mergeRecord() code -- we're' ok for this.
-void MetricsSummarizerPlayer::mergeRecord(MediaAnalyticsItem &summation, MediaAnalyticsItem &item) {
-
- ALOGV("MetricsSummarizerPlayer::mergeRecord()");
-
-
- int64_t duration = 0;
- if (item.getInt64("android.media.mediaplayer.durationMs", &duration)) {
- ALOGV("found durationMs of %" PRId64, duration);
- minMaxVar64(summation, "android.media.mediaplayer.durationMs", duration);
- }
-
- int64_t playing = 0;
- if (item.getInt64("android.media.mediaplayer.playingMs", &playing)) {
- ALOGV("found playingMs of %" PRId64, playing);
- }
- if (playing >= 0) {
- minMaxVar64(summation,"android.media.mediaplayer.playingMs",playing);
- }
-
- int64_t frames = 0;
- if (item.getInt64("android.media.mediaplayer.frames", &frames)) {
- ALOGV("found framess of %" PRId64, frames);
- }
- if (frames >= 0) {
- minMaxVar64(summation,"android.media.mediaplayer.frames",frames);
- }
-
- int64_t dropped = 0;
- if (item.getInt64("android.media.mediaplayer.dropped", &dropped)) {
- ALOGV("found dropped of %" PRId64, dropped);
- }
- if (dropped >= 0) {
- minMaxVar64(summation,"android.media.mediaplayer.dropped",dropped);
- }
-}
-
-} // namespace android
diff --git a/services/mediaanalytics/MetricsSummarizerPlayer.h b/services/mediaanalytics/MetricsSummarizerPlayer.h
deleted file mode 100644
index ad1bf74..0000000
--- a/services/mediaanalytics/MetricsSummarizerPlayer.h
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-#ifndef ANDROID_METRICSSUMMARIZERPLAYER_H
-#define ANDROID_METRICSSUMMARIZERPLAYER_H
-
-#include <utils/threads.h>
-#include <utils/Errors.h>
-#include <utils/KeyedVector.h>
-#include <utils/String8.h>
-#include <utils/List.h>
-
-#include <media/IMediaAnalyticsService.h>
-#include "MetricsSummarizer.h"
-
-
-namespace android {
-
-class MetricsSummarizerPlayer : public MetricsSummarizer
-{
-
- public:
-
- MetricsSummarizerPlayer(const char *key);
- virtual ~MetricsSummarizerPlayer() {};
-
- virtual void mergeRecord(MediaAnalyticsItem &have, MediaAnalyticsItem &incoming);
-
-};
-
-// ----------------------------------------------------------------------------
-
-}; // namespace android
-
-#endif // ANDROID_METRICSSUMMARIZERPLAYER_H
diff --git a/services/mediaanalytics/MetricsSummarizerRecorder.cpp b/services/mediaanalytics/MetricsSummarizerRecorder.cpp
deleted file mode 100644
index c2919c3..0000000
--- a/services/mediaanalytics/MetricsSummarizerRecorder.cpp
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "MetricsSummarizerRecorder"
-#include <utils/Log.h>
-
-#include <stdint.h>
-#include <inttypes.h>
-
-#include <utils/threads.h>
-#include <utils/Errors.h>
-#include <utils/KeyedVector.h>
-#include <utils/String8.h>
-#include <utils/List.h>
-
-#include <media/IMediaAnalyticsService.h>
-
-#include "MetricsSummarizer.h"
-#include "MetricsSummarizerRecorder.h"
-
-
-
-
-namespace android {
-
-MetricsSummarizerRecorder::MetricsSummarizerRecorder(const char *key)
- : MetricsSummarizer(key)
-{
- ALOGV("MetricsSummarizerRecorder::MetricsSummarizerRecorder");
-}
-
-} // namespace android
diff --git a/services/mediaanalytics/MetricsSummarizerRecorder.h b/services/mediaanalytics/MetricsSummarizerRecorder.h
deleted file mode 100644
index 963baab..0000000
--- a/services/mediaanalytics/MetricsSummarizerRecorder.h
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-#ifndef ANDROID_METRICSSUMMARIZERRECORDER_H
-#define ANDROID_METRICSSUMMARIZERRECORDER_H
-
-#include <utils/threads.h>
-#include <utils/Errors.h>
-#include <utils/KeyedVector.h>
-#include <utils/String8.h>
-#include <utils/List.h>
-
-#include <media/IMediaAnalyticsService.h>
-#include "MetricsSummarizer.h"
-
-
-namespace android {
-
-class MetricsSummarizerRecorder : public MetricsSummarizer
-{
-
- public:
-
- MetricsSummarizerRecorder(const char *key);
- virtual ~MetricsSummarizerRecorder() {};
-
-};
-
-// ----------------------------------------------------------------------------
-
-}; // namespace android
-
-#endif // ANDROID_METRICSSUMMARIZERRECORDER_H
diff --git a/services/mediacodec/Android.mk b/services/mediacodec/Android.mk
index 8e5b260..97eaf77 100644
--- a/services/mediacodec/Android.mk
+++ b/services/mediacodec/Android.mk
@@ -1,5 +1,28 @@
LOCAL_PATH := $(call my-dir)
+_software_codecs := \
+ libstagefright_soft_aacdec \
+ libstagefright_soft_aacenc \
+ libstagefright_soft_amrdec \
+ libstagefright_soft_amrnbenc \
+ libstagefright_soft_amrwbenc \
+ libstagefright_soft_avcdec \
+ libstagefright_soft_avcenc \
+ libstagefright_soft_flacdec \
+ libstagefright_soft_flacenc \
+ libstagefright_soft_g711dec \
+ libstagefright_soft_gsmdec \
+ libstagefright_soft_hevcdec \
+ libstagefright_soft_mp3dec \
+ libstagefright_soft_mpeg2dec \
+ libstagefright_soft_mpeg4dec \
+ libstagefright_soft_mpeg4enc \
+ libstagefright_soft_opusdec \
+ libstagefright_soft_rawdec \
+ libstagefright_soft_vorbisdec \
+ libstagefright_soft_vpxdec \
+ libstagefright_soft_vpxenc \
+
# service executable
include $(CLEAR_VARS)
# seccomp is not required for coverage build.
@@ -27,6 +50,15 @@
LOCAL_MODULE_RELATIVE_PATH := hw
LOCAL_VENDOR_MODULE := true
LOCAL_32_BIT_ONLY := true
+# Since this is 32-bit-only module, only 32-bit version of the codecs are installed.
+# TODO(b/72343507): eliminate the need for manually adding .vendor suffix. This should be done
+# by the build system.
+LOCAL_REQUIRED_MODULES := \
+$(foreach codec,$(_software_codecs),\
+ $(eval _vendor_suffix := $(if $(BOARD_VNDK_VERSION),.vendor))\
+ $(codec)$(_vendor_suffix)\
+)
+_software_codecs :=
LOCAL_INIT_RC := android.hardware.media.omx@1.0-service.rc
include $(BUILD_EXECUTABLE)
diff --git a/services/mediadrm/Android.mk b/services/mediadrm/Android.mk
index 2daa829..e870965 100644
--- a/services/mediadrm/Android.mk
+++ b/services/mediadrm/Android.mk
@@ -28,7 +28,8 @@
libhidlbase \
libhidlmemory \
libhidltransport \
- android.hardware.drm@1.0
+ android.hardware.drm@1.0 \
+ android.hardware.drm@1.1
LOCAL_CFLAGS += -Wall -Wextra -Werror
diff --git a/services/mediaextractor/Android.mk b/services/mediaextractor/Android.mk
index 4980316..9f3746f 100644
--- a/services/mediaextractor/Android.mk
+++ b/services/mediaextractor/Android.mk
@@ -21,6 +21,16 @@
# extractor libraries
LOCAL_REQUIRED_MODULES := \
+ libaacextractor \
+ libamrextractor \
+ libflacextractor \
+ libmidiextractor \
+ libmkvextractor \
+ libmp3extractor \
+ libmp4extractor \
+ libmpeg2extractor \
+ liboggextractor \
+ libwavextractor \
MediaComponents \
LOCAL_SRC_FILES := main_extractorservice.cpp
diff --git a/services/mediaextractor/MediaExtractorService.cpp b/services/mediaextractor/MediaExtractorService.cpp
index 0dc1fce..f0f44f5 100644
--- a/services/mediaextractor/MediaExtractorService.cpp
+++ b/services/mediaextractor/MediaExtractorService.cpp
@@ -56,7 +56,7 @@
}
status_t MediaExtractorService::dump(int fd, const Vector<String16>& args) {
- return dumpExtractors(fd, args);
+ return MediaExtractorFactory::dump(fd, args) || dumpExtractors(fd, args);
}
status_t MediaExtractorService::onTransact(uint32_t code, const Parcel& data, Parcel* reply,
diff --git a/services/mediaextractor/main_extractorservice.cpp b/services/mediaextractor/main_extractorservice.cpp
index 0dc5e29..8d3359a 100644
--- a/services/mediaextractor/main_extractorservice.cpp
+++ b/services/mediaextractor/main_extractorservice.cpp
@@ -25,6 +25,7 @@
#include <string>
#include <android-base/logging.h>
+#include <android-base/properties.h>
#include <utils/misc.h>
// from LOCAL_C_INCLUDES
@@ -65,14 +66,10 @@
sp<IServiceManager> sm = defaultServiceManager();
MediaExtractorService::instantiate();
- // TODO: Uncomment below once sepolicy change is landed.
- /*
- char value[PROPERTY_VALUE_MAX];
- property_get("ro.build.type", value, "unknown");
- if (strcmp(value, "userdebug") == 0 || strcmp(value, "eng") == 0) {
+ std::string value = base::GetProperty("ro.build.type", "unknown");
+ if (value == "userdebug" || value == "eng") {
media::MediaExtractorUpdateService::instantiate();
}
- */
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();
diff --git a/services/oboeservice/AAudioEndpointManager.cpp b/services/oboeservice/AAudioEndpointManager.cpp
index b0c3771..7b8d817 100644
--- a/services/oboeservice/AAudioEndpointManager.cpp
+++ b/services/oboeservice/AAudioEndpointManager.cpp
@@ -102,8 +102,8 @@
}
}
- ALOGV("findExclusiveEndpoint_l(), found %p for device = %d",
- endpoint.get(), configuration.getDeviceId());
+ ALOGV("findExclusiveEndpoint_l(), found %p for device = %d, sessionId = %d",
+ endpoint.get(), configuration.getDeviceId(), configuration.getSessionId());
return endpoint;
}
@@ -118,8 +118,8 @@
}
}
- ALOGV("findSharedEndpoint_l(), found %p for device = %d",
- endpoint.get(), configuration.getDeviceId());
+ ALOGV("findSharedEndpoint_l(), found %p for device = %d, sessionId = %d",
+ endpoint.get(), configuration.getDeviceId(), configuration.getSessionId());
return endpoint;
}
@@ -151,19 +151,17 @@
return nullptr;
} else {
sp<AAudioServiceEndpointMMAP> endpointMMap = new AAudioServiceEndpointMMAP();
- ALOGD("openEndpoint(),created MMAP %p", endpointMMap.get());
+ ALOGD("openExclusiveEndpoint(), no match so try to open MMAP %p for dev %d",
+ endpointMMap.get(), configuration.getDeviceId());
endpoint = endpointMMap;
aaudio_result_t result = endpoint->open(request);
if (result != AAUDIO_OK) {
- ALOGE("openEndpoint(), open failed");
+ ALOGE("openExclusiveEndpoint(), open failed");
endpoint.clear();
} else {
mExclusiveStreams.push_back(endpointMMap);
}
-
- ALOGD("openEndpoint(), created %p for device = %d",
- endpoint.get(), configuration.getDeviceId());
}
if (endpoint.get() != nullptr) {
@@ -209,7 +207,7 @@
mSharedStreams.push_back(endpoint);
}
}
- ALOGD("openSharedEndpoint(), created %p for device = %d, dir = %d",
+ ALOGD("openSharedEndpoint(), created %p, requested device = %d, dir = %d",
endpoint.get(), configuration.getDeviceId(), (int)direction);
IPCThreadState::self()->restoreCallingIdentity(token);
}
diff --git a/services/oboeservice/AAudioEndpointManager.h b/services/oboeservice/AAudioEndpointManager.h
index 32c8454..f6aeb5a 100644
--- a/services/oboeservice/AAudioEndpointManager.h
+++ b/services/oboeservice/AAudioEndpointManager.h
@@ -47,7 +47,7 @@
std::string dump() const;
/**
- * Find a service endpoint for the given deviceId and direction.
+ * Find a service endpoint for the given deviceId, sessionId and direction.
* If an endpoint does not already exist then try to create one.
*
* @param audioService
diff --git a/services/oboeservice/AAudioServiceEndpoint.cpp b/services/oboeservice/AAudioServiceEndpoint.cpp
index 01e9c6f..33439fc 100644
--- a/services/oboeservice/AAudioServiceEndpoint.cpp
+++ b/services/oboeservice/AAudioServiceEndpoint.cpp
@@ -64,6 +64,7 @@
result << " ContentType: " << getContentType() << "\n";
result << " InputPreset: " << getInputPreset() << "\n";
result << " Reference Count: " << mOpenCount << "\n";
+ result << " Session Id: " << getSessionId() << "\n";
result << " Connected: " << mConnected.load() << "\n";
result << " Registered Streams:" << "\n";
result << AAudioServiceStreamShared::dumpHeader() << "\n";
@@ -113,6 +114,10 @@
configuration.getDeviceId() != getDeviceId()) {
return false;
}
+ if (configuration.getSessionId() != AAUDIO_SESSION_ID_ALLOCATE &&
+ configuration.getSessionId() != getSessionId()) {
+ return false;
+ }
if (configuration.getSampleRate() != AAUDIO_UNSPECIFIED &&
configuration.getSampleRate() != getSampleRate()) {
return false;
diff --git a/services/oboeservice/AAudioServiceEndpointMMAP.cpp b/services/oboeservice/AAudioServiceEndpointMMAP.cpp
index 541be20..db01c88 100644
--- a/services/oboeservice/AAudioServiceEndpointMMAP.cpp
+++ b/services/oboeservice/AAudioServiceEndpointMMAP.cpp
@@ -144,12 +144,16 @@
? MmapStreamInterface::DIRECTION_OUTPUT
: MmapStreamInterface::DIRECTION_INPUT;
+ aaudio_session_id_t requestedSessionId = getSessionId();
+ audio_session_t sessionId = AAudioConvert_aaudioToAndroidSessionId(requestedSessionId);
+
// Open HAL stream. Set mMmapStream
status_t status = MmapStreamInterface::openMmapStream(streamDirection,
&attributes,
&config,
mMmapClient,
&deviceId,
+ &sessionId,
this, // callback
mMmapStream,
&mPortHandle);
@@ -165,6 +169,17 @@
}
setDeviceId(deviceId);
+ if (sessionId == AUDIO_SESSION_ALLOCATE) {
+ ALOGW("open() - openMmapStream() failed to set sessionId");
+ }
+
+ aaudio_session_id_t actualSessionId =
+ (requestedSessionId == AAUDIO_SESSION_ID_NONE)
+ ? AAUDIO_SESSION_ID_NONE
+ : (aaudio_session_id_t) sessionId;
+ setSessionId(actualSessionId);
+ ALOGD("open() deviceId = %d, sessionId = %d", getDeviceId(), getSessionId());
+
// Create MMAP/NOIRQ buffer.
int32_t minSizeFrames = getBufferCapacity();
if (minSizeFrames <= 0) { // zero will get rejected
diff --git a/services/oboeservice/AAudioServiceEndpointShared.cpp b/services/oboeservice/AAudioServiceEndpointShared.cpp
index 2de537a..584efe5 100644
--- a/services/oboeservice/AAudioServiceEndpointShared.cpp
+++ b/services/oboeservice/AAudioServiceEndpointShared.cpp
@@ -77,6 +77,8 @@
setSampleRate(mStreamInternal->getSampleRate());
setSamplesPerFrame(mStreamInternal->getSamplesPerFrame());
setDeviceId(mStreamInternal->getDeviceId());
+ setSessionId(mStreamInternal->getSessionId());
+ ALOGD("open() deviceId = %d, sessionId = %d", getDeviceId(), getSessionId());
mFramesPerBurst = mStreamInternal->getFramesPerBurst();
return result;
diff --git a/services/oboeservice/AAudioServiceStreamBase.cpp b/services/oboeservice/AAudioServiceStreamBase.cpp
index 864fc35..e8c9e41 100644
--- a/services/oboeservice/AAudioServiceStreamBase.cpp
+++ b/services/oboeservice/AAudioServiceStreamBase.cpp
@@ -271,11 +271,11 @@
}
aaudio_result_t AAudioServiceStreamBase::flush() {
- if (getState() != AAUDIO_STREAM_STATE_PAUSED) {
- ALOGE("flush() stream not paused, state = %s",
- AAudio_convertStreamStateToText(mState));
- return AAUDIO_ERROR_INVALID_STATE;
+ aaudio_result_t result = AAudio_isFlushAllowed(getState());
+ if (result != AAUDIO_OK) {
+ return result;
}
+
// Data will get flushed when the client receives the FLUSHED event.
sendServiceEvent(AAUDIO_SERVICE_EVENT_FLUSHED);
setState(AAUDIO_STREAM_STATE_FLUSHED);
diff --git a/services/soundtrigger/Android.mk b/services/soundtrigger/Android.mk
index 10ee141..ad3666e 100644
--- a/services/soundtrigger/Android.mk
+++ b/services/soundtrigger/Android.mk
@@ -49,11 +49,16 @@
LOCAL_SHARED_LIBRARIES += \
libhwbinder \
libhidlbase \
+ libhidlmemory \
libhidltransport \
libbase \
libaudiohal \
+ libaudiohal_deathhandler \
android.hardware.soundtrigger@2.0 \
- android.hardware.audio.common@2.0
+ android.hardware.soundtrigger@2.1 \
+ android.hardware.audio.common@2.0 \
+ android.hidl.allocator@1.0 \
+ android.hidl.memory@1.0
endif
diff --git a/services/soundtrigger/SoundTriggerHalHidl.cpp b/services/soundtrigger/SoundTriggerHalHidl.cpp
index 0cd5cf7..adf252e 100644
--- a/services/soundtrigger/SoundTriggerHalHidl.cpp
+++ b/services/soundtrigger/SoundTriggerHalHidl.cpp
@@ -17,17 +17,87 @@
#define LOG_TAG "SoundTriggerHalHidl"
//#define LOG_NDEBUG 0
+#include <android/hidl/allocator/1.0/IAllocator.h>
#include <media/audiohal/hidl/HalDeathHandler.h>
#include <utils/Log.h>
#include "SoundTriggerHalHidl.h"
+#include <hidlmemory/mapping.h>
#include <hwbinder/IPCThreadState.h>
#include <hwbinder/ProcessState.h>
namespace android {
-using android::hardware::Return;
-using android::hardware::ProcessState;
-using android::hardware::audio::common::V2_0::AudioDevice;
+using ::android::hardware::ProcessState;
+using ::android::hardware::Return;
+using ::android::hardware::Status;
+using ::android::hardware::Void;
+using ::android::hardware::audio::common::V2_0::AudioDevice;
+using ::android::hardware::hidl_memory;
+using ::android::hidl::allocator::V1_0::IAllocator;
+using ::android::hidl::memory::V1_0::IMemory;
+
+namespace {
+
+// Backs up by the vector with the contents of shared memory.
+// It is assumed that the passed hidl_vector is empty, so it's
+// not cleared if the memory is a null object.
+// The caller needs to keep the returned sp<IMemory> as long as
+// the data is needed.
+std::pair<bool, sp<IMemory>> memoryAsVector(const hidl_memory& m, hidl_vec<uint8_t>* vec) {
+ sp<IMemory> memory;
+ if (m.size() == 0) {
+ return std::make_pair(true, memory);
+ }
+ memory = mapMemory(m);
+ if (memory != nullptr) {
+ memory->read();
+ vec->setToExternal(static_cast<uint8_t*>(static_cast<void*>(memory->getPointer())),
+ memory->getSize());
+ return std::make_pair(true, memory);
+ }
+ ALOGE("%s: Could not map HIDL memory to IMemory", __func__);
+ return std::make_pair(false, memory);
+}
+
+// Moves the data from the vector into allocated shared memory,
+// emptying the vector.
+// It is assumed that the passed hidl_memory is a null object, so it's
+// not reset if the vector is empty.
+// The caller needs to keep the returned sp<IMemory> as long as
+// the data is needed.
+std::pair<bool, sp<IMemory>> moveVectorToMemory(hidl_vec<uint8_t>* v, hidl_memory* mem) {
+ sp<IMemory> memory;
+ if (v->size() == 0) {
+ return std::make_pair(true, memory);
+ }
+ sp<IAllocator> ashmem = IAllocator::getService("ashmem");
+ if (ashmem == 0) {
+ ALOGE("Failed to retrieve ashmem allocator service");
+ return std::make_pair(false, memory);
+ }
+ bool success = false;
+ Return<void> r = ashmem->allocate(v->size(), [&](bool s, const hidl_memory& m) {
+ success = s;
+ if (success) *mem = m;
+ });
+ if (r.isOk() && success) {
+ memory = hardware::mapMemory(*mem);
+ if (memory != 0) {
+ memory->update();
+ memcpy(memory->getPointer(), v->data(), v->size());
+ memory->commit();
+ v->resize(0);
+ return std::make_pair(true, memory);
+ } else {
+ ALOGE("Failed to map allocated ashmem");
+ }
+ } else {
+ ALOGE("Failed to allocate %llu bytes from ashmem", (unsigned long long)v->size());
+ }
+ return std::make_pair(false, memory);
+}
+
+} // namespace
/* static */
sp<SoundTriggerHalInterface> SoundTriggerHalInterface::connectModule(const char *moduleName)
@@ -94,36 +164,62 @@
"loadSoundModel(): wrap around in sound model IDs, num loaded models %zd",
mSoundModels.size());
- ISoundTriggerHw::SoundModel *halSoundModel =
- convertSoundModelToHal(sound_model);
- if (halSoundModel == NULL) {
- return -EINVAL;
- }
-
Return<void> hidlReturn;
int ret;
SoundModelHandle halHandle;
- {
- AutoMutex lock(mHalLock);
- if (sound_model->type == SOUND_MODEL_TYPE_KEYPHRASE) {
+ sp<V2_1_ISoundTriggerHw> soundtrigger_2_1 = toService2_1(soundtrigger);
+ if (sound_model->type == SOUND_MODEL_TYPE_KEYPHRASE) {
+ if (!soundtrigger_2_1) {
+ ISoundTriggerHw::PhraseSoundModel halSoundModel;
+ convertPhraseSoundModelToHal(&halSoundModel, sound_model);
+ AutoMutex lock(mHalLock);
hidlReturn = soundtrigger->loadPhraseSoundModel(
- *(const ISoundTriggerHw::PhraseSoundModel *)halSoundModel,
+ halSoundModel,
this, modelId, [&](int32_t retval, auto res) {
- ret = retval;
- halHandle = res;
- });
-
+ ret = retval;
+ halHandle = res;
+ });
} else {
- hidlReturn = soundtrigger->loadSoundModel(*halSoundModel,
+ V2_1_ISoundTriggerHw::PhraseSoundModel halSoundModel;
+ auto result = convertPhraseSoundModelToHal(&halSoundModel, sound_model);
+ if (result.first) {
+ AutoMutex lock(mHalLock);
+ hidlReturn = soundtrigger_2_1->loadPhraseSoundModel_2_1(
+ halSoundModel,
+ this, modelId, [&](int32_t retval, auto res) {
+ ret = retval;
+ halHandle = res;
+ });
+ } else {
+ return NO_MEMORY;
+ }
+ }
+ } else {
+ if (!soundtrigger_2_1) {
+ ISoundTriggerHw::SoundModel halSoundModel;
+ convertSoundModelToHal(&halSoundModel, sound_model);
+ AutoMutex lock(mHalLock);
+ hidlReturn = soundtrigger->loadSoundModel(halSoundModel,
this, modelId, [&](int32_t retval, auto res) {
- ret = retval;
- halHandle = res;
- });
+ ret = retval;
+ halHandle = res;
+ });
+ } else {
+ V2_1_ISoundTriggerHw::SoundModel halSoundModel;
+ auto result = convertSoundModelToHal(&halSoundModel, sound_model);
+ if (result.first) {
+ AutoMutex lock(mHalLock);
+ hidlReturn = soundtrigger_2_1->loadSoundModel_2_1(halSoundModel,
+ this, modelId, [&](int32_t retval, auto res) {
+ ret = retval;
+ halHandle = res;
+ });
+ } else {
+ return NO_MEMORY;
+ }
}
}
- delete halSoundModel;
-
if (hidlReturn.isOk()) {
if (ret == 0) {
AutoMutex lock(mLock);
@@ -185,16 +281,27 @@
model->mRecognitionCallback = callback;
model->mRecognitionCookie = cookie;
- ISoundTriggerHw::RecognitionConfig *halConfig =
- convertRecognitionConfigToHal(config);
-
+ sp<V2_1_ISoundTriggerHw> soundtrigger_2_1 = toService2_1(soundtrigger);
Return<int32_t> hidlReturn(0);
- {
- AutoMutex lock(mHalLock);
- hidlReturn = soundtrigger->startRecognition(model->mHalHandle, *halConfig, this, handle);
- }
- delete halConfig;
+ if (!soundtrigger_2_1) {
+ ISoundTriggerHw::RecognitionConfig halConfig;
+ convertRecognitionConfigToHal(&halConfig, config);
+ {
+ AutoMutex lock(mHalLock);
+ hidlReturn = soundtrigger->startRecognition(model->mHalHandle, halConfig, this, handle);
+ }
+ } else {
+ V2_1_ISoundTriggerHw::RecognitionConfig halConfig;
+ auto result = convertRecognitionConfigToHal(&halConfig, config);
+ if (result.first) {
+ AutoMutex lock(mHalLock);
+ hidlReturn = soundtrigger_2_1->startRecognition_2_1(
+ model->mHalHandle, halConfig, this, handle);
+ } else {
+ return NO_MEMORY;
+ }
+ }
if (!hidlReturn.isOk()) {
ALOGE("startRecognition error %s", hidlReturn.description().c_str());
@@ -275,6 +382,12 @@
return mISoundTrigger;
}
+sp<V2_1_ISoundTriggerHw> SoundTriggerHalHidl::toService2_1(const sp<ISoundTriggerHw>& s)
+{
+ auto castResult_2_1 = V2_1_ISoundTriggerHw::castFrom(s);
+ return castResult_2_1.isOk() ? static_cast<sp<V2_1_ISoundTriggerHw>>(castResult_2_1) : nullptr;
+}
+
sp<SoundTriggerHalHidl::SoundModel> SoundTriggerHalHidl::getModel(sound_model_handle_t handle)
{
AutoMutex lock(mLock);
@@ -347,40 +460,52 @@
halTriggerPhrase->text = triggerPhrase->text;
}
-ISoundTriggerHw::SoundModel *SoundTriggerHalHidl::convertSoundModelToHal(
+
+void SoundTriggerHalHidl::convertTriggerPhrasesToHal(
+ hidl_vec<ISoundTriggerHw::Phrase> *halTriggerPhrases,
+ struct sound_trigger_phrase_sound_model *keyPhraseModel)
+{
+ halTriggerPhrases->resize(keyPhraseModel->num_phrases);
+ for (unsigned int i = 0; i < keyPhraseModel->num_phrases; i++) {
+ convertTriggerPhraseToHal(&(*halTriggerPhrases)[i], &keyPhraseModel->phrases[i]);
+ }
+}
+
+void SoundTriggerHalHidl::convertSoundModelToHal(ISoundTriggerHw::SoundModel *halModel,
const struct sound_trigger_sound_model *soundModel)
{
- ISoundTriggerHw::SoundModel *halModel = NULL;
- if (soundModel->type == SOUND_MODEL_TYPE_KEYPHRASE) {
- ISoundTriggerHw::PhraseSoundModel *halKeyPhraseModel =
- new ISoundTriggerHw::PhraseSoundModel();
- struct sound_trigger_phrase_sound_model *keyPhraseModel =
- (struct sound_trigger_phrase_sound_model *)soundModel;
- ISoundTriggerHw::Phrase *halPhrases =
- new ISoundTriggerHw::Phrase[keyPhraseModel->num_phrases];
-
-
- for (unsigned int i = 0; i < keyPhraseModel->num_phrases; i++) {
- convertTriggerPhraseToHal(&halPhrases[i],
- &keyPhraseModel->phrases[i]);
- }
- halKeyPhraseModel->phrases.setToExternal(halPhrases, keyPhraseModel->num_phrases);
- // FIXME: transfer buffer ownership. should have a method for that in hidl_vec
- halKeyPhraseModel->phrases.resize(keyPhraseModel->num_phrases);
-
- delete[] halPhrases;
-
- halModel = (ISoundTriggerHw::SoundModel *)halKeyPhraseModel;
- } else {
- halModel = new ISoundTriggerHw::SoundModel();
- }
halModel->type = (SoundModelType)soundModel->type;
convertUuidToHal(&halModel->uuid, &soundModel->uuid);
convertUuidToHal(&halModel->vendorUuid, &soundModel->vendor_uuid);
halModel->data.setToExternal((uint8_t *)soundModel + soundModel->data_offset, soundModel->data_size);
- halModel->data.resize(soundModel->data_size);
+}
- return halModel;
+std::pair<bool, sp<IMemory>> SoundTriggerHalHidl::convertSoundModelToHal(
+ V2_1_ISoundTriggerHw::SoundModel *halModel,
+ const struct sound_trigger_sound_model *soundModel)
+{
+ convertSoundModelToHal(&halModel->header, soundModel);
+ return moveVectorToMemory(&halModel->header.data, &halModel->data);
+}
+
+void SoundTriggerHalHidl::convertPhraseSoundModelToHal(
+ ISoundTriggerHw::PhraseSoundModel *halKeyPhraseModel,
+ const struct sound_trigger_sound_model *soundModel)
+{
+ struct sound_trigger_phrase_sound_model *keyPhraseModel =
+ (struct sound_trigger_phrase_sound_model *)soundModel;
+ convertTriggerPhrasesToHal(&halKeyPhraseModel->phrases, keyPhraseModel);
+ convertSoundModelToHal(&halKeyPhraseModel->common, soundModel);
+}
+
+std::pair<bool, sp<IMemory>> SoundTriggerHalHidl::convertPhraseSoundModelToHal(
+ V2_1_ISoundTriggerHw::PhraseSoundModel *halKeyPhraseModel,
+ const struct sound_trigger_sound_model *soundModel)
+{
+ struct sound_trigger_phrase_sound_model *keyPhraseModel =
+ (struct sound_trigger_phrase_sound_model *)soundModel;
+ convertTriggerPhrasesToHal(&halKeyPhraseModel->phrases, keyPhraseModel);
+ return convertSoundModelToHal(&halKeyPhraseModel->common, soundModel);
}
void SoundTriggerHalHidl::convertPhraseRecognitionExtraToHal(
@@ -390,52 +515,42 @@
halExtra->id = extra->id;
halExtra->recognitionModes = extra->recognition_modes;
halExtra->confidenceLevel = extra->confidence_level;
- ConfidenceLevel *halLevels =
- new ConfidenceLevel[extra->num_levels];
- for (unsigned int i = 0; i < extra->num_levels; i++) {
- halLevels[i].userId = extra->levels[i].user_id;
- halLevels[i].levelPercent = extra->levels[i].level;
- }
- halExtra->levels.setToExternal(halLevels, extra->num_levels);
- // FIXME: transfer buffer ownership. should have a method for that in hidl_vec
halExtra->levels.resize(extra->num_levels);
-
- delete[] halLevels;
+ for (unsigned int i = 0; i < extra->num_levels; i++) {
+ halExtra->levels[i].userId = extra->levels[i].user_id;
+ halExtra->levels[i].levelPercent = extra->levels[i].level;
+ }
}
-
-ISoundTriggerHw::RecognitionConfig *SoundTriggerHalHidl::convertRecognitionConfigToHal(
+void SoundTriggerHalHidl::convertRecognitionConfigToHal(
+ ISoundTriggerHw::RecognitionConfig *halConfig,
const struct sound_trigger_recognition_config *config)
{
- ISoundTriggerHw::RecognitionConfig *halConfig =
- new ISoundTriggerHw::RecognitionConfig();
-
halConfig->captureHandle = config->capture_handle;
halConfig->captureDevice = (AudioDevice)config->capture_device;
halConfig->captureRequested = (uint32_t)config->capture_requested;
- PhraseRecognitionExtra *halExtras =
- new PhraseRecognitionExtra[config->num_phrases];
-
+ halConfig->phrases.resize(config->num_phrases);
for (unsigned int i = 0; i < config->num_phrases; i++) {
- convertPhraseRecognitionExtraToHal(&halExtras[i],
+ convertPhraseRecognitionExtraToHal(&halConfig->phrases[i],
&config->phrases[i]);
}
- halConfig->phrases.setToExternal(halExtras, config->num_phrases);
- // FIXME: transfer buffer ownership. should have a method for that in hidl_vec
- halConfig->phrases.resize(config->num_phrases);
-
- delete[] halExtras;
halConfig->data.setToExternal((uint8_t *)config + config->data_offset, config->data_size);
+}
- return halConfig;
+std::pair<bool, sp<IMemory>> SoundTriggerHalHidl::convertRecognitionConfigToHal(
+ V2_1_ISoundTriggerHw::RecognitionConfig *halConfig,
+ const struct sound_trigger_recognition_config *config)
+{
+ convertRecognitionConfigToHal(&halConfig->header, config);
+ return moveVectorToMemory(&halConfig->header.data, &halConfig->data);
}
// ISoundTriggerHwCallback
::android::hardware::Return<void> SoundTriggerHalHidl::recognitionCallback(
- const ISoundTriggerHwCallback::RecognitionEvent& halEvent,
+ const V2_0_ISoundTriggerHwCallback::RecognitionEvent& halEvent,
CallbackCookie cookie)
{
sp<SoundModel> model;
@@ -459,7 +574,7 @@
}
::android::hardware::Return<void> SoundTriggerHalHidl::phraseRecognitionCallback(
- const ISoundTriggerHwCallback::PhraseRecognitionEvent& halEvent,
+ const V2_0_ISoundTriggerHwCallback::PhraseRecognitionEvent& halEvent,
CallbackCookie cookie)
{
sp<SoundModel> model;
@@ -471,14 +586,13 @@
}
}
- struct sound_trigger_recognition_event *event = convertRecognitionEventFromHal(
- (const ISoundTriggerHwCallback::RecognitionEvent *)&halEvent);
+ struct sound_trigger_phrase_recognition_event *event =
+ convertPhraseRecognitionEventFromHal(&halEvent);
if (event == NULL) {
return Return<void>();
}
-
- event->model = model->mHandle;
- model->mRecognitionCallback(event, model->mRecognitionCookie);
+ event->common.model = model->mHandle;
+ model->mRecognitionCallback(&event->common, model->mRecognitionCookie);
free(event);
@@ -486,7 +600,7 @@
}
::android::hardware::Return<void> SoundTriggerHalHidl::soundModelCallback(
- const ISoundTriggerHwCallback::ModelEvent& halEvent,
+ const V2_0_ISoundTriggerHwCallback::ModelEvent& halEvent,
CallbackCookie cookie)
{
sp<SoundModel> model;
@@ -511,9 +625,37 @@
return Return<void>();
}
+::android::hardware::Return<void> SoundTriggerHalHidl::recognitionCallback_2_1(
+ const ISoundTriggerHwCallback::RecognitionEvent& event, CallbackCookie cookie) {
+ // The data vector in the 'header' part of V2.1 structure is empty, thus copying is cheap.
+ V2_0_ISoundTriggerHwCallback::RecognitionEvent event_2_0 = event.header;
+ auto result = memoryAsVector(event.data, &event_2_0.data);
+ return result.first ? recognitionCallback(event_2_0, cookie) : Void();
+}
+
+::android::hardware::Return<void> SoundTriggerHalHidl::phraseRecognitionCallback_2_1(
+ const ISoundTriggerHwCallback::PhraseRecognitionEvent& event, int32_t cookie) {
+ V2_0_ISoundTriggerHwCallback::PhraseRecognitionEvent event_2_0;
+ // The data vector in the 'header' part of V2.1 structure is empty, thus copying is cheap.
+ event_2_0.common = event.common.header;
+ event_2_0.phraseExtras.setToExternal(
+ const_cast<PhraseRecognitionExtra*>(event.phraseExtras.data()),
+ event.phraseExtras.size());
+ auto result = memoryAsVector(event.common.data, &event_2_0.common.data);
+ return result.first ? phraseRecognitionCallback(event_2_0, cookie) : Void();
+}
+
+::android::hardware::Return<void> SoundTriggerHalHidl::soundModelCallback_2_1(
+ const ISoundTriggerHwCallback::ModelEvent& event, CallbackCookie cookie) {
+ // The data vector in the 'header' part of V2.1 structure is empty, thus copying is cheap.
+ V2_0_ISoundTriggerHwCallback::ModelEvent event_2_0 = event.header;
+ auto result = memoryAsVector(event.data, &event_2_0.data);
+ return result.first ? soundModelCallback(event_2_0, cookie) : Void();
+}
+
struct sound_trigger_model_event *SoundTriggerHalHidl::convertSoundModelEventFromHal(
- const ISoundTriggerHwCallback::ModelEvent *halEvent)
+ const V2_0_ISoundTriggerHwCallback::ModelEvent *halEvent)
{
struct sound_trigger_model_event *event = (struct sound_trigger_model_event *)malloc(
sizeof(struct sound_trigger_model_event) +
@@ -550,37 +692,55 @@
}
-struct sound_trigger_recognition_event *SoundTriggerHalHidl::convertRecognitionEventFromHal(
- const ISoundTriggerHwCallback::RecognitionEvent *halEvent)
+struct sound_trigger_phrase_recognition_event* SoundTriggerHalHidl::convertPhraseRecognitionEventFromHal(
+ const V2_0_ISoundTriggerHwCallback::PhraseRecognitionEvent *halPhraseEvent)
{
- struct sound_trigger_recognition_event *event;
-
- if (halEvent->type == SoundModelType::KEYPHRASE) {
- struct sound_trigger_phrase_recognition_event *phraseEvent =
- (struct sound_trigger_phrase_recognition_event *)malloc(
- sizeof(struct sound_trigger_phrase_recognition_event) +
- halEvent->data.size());
- if (phraseEvent == NULL) {
- return NULL;
- }
- const ISoundTriggerHwCallback::PhraseRecognitionEvent *halPhraseEvent =
- (const ISoundTriggerHwCallback::PhraseRecognitionEvent *)halEvent;
-
- for (unsigned int i = 0; i < halPhraseEvent->phraseExtras.size(); i++) {
- convertPhraseRecognitionExtraFromHal(&phraseEvent->phrase_extras[i],
- &halPhraseEvent->phraseExtras[i]);
- }
- phraseEvent->num_phrases = halPhraseEvent->phraseExtras.size();
- event = (struct sound_trigger_recognition_event *)phraseEvent;
- event->data_offset = sizeof(sound_trigger_phrase_recognition_event);
- } else {
- event = (struct sound_trigger_recognition_event *)malloc(
- sizeof(struct sound_trigger_recognition_event) + halEvent->data.size());
- if (event == NULL) {
- return NULL;
- }
- event->data_offset = sizeof(sound_trigger_recognition_event);
+ if (halPhraseEvent->common.type != SoundModelType::KEYPHRASE) {
+ ALOGE("Received non-keyphrase event type as PhraseRecognitionEvent");
+ return NULL;
}
+ struct sound_trigger_phrase_recognition_event *phraseEvent =
+ (struct sound_trigger_phrase_recognition_event *)malloc(
+ sizeof(struct sound_trigger_phrase_recognition_event) +
+ halPhraseEvent->common.data.size());
+ if (phraseEvent == NULL) {
+ return NULL;
+ }
+ phraseEvent->common.data_offset = sizeof(sound_trigger_phrase_recognition_event);
+
+ for (unsigned int i = 0; i < halPhraseEvent->phraseExtras.size(); i++) {
+ convertPhraseRecognitionExtraFromHal(&phraseEvent->phrase_extras[i],
+ &halPhraseEvent->phraseExtras[i]);
+ }
+ phraseEvent->num_phrases = halPhraseEvent->phraseExtras.size();
+
+ fillRecognitionEventFromHal(&phraseEvent->common, &halPhraseEvent->common);
+ return phraseEvent;
+}
+
+struct sound_trigger_recognition_event *SoundTriggerHalHidl::convertRecognitionEventFromHal(
+ const V2_0_ISoundTriggerHwCallback::RecognitionEvent *halEvent)
+{
+ if (halEvent->type == SoundModelType::KEYPHRASE) {
+ ALOGE("Received keyphrase event type as RecognitionEvent");
+ return NULL;
+ }
+ struct sound_trigger_recognition_event *event;
+ event = (struct sound_trigger_recognition_event *)malloc(
+ sizeof(struct sound_trigger_recognition_event) + halEvent->data.size());
+ if (event == NULL) {
+ return NULL;
+ }
+ event->data_offset = sizeof(sound_trigger_recognition_event);
+
+ fillRecognitionEventFromHal(event, halEvent);
+ return event;
+}
+
+void SoundTriggerHalHidl::fillRecognitionEventFromHal(
+ struct sound_trigger_recognition_event *event,
+ const V2_0_ISoundTriggerHwCallback::RecognitionEvent *halEvent)
+{
event->status = (int)halEvent->status;
event->type = (sound_trigger_sound_model_type_t)halEvent->type;
// event->model to be set by caller
@@ -597,8 +757,6 @@
uint8_t *dst = (uint8_t *)event + event->data_offset;
uint8_t *src = (uint8_t *)&halEvent->data[0];
memcpy(dst, src, halEvent->data.size());
-
- return event;
}
} // namespace android
diff --git a/services/soundtrigger/SoundTriggerHalHidl.h b/services/soundtrigger/SoundTriggerHalHidl.h
index 0c68cf1..0b44ae0 100644
--- a/services/soundtrigger/SoundTriggerHalHidl.h
+++ b/services/soundtrigger/SoundTriggerHalHidl.h
@@ -17,6 +17,8 @@
#ifndef ANDROID_HARDWARE_SOUNDTRIGGER_HAL_HIDL_H
#define ANDROID_HARDWARE_SOUNDTRIGGER_HAL_HIDL_H
+#include <utility>
+
#include <stdatomic.h>
#include <utils/RefBase.h>
#include <utils/KeyedVector.h>
@@ -24,21 +26,29 @@
#include <utils/threads.h>
#include "SoundTriggerHalInterface.h"
#include <android/hardware/soundtrigger/2.0/types.h>
-#include <android/hardware/soundtrigger/2.0/ISoundTriggerHw.h>
+#include <android/hardware/soundtrigger/2.1/ISoundTriggerHw.h>
#include <android/hardware/soundtrigger/2.0/ISoundTriggerHwCallback.h>
+#include <android/hardware/soundtrigger/2.1/ISoundTriggerHwCallback.h>
namespace android {
-using android::hardware::audio::common::V2_0::Uuid;
-using android::hardware::soundtrigger::V2_0::ConfidenceLevel;
-using android::hardware::soundtrigger::V2_0::PhraseRecognitionExtra;
-using android::hardware::soundtrigger::V2_0::SoundModelType;
-using android::hardware::soundtrigger::V2_0::SoundModelHandle;
-using android::hardware::soundtrigger::V2_0::ISoundTriggerHw;
-using android::hardware::soundtrigger::V2_0::ISoundTriggerHwCallback;
+using ::android::hardware::audio::common::V2_0::Uuid;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::soundtrigger::V2_0::ConfidenceLevel;
+using ::android::hardware::soundtrigger::V2_0::PhraseRecognitionExtra;
+using ::android::hardware::soundtrigger::V2_0::SoundModelType;
+using ::android::hardware::soundtrigger::V2_0::SoundModelHandle;
+using ::android::hardware::soundtrigger::V2_0::ISoundTriggerHw;
+using V2_0_ISoundTriggerHwCallback =
+ ::android::hardware::soundtrigger::V2_0::ISoundTriggerHwCallback;
+using V2_1_ISoundTriggerHw =
+ ::android::hardware::soundtrigger::V2_1::ISoundTriggerHw;
+using V2_1_ISoundTriggerHwCallback =
+ ::android::hardware::soundtrigger::V2_1::ISoundTriggerHwCallback;
+using ::android::hidl::memory::V1_0::IMemory;
class SoundTriggerHalHidl : public SoundTriggerHalInterface,
- public virtual ISoundTriggerHwCallback
+ public virtual V2_1_ISoundTriggerHwCallback
{
public:
@@ -84,11 +94,17 @@
// ISoundTriggerHwCallback
virtual ::android::hardware::Return<void> recognitionCallback(
- const ISoundTriggerHwCallback::RecognitionEvent& event, CallbackCookie cookie);
+ const V2_0_ISoundTriggerHwCallback::RecognitionEvent& event, CallbackCookie cookie);
virtual ::android::hardware::Return<void> phraseRecognitionCallback(
- const ISoundTriggerHwCallback::PhraseRecognitionEvent& event, int32_t cookie);
+ const V2_0_ISoundTriggerHwCallback::PhraseRecognitionEvent& event, int32_t cookie);
virtual ::android::hardware::Return<void> soundModelCallback(
- const ISoundTriggerHwCallback::ModelEvent& event, CallbackCookie cookie);
+ const V2_0_ISoundTriggerHwCallback::ModelEvent& event, CallbackCookie cookie);
+ virtual ::android::hardware::Return<void> recognitionCallback_2_1(
+ const RecognitionEvent& event, CallbackCookie cookie);
+ virtual ::android::hardware::Return<void> phraseRecognitionCallback_2_1(
+ const PhraseRecognitionEvent& event, int32_t cookie);
+ virtual ::android::hardware::Return<void> soundModelCallback_2_1(
+ const ModelEvent& event, CallbackCookie cookie);
private:
class SoundModel : public RefBase {
public:
@@ -124,25 +140,48 @@
void convertTriggerPhraseToHal(
ISoundTriggerHw::Phrase *halTriggerPhrase,
const struct sound_trigger_phrase *triggerPhrase);
- ISoundTriggerHw::SoundModel *convertSoundModelToHal(
+ void convertTriggerPhrasesToHal(
+ hidl_vec<ISoundTriggerHw::Phrase> *halTriggerPhrases,
+ struct sound_trigger_phrase_sound_model *keyPhraseModel);
+ void convertSoundModelToHal(ISoundTriggerHw::SoundModel *halModel,
const struct sound_trigger_sound_model *soundModel);
+ std::pair<bool, sp<IMemory>> convertSoundModelToHal(
+ V2_1_ISoundTriggerHw::SoundModel *halModel,
+ const struct sound_trigger_sound_model *soundModel)
+ __attribute__((warn_unused_result));
+ void convertPhraseSoundModelToHal(ISoundTriggerHw::PhraseSoundModel *halKeyPhraseModel,
+ const struct sound_trigger_sound_model *soundModel);
+ std::pair<bool, sp<IMemory>> convertPhraseSoundModelToHal(
+ V2_1_ISoundTriggerHw::PhraseSoundModel *halKeyPhraseModel,
+ const struct sound_trigger_sound_model *soundModel)
+ __attribute__((warn_unused_result));
void convertPhraseRecognitionExtraToHal(
PhraseRecognitionExtra *halExtra,
const struct sound_trigger_phrase_recognition_extra *extra);
- ISoundTriggerHw::RecognitionConfig *convertRecognitionConfigToHal(
+ void convertRecognitionConfigToHal(ISoundTriggerHw::RecognitionConfig *halConfig,
const struct sound_trigger_recognition_config *config);
+ std::pair<bool, sp<IMemory>> convertRecognitionConfigToHal(
+ V2_1_ISoundTriggerHw::RecognitionConfig *halConfig,
+ const struct sound_trigger_recognition_config *config)
+ __attribute__((warn_unused_result));
struct sound_trigger_model_event *convertSoundModelEventFromHal(
- const ISoundTriggerHwCallback::ModelEvent *halEvent);
+ const V2_0_ISoundTriggerHwCallback::ModelEvent *halEvent);
void convertPhraseRecognitionExtraFromHal(
struct sound_trigger_phrase_recognition_extra *extra,
const PhraseRecognitionExtra *halExtra);
+ struct sound_trigger_phrase_recognition_event* convertPhraseRecognitionEventFromHal(
+ const V2_0_ISoundTriggerHwCallback::PhraseRecognitionEvent *halPhraseEvent);
struct sound_trigger_recognition_event *convertRecognitionEventFromHal(
- const ISoundTriggerHwCallback::RecognitionEvent *halEvent);
+ const V2_0_ISoundTriggerHwCallback::RecognitionEvent *halEvent);
+ void fillRecognitionEventFromHal(
+ struct sound_trigger_recognition_event *event,
+ const V2_0_ISoundTriggerHwCallback::RecognitionEvent *halEvent);
uint32_t nextUniqueId();
sp<ISoundTriggerHw> getService();
+ sp<V2_1_ISoundTriggerHw> toService2_1(const sp<ISoundTriggerHw>& s);
sp<SoundModel> getModel(sound_model_handle_t handle);
sp<SoundModel> removeModel(sound_model_handle_t handle);