Merge "Remove unnecessary includes and forward declarations"
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/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..ef1c61f 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++) {
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 6e861a6..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;
@@ -1646,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>
@@ -1658,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,
@@ -4562,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,
/**
@@ -4634,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,
/**
@@ -5165,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;
/**
@@ -6895,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;
@@ -7226,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
@@ -7387,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;
@@ -7474,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..24c0c51 100644
--- a/camera/tests/CameraBinderTests.cpp
+++ b/camera/tests/CameraBinderTests.cpp
@@ -317,6 +317,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 +424,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 +445,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 +495,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 +522,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 +555,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 +592,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/stagefright/audioloop.cpp b/cmds/stagefright/audioloop.cpp
index 02ecd35..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>
@@ -36,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");
}
@@ -53,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);
@@ -65,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;
@@ -75,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;
@@ -86,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);
@@ -130,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;
@@ -150,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..2e8da4b 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",
@@ -34,6 +36,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 31344c3..c4b197c 100644
--- a/drm/libmediadrm/DrmHal.cpp
+++ b/drm/libmediadrm/DrmHal.cpp
@@ -37,17 +37,15 @@
#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::KeyRequestType;
+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;
@@ -58,6 +56,8 @@
namespace android {
+#define INIT_CHECK() {if (mInitCheck != OK) return mInitCheck;}
+
static inline int getCallingPid() {
return IPCThreadState::self()->getCallingPid();
}
@@ -89,6 +89,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) {
@@ -407,6 +443,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 +462,7 @@
status_t DrmHal::destroyPlugin() {
Mutex::Autolock autoLock(mLock);
- if (mInitCheck != OK) {
- return mInitCheck;
- }
+ INIT_CHECK();
closeOpenSessions();
reportMetrics();
@@ -445,10 +480,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;
@@ -491,10 +523,7 @@
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()) {
@@ -519,10 +548,7 @@
String8> const &optionalParameters, Vector<uint8_t> &request,
String8 &defaultUrl, DrmPlugin::KeyRequestType *keyRequestType) {
Mutex::Autolock autoLock(mLock);
-
- if (mInitCheck != OK) {
- return mInitCheck;
- }
+ INIT_CHECK();
DrmSessionManager::Instance()->useSession(sessionId);
@@ -574,10 +600,7 @@
status_t DrmHal::provideKeyResponse(Vector<uint8_t> const &sessionId,
Vector<uint8_t> const &response, Vector<uint8_t> &keySetId) {
Mutex::Autolock autoLock(mLock);
-
- if (mInitCheck != OK) {
- return mInitCheck;
- }
+ INIT_CHECK();
DrmSessionManager::Instance()->useSession(sessionId);
@@ -598,10 +621,7 @@
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 +629,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 +640,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 +664,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;
@@ -675,10 +686,7 @@
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;
@@ -698,10 +706,7 @@
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 +725,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 +743,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 +886,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 +910,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;
@@ -815,12 +928,9 @@
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,10 +938,7 @@
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));
@@ -847,10 +954,7 @@
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);
@@ -862,10 +966,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);
@@ -878,10 +979,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);
@@ -904,10 +1002,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);
@@ -930,10 +1025,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);
@@ -956,10 +1048,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);
@@ -984,10 +1073,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;
diff --git a/drm/libmediadrm/IDrm.cpp b/drm/libmediadrm/IDrm.cpp
index d157be7..e7417cc 100644
--- a/drm/libmediadrm/IDrm.cpp
+++ b/drm/libmediadrm/IDrm.cpp
@@ -56,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> {
@@ -351,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());
@@ -801,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);
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/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/include/aaudio/AAudio.h b/media/libaaudio/include/aaudio/AAudio.h
index 5da8114..e40a6cd 100644
--- a/media/libaaudio/include/aaudio/AAudio.h
+++ b/media/libaaudio/include/aaudio/AAudio.h
@@ -510,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
diff --git a/media/libaaudio/src/core/AudioStream.cpp b/media/libaaudio/src/core/AudioStream.cpp
index 82c0667..9a2405a 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
diff --git a/media/libaaudio/src/core/AudioStream.h b/media/libaaudio/src/core/AudioStream.h
index 42b585f..c59ee6c 100644
--- a/media/libaaudio/src/core/AudioStream.h
+++ b/media/libaaudio/src/core/AudioStream.h
@@ -515,9 +515,9 @@
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;
diff --git a/media/libaaudio/src/utility/AAudioUtilities.cpp b/media/libaaudio/src/utility/AAudioUtilities.cpp
index 2bee6e3..2a34016 100644
--- a/media/libaaudio/src/utility/AAudioUtilities.cpp
+++ b/media/libaaudio/src/utility/AAudioUtilities.cpp
@@ -341,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
}
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/libaudioclient/AudioSystem.cpp b/media/libaudioclient/AudioSystem.cpp
index 16150f6..7969ed0 100644
--- a/media/libaudioclient/AudioSystem.cpp
+++ b/media/libaudioclient/AudioSystem.cpp
@@ -858,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,
@@ -866,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);
}
diff --git a/media/libaudioclient/IAudioPolicyService.cpp b/media/libaudioclient/IAudioPolicyService.cpp
index c0e53b3..6478975 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));
@@ -968,6 +970,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));
@@ -978,7 +981,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);
diff --git a/media/libaudioclient/include/media/AudioSystem.h b/media/libaudioclient/include/media/AudioSystem.h
index d6cc37a..e452837 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,
diff --git a/media/libaudioclient/include/media/IAudioPolicyService.h b/media/libaudioclient/include/media/IAudioPolicyService.h
index 7e9413d..5338927 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,
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/DeviceHalHidl.cpp b/media/libaudiohal/2.0/DeviceHalHidl.cpp
similarity index 99%
rename from media/libaudiohal/DeviceHalHidl.cpp
rename to media/libaudiohal/2.0/DeviceHalHidl.cpp
index 49ef991..0d9c6c4 100644
--- a/media/libaudiohal/DeviceHalHidl.cpp
+++ b/media/libaudiohal/2.0/DeviceHalHidl.cpp
@@ -37,6 +37,7 @@
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;
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/DevicesFactoryHalHybrid.cpp b/media/libaudiohal/2.0/DevicesFactoryHalHybrid.cpp
similarity index 91%
rename from media/libaudiohal/DevicesFactoryHalHybrid.cpp
rename to media/libaudiohal/2.0/DevicesFactoryHalHybrid.cpp
index 8dc1434..77df6b5 100644
--- a/media/libaudiohal/DevicesFactoryHalHybrid.cpp
+++ b/media/libaudiohal/2.0/DevicesFactoryHalHybrid.cpp
@@ -23,11 +23,6 @@
namespace android {
-// static
-sp<DevicesFactoryHalInterface> DevicesFactoryHalInterface::create() {
- return new DevicesFactoryHalHybrid();
-}
-
DevicesFactoryHalHybrid::DevicesFactoryHalHybrid()
: mLocalFactory(new DevicesFactoryHalLocal()),
mHidlFactory(new DevicesFactoryHalHidl()) {
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/EffectBufferHalHidl.cpp b/media/libaudiohal/2.0/EffectBufferHalHidl.cpp
similarity index 97%
rename from media/libaudiohal/EffectBufferHalHidl.cpp
rename to media/libaudiohal/2.0/EffectBufferHalHidl.cpp
index 8b5201b..226a500 100644
--- a/media/libaudiohal/EffectBufferHalHidl.cpp
+++ b/media/libaudiohal/2.0/EffectBufferHalHidl.cpp
@@ -37,14 +37,12 @@
return counter++;
}
-// static
-status_t EffectBufferHalInterface::allocate(
+status_t EffectBufferHalHidl::allocate(
size_t size, sp<EffectBufferHalInterface>* buffer) {
return mirror(nullptr, size, buffer);
}
-// static
-status_t EffectBufferHalInterface::mirror(
+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();
diff --git a/media/libaudiohal/EffectBufferHalHidl.h b/media/libaudiohal/2.0/EffectBufferHalHidl.h
similarity index 92%
rename from media/libaudiohal/EffectBufferHalHidl.h
rename to media/libaudiohal/2.0/EffectBufferHalHidl.h
index d7a43ae..31e0087 100644
--- a/media/libaudiohal/EffectBufferHalHidl.h
+++ b/media/libaudiohal/2.0/EffectBufferHalHidl.h
@@ -32,6 +32,9 @@
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;
diff --git a/media/libaudiohal/EffectHalHidl.cpp b/media/libaudiohal/2.0/EffectHalHidl.cpp
similarity index 99%
rename from media/libaudiohal/EffectHalHidl.cpp
rename to media/libaudiohal/2.0/EffectHalHidl.cpp
index f4d1958..4fb032c 100644
--- a/media/libaudiohal/EffectHalHidl.cpp
+++ b/media/libaudiohal/2.0/EffectHalHidl.cpp
@@ -31,6 +31,7 @@
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;
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/EffectsFactoryHalHidl.cpp b/media/libaudiohal/2.0/EffectsFactoryHalHidl.cpp
similarity index 91%
rename from media/libaudiohal/EffectsFactoryHalHidl.cpp
rename to media/libaudiohal/2.0/EffectsFactoryHalHidl.cpp
index a8081b7..0d40e6d 100644
--- a/media/libaudiohal/EffectsFactoryHalHidl.cpp
+++ b/media/libaudiohal/2.0/EffectsFactoryHalHidl.cpp
@@ -20,10 +20,12 @@
#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;
@@ -31,16 +33,6 @@
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) {
@@ -145,4 +137,14 @@
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/EffectsFactoryHalHidl.h b/media/libaudiohal/2.0/EffectsFactoryHalHidl.h
similarity index 90%
rename from media/libaudiohal/EffectsFactoryHalHidl.h
rename to media/libaudiohal/2.0/EffectsFactoryHalHidl.h
index e89f042..82b5481 100644
--- a/media/libaudiohal/EffectsFactoryHalHidl.h
+++ b/media/libaudiohal/2.0/EffectsFactoryHalHidl.h
@@ -21,6 +21,8 @@
#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;
@@ -49,6 +51,10 @@
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;
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/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/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/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/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/DrmHal.h b/media/libmedia/include/media/DrmHal.h
index 55fbce9..1a0553e 100644
--- a/media/libmedia/include/media/DrmHal.h
+++ b/media/libmedia/include/media/DrmHal.h
@@ -19,6 +19,7 @@
#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>
@@ -27,11 +28,12 @@
#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;
@@ -99,6 +101,15 @@
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;
@@ -167,6 +178,7 @@
const Vector<sp<IDrmFactory>> mFactories;
sp<IDrmPlugin> mPlugin;
+ sp<drm::V1_1::IDrmPlugin> mPluginV1_1;
Vector<Vector<uint8_t>> mOpenSessions;
void closeOpenSessions();
diff --git a/media/libmedia/include/media/IDrm.h b/media/libmedia/include/media/IDrm.h
index ce0360b..9266f99 100644
--- a/media/libmedia/include/media/IDrm.h
+++ b/media/libmedia/include/media/IDrm.h
@@ -79,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;
diff --git a/media/libmedia/nuplayer2/GenericSource.cpp b/media/libmedia/nuplayer2/GenericSource.cpp
index 011691a..c0b81fb 100644
--- a/media/libmedia/nuplayer2/GenericSource.cpp
+++ b/media/libmedia/nuplayer2/GenericSource.cpp
@@ -33,7 +33,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>
diff --git a/media/libmedia/nuplayer2/NuPlayer2Decoder.cpp b/media/libmedia/nuplayer2/NuPlayer2Decoder.cpp
index 25d41f3..715d6fc 100644
--- a/media/libmedia/nuplayer2/NuPlayer2Decoder.cpp
+++ b/media/libmedia/nuplayer2/NuPlayer2Decoder.cpp
@@ -40,6 +40,7 @@
#include <media/stagefright/MediaErrors.h>
#include <media/stagefright/SurfaceUtils.h>
+#include <system/window.h>
#include "ATSParser.h"
namespace android {
@@ -241,22 +242,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 +330,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 +545,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);
diff --git a/media/libmediaextractor/Android.bp b/media/libmediaextractor/Android.bp
index dcdb320..18573db 100644
--- a/media/libmediaextractor/Android.bp
+++ b/media/libmediaextractor/Android.bp
@@ -1,4 +1,4 @@
-cc_library_shared {
+cc_library {
name: "libmediaextractor",
include_dirs: [
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/libstagefright/Android.bp b/media/libstagefright/Android.bp
index c3d9c24..01f73a1 100644
--- a/media/libstagefright/Android.bp
+++ b/media/libstagefright/Android.bp
@@ -128,7 +128,6 @@
"libstagefright_timedtext",
"libvpx",
"libwebm",
- "libstagefright_mpeg2support",
"libstagefright_esds",
"libstagefright_id3",
"libFLAC",
@@ -173,7 +172,7 @@
},
}
-cc_library_shared {
+cc_library {
name: "libstagefright_player2",
srcs: [
@@ -190,7 +189,6 @@
"NuCachedSource2.cpp",
"RemoteMediaExtractor.cpp",
"RemoteMediaSource.cpp",
- "SurfaceUtils.cpp",
"Utils.cpp",
"VideoFrameScheduler.cpp",
"http/MediaHTTP.cpp",
@@ -202,7 +200,6 @@
"libdrmframework",
"libgui",
"liblog",
- "libmedia_omx",
"libmedia_player2_util",
"libaudioclient",
"libmediaextractor",
@@ -213,15 +210,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/MediaCodecSource.cpp b/media/libstagefright/MediaCodecSource.cpp
index cf800b2..8bd0a51 100644
--- a/media/libstagefright/MediaCodecSource.cpp
+++ b/media/libstagefright/MediaCodecSource.cpp
@@ -457,13 +457,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 +480,7 @@
}
status_t MediaCodecSource::initEncoder() {
+
mReflector = new AHandlerReflector<MediaCodecSource>(this);
mLooper->registerHandler(mReflector);
@@ -500,23 +494,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 +509,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) {
diff --git a/media/libstagefright/MediaExtractorFactory.cpp b/media/libstagefright/MediaExtractorFactory.cpp
index 644b17d..472c137 100644
--- a/media/libstagefright/MediaExtractorFactory.cpp
+++ b/media/libstagefright/MediaExtractorFactory.cpp
@@ -37,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) {
@@ -245,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;
@@ -253,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);
@@ -263,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 =
@@ -289,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);
@@ -301,10 +334,20 @@
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;
diff --git a/media/libstagefright/codec2/vndk/C2Store.cpp b/media/libstagefright/codec2/vndk/C2Store.cpp
index daa9d3f..555e77b 100644
--- a/media/libstagefright/codec2/vndk/C2Store.cpp
+++ b/media/libstagefright/codec2/vndk/C2Store.cpp
@@ -405,6 +405,7 @@
// 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.aac.decoder", "libstagefright_soft_c2aacdec.so");
+ mComponents.emplace("c2.google.aac.encoder", "libstagefright_soft_c2aacenc.so");
}
c2_status_t C2PlatformComponentStore::copyBuffer(
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..94308c4
--- /dev/null
+++ b/media/libstagefright/codecs/aacenc/C2SoftAacEnc.cpp
@@ -0,0 +1,416 @@
+/*
+ * 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->worklets_processed = 0u;
+
+ if (mSignalledError) {
+ return;
+ }
+ bool eos = (work->input.flags & C2BufferPack::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.infos.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;
+
+ 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 =
+ (C2BufferPack::flags_t)(eos ? C2BufferPack::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->worklets_processed = 1u;
+ if (nOutputBytes) {
+ work->worklets.front()->output.buffers.push_back(
+ createLinearBuffer(block, 0, nOutputBytes));
+ } else {
+ work->worklets.front()->output.buffers.emplace_back(nullptr);
+ }
+
+#if 0
+ ALOGI("sending %d bytes of data (time = %lld us, flags = 0x%08lx)",
+ nOutputBytes, mInputTimeUs, 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..947c960
--- /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;
+ int64_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/foundation/Android.bp b/media/libstagefright/foundation/Android.bp
index df3e280..4dfe5e5 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: {
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/media/stagefright/MediaExtractorFactory.h b/media/libstagefright/include/media/stagefright/MediaExtractorFactory.h
index 55654f1..e5c67e1 100644
--- a/media/libstagefright/include/media/stagefright/MediaExtractorFactory.h
+++ b/media/libstagefright/include/media/stagefright/MediaExtractorFactory.h
@@ -48,8 +48,10 @@
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/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 bbd2afa..61d73a0 100644
--- a/packages/MediaComponents/Android.mk
+++ b/packages/MediaComponents/Android.mk
@@ -34,22 +34,19 @@
LOCAL_JAVA_LIBRARIES += android-support-annotations
-# 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
-
# 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 += \
@@ -59,3 +56,5 @@
LOCAL_USE_AAPT2 := true
include $(BUILD_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
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..1a27056
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
@@ -0,0 +1,461 @@
+/*
+ * 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.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.media.IMediaSession2;
+import android.media.IMediaSession2Callback;
+import android.media.MediaSession2;
+import android.media.MediaSession2.Command;
+import android.media.MediaSession2.CommandGroup;
+import android.media.MediaController2;
+import android.media.MediaController2.ControllerCallback;
+import android.media.MediaPlayerBase;
+import android.media.MediaSessionService2;
+import android.media.SessionToken;
+import android.media.session.PlaybackState;
+import android.media.update.MediaController2Provider;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+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 SessionToken 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(MediaController2 instance, Context context, SessionToken token,
+ ControllerCallback callback, Executor executor) {
+ 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.release();
+ };
+
+ 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 release_impl() {
+ 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();
+ });
+ }
+
+ @Override
+ public SessionToken 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());
+ }
+ }
+
+ @Override
+ public PlaybackState getPlaybackState_impl() {
+ final IMediaSession2 binder = mSessionBinder;
+ if (binder != null) {
+ try {
+ return binder.getPlaybackState();
+ } 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): What to return for error case?
+ return null;
+ }
+
+ @Override
+ public void addPlaybackListener_impl(
+ MediaPlayerBase.PlaybackListener listener, Handler handler) {
+ if (listener == null) {
+ throw new IllegalArgumentException("listener shouldn't be null");
+ }
+ if (handler == null) {
+ throw new IllegalArgumentException("handler shouldn't be null");
+ }
+ boolean registerCallback;
+ synchronized (mLock) {
+ if (PlaybackListenerHolder.contains(mPlaybackListeners, listener)) {
+ throw new IllegalArgumentException("listener is already added. Ignoring.");
+ }
+ registerCallback = mPlaybackListeners.isEmpty();
+ mPlaybackListeners.add(new PlaybackListenerHolder(listener, handler));
+ }
+ if (registerCallback) {
+ registerCallbackForPlaybackNotLocked();
+ }
+ }
+
+ @Override
+ public void removePlaybackListener_impl(MediaPlayerBase.PlaybackListener listener) {
+ if (listener == null) {
+ throw new IllegalArgumentException("listener shouldn't be null");
+ }
+ boolean unregisterCallback;
+ synchronized (mLock) {
+ int idx = PlaybackListenerHolder.indexOf(mPlaybackListeners, listener);
+ if (idx >= 0) {
+ mPlaybackListeners.get(idx).removeCallbacksAndMessages(null);
+ mPlaybackListeners.remove(idx);
+ }
+ unregisterCallback = mPlaybackListeners.isEmpty();
+ }
+ if (unregisterCallback) {
+ final IMediaSession2 binder = mSessionBinder;
+ if (binder != null) {
+ // Lazy unregister
+ try {
+ binder.unregisterCallback(mSessionCallbackStub, CALLBACK_FLAG_PLAYBACK);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Cannot connect to the service or the session is gone", e);
+ }
+ }
+ }
+ }
+
+ ///////////////////////////////////////////////////
+ // 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 PlaybackState 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);
+ }
+ 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) {
+ 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.release();
+ }
+ }
+ }
+
+ private 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;
+ }
+
+ public void destroy() {
+ mController.clear();
+ }
+
+ @Override
+ public void onPlaybackStateChanged(PlaybackState state) throws RuntimeException {
+ final MediaController2Impl controller = getController();
+ controller.pushPlaybackStateChanges(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));
+ }
+ }
+
+ // 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.release();
+ }
+ }
+}
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..09d7adc
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
@@ -0,0 +1,424 @@
+/*
+ * 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.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.media.IMediaSession2Callback;
+import android.media.MediaController2;
+import android.media.MediaPlayerBase;
+import android.media.MediaSession2;
+import android.media.MediaSession2.Builder;
+import android.media.MediaSession2.ControllerInfo;
+import android.media.MediaSession2.SessionCallback;
+import android.media.SessionToken;
+import android.media.session.MediaSessionManager;
+import android.media.session.PlaybackState;
+import android.media.update.MediaSession2Provider;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.util.Log;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+
+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 SessionCallback mCallback;
+ private final MediaSession2Stub mSessionStub;
+ private final SessionToken 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
+ */
+ public MediaSession2Impl(MediaSession2 instance, Context context, MediaPlayerBase player,
+ String id, SessionCallback callback) {
+ mInstance = instance;
+
+ // Argument checks are done by builder already.
+ // Initialize finals first.
+ mContext = context;
+ mId = id;
+ mHandler = new Handler(Looper.myLooper());
+ mCallback = callback;
+ mSessionStub = new MediaSession2Stub(this);
+ // 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) throws IllegalArgumentException {
+ ensureCallingThread();
+ // TODO(jaewan): Remove this when we don't inherits MediaPlayerBase.
+ if (player instanceof MediaSession2 || player instanceof MediaController2) {
+ throw new IllegalArgumentException("player doesn't accept MediaSession2 nor"
+ + " MediaController2");
+ }
+ if (player != null && mPlayer == player) {
+ // Player didn't changed. No-op.
+ return;
+ }
+ setPlayerInternal(player);
+ }
+
+ private void setPlayerInternal(MediaPlayerBase player) {
+ mHandler.removeCallbacksAndMessages(null);
+ if (mPlayer == null && player != null) {
+ if (DEBUG) {
+ Log.d(TAG, "session is ready to use, id=" + mId);
+ }
+ } else if (mPlayer != null && player == null) {
+ if (DEBUG) {
+ Log.d(TAG, "session is now unavailable, id=" + mId);
+ }
+ if (mSessionStub != null) {
+ // Invalidate previously published session stub.
+ mSessionStub.destroyNotLocked();
+ }
+ }
+ if (mPlayer != null && mListener != null) {
+ // This might not work for a poorly implemented player.
+ mPlayer.removePlaybackListener(mListener);
+ }
+ if (player != null) {
+ mListener = new MyPlaybackListener(this, player);
+ player.addPlaybackListener(mListener, mHandler);
+ notifyPlaybackStateChanged(player.getPlaybackState());
+ }
+ mPlayer = player;
+ }
+
+ @Override
+ public MediaPlayerBase getPlayer_impl() {
+ return getPlayer();
+ }
+
+ // TODO(jaewan): Change this to @NonNull
+ @Override
+ public SessionToken getToken_impl() {
+ return mSessionToken;
+ }
+
+ @Override
+ public List<ControllerInfo> getConnectedControllers_impl() {
+ return mSessionStub.getControllers();
+ }
+
+ @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 PlaybackState getPlaybackState_impl() {
+ ensureCallingThread();
+ ensurePlayer();
+ return mPlayer.getPlaybackState();
+ }
+
+ @Override
+ public void addPlaybackListener_impl(
+ MediaPlayerBase.PlaybackListener listener, Handler handler) {
+ if (listener == null) {
+ throw new IllegalArgumentException("listener shouldn't be null");
+ }
+ if (handler == null) {
+ throw new IllegalArgumentException("handler shouldn't be null");
+ }
+ ensureCallingThread();
+ if (PlaybackListenerHolder.contains(mListeners, listener)) {
+ Log.w(TAG, "listener is already added. Ignoring.");
+ return;
+ }
+ mListeners.add(new PlaybackListenerHolder(listener, handler));
+ }
+
+ @Override
+ public void removePlaybackListener_impl(MediaPlayerBase.PlaybackListener listener) {
+ if (listener == null) {
+ throw new IllegalArgumentException("listener shouldn't be null");
+ }
+ ensureCallingThread();
+ int idx = PlaybackListenerHolder.indexOf(mListeners, listener);
+ if (idx >= 0) {
+ mListeners.get(idx).removeCallbacksAndMessages(null);
+ mListeners.remove(idx);
+ }
+ }
+
+ ///////////////////////////////////////////////////
+ // 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()}.
+ // TODO(jaewan): Should we pend command instead of exception?
+ private void ensureCallingThread() {
+ 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(PlaybackState 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;
+ }
+
+ SessionCallback getCallback() {
+ return mCallback;
+ }
+
+ 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(PlaybackState state) {
+ MediaSession2Impl session = mSession.get();
+ if (session == null || session.getHandler().getLooper() != Looper.myLooper()
+ || 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(ControllerInfo instance, Context context, 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;
+ }
+ }
+}
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..bae02e5
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
@@ -0,0 +1,309 @@
+/*
+ * 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.MediaSession2;
+import android.media.MediaSession2.Command;
+import android.media.MediaSession2.CommandGroup;
+import android.media.MediaSession2.ControllerInfo;
+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;
+
+// TODO(jaewan): Add a hook for media apps to log which app requested specific command.
+// TODO(jaewan): Add a way to block specific command from a specific app. Also add supported
+// command per apps.
+public class MediaSession2Stub extends IMediaSession2.Stub {
+ private static final String TAG = "MediaSession2Stub";
+ private static final boolean DEBUG = true; // TODO(jaewan): Rename.
+
+ private final Object mLock = new Object();
+ private final CommandHandler mCommandHandler;
+ private final WeakReference<MediaSession2Impl> mSession;
+ private final Context mContext;
+
+ @GuardedBy("mLock")
+ private final ArrayMap<IBinder, ControllerInfo> mControllers = new ArrayMap<>();
+
+ public MediaSession2Stub(MediaSession2Impl session) {
+ mSession = new WeakReference<>(session);
+ mContext = session.getContext();
+ mCommandHandler = new CommandHandler(session.getHandler().getLooper());
+ }
+
+ 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);
+ }
+
+ @Deprecated
+ @Override
+ public PlaybackState getPlaybackState() throws RemoteException {
+ MediaSession2Impl session = getSession();
+ // TODO(jaewan): Check if mPlayer.getPlaybackState() is safe here.
+ return session.getInstance().getPlayer().getPlaybackState();
+ }
+
+ @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) controllerInfo.getProvider()).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 impl =
+ ((ControllerInfoImpl) controllerInfo.getProvider());
+ impl.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) controllerInfo.getProvider()).containsFlag(flag)) {
+ controllers.add(controllerInfo);
+ }
+ }
+ }
+ return controllers;
+ }
+
+ // Should be used without a lock to prevent potential deadlock.
+ public void notifyPlaybackStateChangedNotLocked(PlaybackState state) {
+ final List<ControllerInfo> list = getControllersWithFlag(CALLBACK_FLAG_PLAYBACK);
+ for (int i = 0; i < list.size(); i++) {
+ IMediaSession2Callback callbackBinder =
+ ((ControllerInfoImpl) list.get(i).getProvider())
+ .getControllerBinder();
+ try {
+ callbackBinder.onPlaybackStateChanged(state);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Controller is gone", e);
+ // TODO(jaewan): What to do when the controller is gone?
+ }
+ }
+ }
+
+ private class CommandHandler extends Handler {
+ public static final int MSG_CONNECT = 1000;
+ public static final int MSG_COMMAND = 1001;
+
+ 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 = session.getCallback().onConnect(request);
+ // Don't reject connection for the request from trusted app.
+ // Otherwise server will fail to retrieve session's information to dispatch
+ // media keys to.
+ boolean accept = allowedCommands != null || request.isTrusted();
+ ControllerInfoImpl impl = (ControllerInfoImpl) request.getProvider();
+ if (accept) {
+ synchronized (mLock) {
+ mControllers.put(impl.getId(), request);
+ }
+ }
+ 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 = session.getCallback().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;
+ }
+ }
+
+ 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();
+ }
+ }
+
+ 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..6a4760d
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/media/MediaSessionService2Impl.java
@@ -0,0 +1,175 @@
+/*
+ * 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 static android.media.MediaSessionService2.DEFAULT_MEDIA_NOTIFICATION_CHANNEL_ID;
+import static android.media.MediaSessionService2.DEFAULT_MEDIA_NOTIFICATION_ID;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+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.session.PlaybackState;
+import android.media.update.MediaSessionService2Provider;
+import android.os.IBinder;
+import android.os.Looper;
+import android.support.annotation.GuardedBy;
+import android.util.Log;
+
+// 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 NotificationChannel mDefaultNotificationChannel;
+ private MediaSession2 mSession;
+
+ public MediaSessionService2Impl(MediaSessionService2 instance) {
+ if (DEBUG) {
+ Log.d(TAG, "MediaSessionService2Impl(" + instance + ")");
+ }
+ mInstance = instance;
+ }
+
+ @Override
+ public MediaSession2 getSession_impl() {
+ synchronized (mLock) {
+ return mSession;
+ }
+ }
+
+ @Override
+ public MediaNotification onUpdateNotification_impl(PlaybackState state) {
+ return createDefaultNotification(state);
+ }
+
+ // TODO(jaewan): Remove this for framework release.
+ private MediaNotification createDefaultNotification(PlaybackState state) {
+ // TODO(jaewan): Place better notification here.
+ if (mDefaultNotificationChannel == null) {
+ mDefaultNotificationChannel = new NotificationChannel(
+ DEFAULT_MEDIA_NOTIFICATION_CHANNEL_ID,
+ DEFAULT_MEDIA_NOTIFICATION_CHANNEL_ID,
+ NotificationManager.IMPORTANCE_DEFAULT);
+ mNotificationManager.createNotificationChannel(mDefaultNotificationChannel);
+ }
+ Notification notification = new Notification.Builder(
+ mInstance, DEFAULT_MEDIA_NOTIFICATION_CHANNEL_ID)
+ .setContentTitle(mInstance.getPackageName())
+ .setContentText("Playback state: " + state.getState())
+ .setSmallIcon(android.R.drawable.sym_def_app_icon).build();
+ return MediaNotification.create(DEFAULT_MEDIA_NOTIFICATION_ID, notification);
+ }
+
+
+ @Override
+ public void onCreate_impl() {
+ mNotificationManager = (NotificationManager) mInstance.getSystemService(
+ NOTIFICATION_SERVICE);
+ mStartSelfIntent = new Intent(mInstance, mInstance.getClass());
+
+ Intent serviceIntent = new Intent(mInstance, mInstance.getClass());
+ serviceIntent.setAction(MediaSessionService2.SERVICE_INTERFACE);
+ 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"
+ + MediaSessionService2.SERVICE_INTERFACE);
+ } 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);
+ }
+ }
+
+ public IBinder onBind_impl(Intent intent) {
+ if (MediaSessionService2.SERVICE_INTERFACE.equals(intent.getAction())) {
+ return mSession.getToken().getSessionBinder().asBinder();
+ }
+ return null;
+ }
+
+ private void updateNotification(PlaybackState state) {
+ MediaNotification mediaNotification = mInstance.onUpdateNotification(state);
+ if (mediaNotification == null) {
+ mediaNotification = createDefaultNotification(state);
+ }
+ 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(PlaybackState 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..4d06463
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/media/PlaybackListenerHolder.java
@@ -0,0 +1,83 @@
+/*
+ * 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;
+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;
+
+/**
+ * Holds {@link android.media.MediaPlayerBase.PlaybackListener} with the {@link Handler}.
+ */
+public class PlaybackListenerHolder extends Handler {
+ private static final int ON_PLAYBACK_CHANGED = 1;
+
+ public final MediaPlayerBase.PlaybackListener listener;
+
+ public PlaybackListenerHolder(
+ @NonNull MediaPlayerBase.PlaybackListener listener, @NonNull Handler handler) {
+ super(handler.getLooper());
+ this.listener = listener;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case ON_PLAYBACK_CHANGED:
+ listener.onPlaybackChanged((PlaybackState) msg.obj);
+ break;
+ }
+ }
+
+ public void postPlaybackChange(PlaybackState state) {
+ obtainMessage(ON_PLAYBACK_CHANGED, state).sendToTarget();
+ }
+
+ /**
+ * 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 633a342..cfef77f 100644
--- a/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
+++ b/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
@@ -16,9 +16,21 @@
package com.android.media.update;
+import android.content.Context;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
+import android.media.MediaController2;
+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.SessionToken;
import android.media.update.MediaControlView2Provider;
+import android.media.update.MediaController2Provider;
+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;
@@ -27,9 +39,14 @@
import android.widget.MediaControlView2;
import android.widget.VideoView2;
+import com.android.media.MediaController2Impl;
+import com.android.media.MediaSession2Impl;
+import com.android.media.MediaSessionService2Impl;
import com.android.widget.MediaControlView2Impl;
import com.android.widget.VideoView2Impl;
+import java.util.concurrent.Executor;
+
public class ApiFactory implements StaticProvider {
public static Object initialize(Resources libResources, Theme libTheme)
throws ReflectiveOperationException {
@@ -38,6 +55,33 @@
}
@Override
+ public MediaController2Provider createMediaController2(
+ MediaController2 instance, Context context, SessionToken token,
+ MediaController2.ControllerCallback callback, Executor executor) {
+ return new MediaController2Impl(instance, context, token, callback, executor);
+ }
+
+ @Override
+ public MediaSession2Provider createMediaSession2(MediaSession2 instance, Context context,
+ MediaPlayerBase player, String id, SessionCallback callback) {
+ return new MediaSession2Impl(instance, context, player, id, callback);
+ }
+
+ @Override
+ public MediaSession2Provider.ControllerInfoProvider createMediaSession2ControllerInfoProvider(
+ ControllerInfo instance, Context context, int uid, int pid, String packageName,
+ IMediaSession2Callback callback) {
+ return new MediaSession2Impl.ControllerInfoImpl(
+ instance, context, uid, pid, packageName, callback);
+ }
+
+ @Override
+ public MediaSessionService2Provider createMediaSessionService2(
+ MediaSessionService2 instance) {
+ return new MediaSessionService2Impl(instance);
+ }
+
+ @Override
public MediaControlView2Provider createMediaControlView2(
MediaControlView2 instance, ViewProvider superProvider) {
return new MediaControlView2Impl(instance, superProvider);
diff --git a/packages/MediaComponents/src/com/android/media/update/ApiHelper.java b/packages/MediaComponents/src/com/android/media/update/ApiHelper.java
index 26f858c..a83e449 100644
--- a/packages/MediaComponents/src/com/android/media/update/ApiHelper.java
+++ b/packages/MediaComponents/src/com/android/media/update/ApiHelper.java
@@ -16,8 +16,13 @@
package com.android.media.update;
+import android.content.Context;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
+import android.content.res.XmlResourceParser;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.view.View;
public class ApiHelper {
private static ApiHelper sInstance;
@@ -46,4 +51,17 @@
public static Resources.Theme getLibTheme() {
return sInstance.mLibTheme;
}
+
+ public static LayoutInflater getLayoutInflater(Context context) {
+ LayoutInflater layoutInflater = LayoutInflater.from(context).cloneInContext(
+ new ContextThemeWrapper(context, getLibTheme()));
+ // TODO: call layoutInflater.setFactory2()
+ 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/widget/MediaControlView2Impl.java b/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
index 317a45f..a814d5e 100644
--- a/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
+++ b/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
@@ -231,6 +231,15 @@
}
@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();
}
@@ -339,11 +348,8 @@
* @hide This doesn't work as advertised
*/
protected View makeControllerView() {
- View root = LayoutInflater.from(mInstance.getContext()).inflate(
- R.layout.media_controller, null);
-
+ View root = ApiHelper.inflateLibLayout(mInstance.getContext(), R.layout.media_controller);
initControllerView(root);
-
return root;
}
diff --git a/packages/MediaComponents/src/com/android/widget/VideoSurfaceView.java b/packages/MediaComponents/src/com/android/widget/VideoSurfaceView.java
index 800be8e..8577123 100644
--- a/packages/MediaComponents/src/com/android/widget/VideoSurfaceView.java
+++ b/packages/MediaComponents/src/com/android/widget/VideoSurfaceView.java
@@ -162,57 +162,22 @@
int height = getDefaultSize(videoHeight, heightMeasureSpec);
if (videoWidth > 0 && videoHeight > 0) {
- int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
- int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
- if (widthSpecMode == MeasureSpec.EXACTLY && heightSpecMode == MeasureSpec.EXACTLY) {
- // the size is fixed
- width = widthSpecSize;
- height = heightSpecSize;
+ width = widthSpecSize;
+ height = heightSpecSize;
- // for compatibility, we adjust size based on aspect ratio
- if (videoWidth * height < width * videoHeight) {
- if (DEBUG) {
- Log.d(TAG, "image too wide, correcting");
- }
- width = height * videoWidth / videoHeight;
- } else if (videoWidth * height > width * videoHeight) {
- if (DEBUG) {
- Log.d(TAG, "image too tall, correcting");
- }
- height = width * videoHeight / videoWidth;
- }
- } else if (widthSpecMode == MeasureSpec.EXACTLY) {
- // only the width is fixed, adjust the height to match aspect ratio if possible
- width = widthSpecSize;
- height = width * videoHeight / videoWidth;
- if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {
- // couldn't match aspect ratio within the constraints
- height = heightSpecSize;
- }
- } else if (heightSpecMode == MeasureSpec.EXACTLY) {
- // only the height is fixed, adjust the width to match aspect ratio if possible
- height = heightSpecSize;
+ // for compatibility, we adjust size based on aspect ratio
+ if (videoWidth * height < width * videoHeight) {
width = height * videoWidth / videoHeight;
- if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {
- // couldn't match aspect ratio within the constraints
- width = widthSpecSize;
+ if (DEBUG) {
+ Log.d(TAG, "image too wide, correcting. width: " + width);
}
- } else {
- // neither the width nor the height are fixed, try to use actual video size
- width = videoWidth;
- height = videoHeight;
- if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {
- // too tall, decrease both width and height
- height = heightSpecSize;
- width = height * videoWidth / videoHeight;
- }
- if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {
- // too wide, decrease both width and height
- width = widthSpecSize;
- height = width * videoHeight / videoWidth;
+ } else if (videoWidth * height > width * videoHeight) {
+ height = width * videoHeight / videoWidth;
+ if (DEBUG) {
+ Log.d(TAG, "image too tall, correcting. height: " + height);
}
}
} else {
diff --git a/packages/MediaComponents/src/com/android/widget/VideoTextureView.java b/packages/MediaComponents/src/com/android/widget/VideoTextureView.java
index f240301..69a4ebe 100644
--- a/packages/MediaComponents/src/com/android/widget/VideoTextureView.java
+++ b/packages/MediaComponents/src/com/android/widget/VideoTextureView.java
@@ -173,57 +173,22 @@
int height = getDefaultSize(videoHeight, heightMeasureSpec);
if (videoWidth > 0 && videoHeight > 0) {
- int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
- int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
- if (widthSpecMode == MeasureSpec.EXACTLY && heightSpecMode == MeasureSpec.EXACTLY) {
- // the size is fixed
- width = widthSpecSize;
- height = heightSpecSize;
+ width = widthSpecSize;
+ height = heightSpecSize;
- // for compatibility, we adjust size based on aspect ratio
- if (videoWidth * height < width * videoHeight) {
- if (DEBUG) {
- Log.d(TAG, "image too wide, correcting");
- }
- width = height * videoWidth / videoHeight;
- } else if (videoWidth * height > width * videoHeight) {
- if (DEBUG) {
- Log.d(TAG, "image too tall, correcting");
- }
- height = width * videoHeight / videoWidth;
- }
- } else if (widthSpecMode == MeasureSpec.EXACTLY) {
- // only the width is fixed, adjust the height to match aspect ratio if possible
- width = widthSpecSize;
- height = width * videoHeight / videoWidth;
- if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {
- // couldn't match aspect ratio within the constraints
- height = heightSpecSize;
- }
- } else if (heightSpecMode == MeasureSpec.EXACTLY) {
- // only the height is fixed, adjust the width to match aspect ratio if possible
- height = heightSpecSize;
+ // for compatibility, we adjust size based on aspect ratio
+ if (videoWidth * height < width * videoHeight) {
width = height * videoWidth / videoHeight;
- if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {
- // couldn't match aspect ratio within the constraints
- width = widthSpecSize;
+ if (DEBUG) {
+ Log.d(TAG, "image too wide, correcting. width: " + width);
}
- } else {
- // neither the width nor the height are fixed, try to use actual video size
- width = videoWidth;
- height = videoHeight;
- if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {
- // too tall, decrease both width and height
- height = heightSpecSize;
- width = height * videoWidth / videoHeight;
- }
- if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {
- // too wide, decrease both width and height
- width = widthSpecSize;
- height = width * videoHeight / videoWidth;
+ } else if (videoWidth * height > width * videoHeight) {
+ height = width * videoHeight / videoWidth;
+ if (DEBUG) {
+ Log.d(TAG, "image too tall, correcting. height: " + height);
}
}
} else {
diff --git a/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java b/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
index 19a41de..57534f1 100644
--- a/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
+++ b/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
@@ -98,8 +98,6 @@
private int mCurrentBufferPercentage;
private int mSeekWhenPrepared; // recording the seek position while preparing
- private int mSurfaceWidth;
- private int mSurfaceHeight;
private int mVideoWidth;
private int mVideoHeight;
@@ -120,8 +118,6 @@
mVideoWidth = 0;
mVideoHeight = 0;
- mSurfaceWidth = 0;
- mSurfaceHeight = 0;
mSpeed = 1.0f;
mFallbackSpeed = mSpeed;
@@ -137,7 +133,8 @@
mTextureView = new VideoTextureView(mInstance.getContext());
mSurfaceView = new VideoSurfaceView(mInstance.getContext());
LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT,
- LayoutParams.WRAP_CONTENT);
+ LayoutParams.MATCH_PARENT);
+ params.gravity = Gravity.CENTER;
mTextureView.setLayoutParams(params);
mSurfaceView.setLayoutParams(params);
mTextureView.setSurfaceListener(this);
@@ -158,16 +155,12 @@
mSubtitleView.setBackgroundColor(0);
mInstance.addView(mSubtitleView);
- // Create MediaSession
- mMediaSession = new MediaSession(mInstance.getContext(), "VideoView2MediaSession");
- mMediaSession.setCallback(new MediaSessionCallback());
-
// 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.api_provider",
+ "http://schemas.android.com/apk/com.android.media.update",
"enableControlView", true);
if (enableControlView) {
- setMediaControlView2_impl(new MediaControlView2(mInstance.getContext()));
+ mMediaControlView = new MediaControlView2(mInstance.getContext());
}
}
@@ -175,15 +168,9 @@
public void setMediaControlView2_impl(MediaControlView2 mediaControlView) {
mMediaControlView = mediaControlView;
- // TODO: change this so that the CC button appears only where there is a subtitle track.
- mMediaControlView.showCCButton();
-
- // Get MediaController from MediaSession and set it inside MediaControlView2
- mMediaControlView.setController(mMediaSession.getController());
-
- LayoutParams params =
- new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
- mInstance.addView(mMediaControlView, params);
+ if (mInstance.isAttachedToWindow()) {
+ attachMediaControlView();
+ }
}
@Override
@@ -415,6 +402,25 @@
}
@Override
+ public void onAttachedToWindow_impl() {
+ mSuperProvider.onAttachedToWindow_impl();
+
+ // Create MediaSession
+ mMediaSession = new MediaSession(mInstance.getContext(), "VideoView2MediaSession");
+ mMediaSession.setCallback(new MediaSessionCallback());
+
+ attachMediaControlView();
+ }
+
+ @Override
+ public void onDetachedFromWindow_impl() {
+ mSuperProvider.onDetachedFromWindow_impl();
+ mMediaSession.release();
+ mMediaSession = null;
+ }
+
+
+ @Override
public CharSequence getAccessibilityClassName_impl() {
return VideoView2.class.getName();
}
@@ -509,9 +515,6 @@
+ ", mTargetState=" + mTargetState + ", width/height: " + width + "/" + height
+ ", " + view.toString());
}
- mSurfaceWidth = width;
- mSurfaceHeight = height;
-
if (needToStart()) {
mInstance.start();
}
@@ -535,8 +538,6 @@
Log.d(TAG, "onSurfaceChanged(). width/height: " + width + "/" + height
+ ", " + view.toString());
}
- mSurfaceWidth = width;
- mSurfaceHeight = height;
}
@Override
@@ -557,6 +558,18 @@
// Protected or private methods
///////////////////////////////////////////////////
+ private void attachMediaControlView() {
+ // TODO: change this so that the CC button appears only where there is a subtitle track.
+ // mMediaControlView.showCCButton();
+
+ // Get MediaController from MediaSession and set it inside MediaControlView
+ mMediaControlView.setController(mMediaSession.getController());
+
+ LayoutParams params =
+ new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+ mInstance.addView(mMediaControlView, params);
+ }
+
private boolean isInPlaybackState() {
return (mMediaPlayer != null
&& mCurrentState != STATE_ERROR
@@ -660,8 +673,6 @@
mAudioManager.abandonAudioFocus(null);
}
}
- mSurfaceWidth = 0;
- mSurfaceHeight = 0;
mVideoWidth = 0;
mVideoHeight = 0;
}
@@ -797,8 +808,8 @@
if (mMediaControlView != null) {
mMediaControlView.setEnabled(true);
}
- mVideoWidth = mp.getVideoWidth();
- mVideoHeight = mp.getVideoHeight();
+ int videoWidth = mp.getVideoWidth();
+ int videoHeight = mp.getVideoHeight();
// mSeekWhenPrepared may be changed after seekTo() call
int seekToPosition = mSeekWhenPrepared;
@@ -816,39 +827,32 @@
mMediaSession.setMetadata(builder.build());
}
- if (mVideoWidth != 0 && mVideoHeight != 0) {
- if (mVideoWidth != mSurfaceWidth || mVideoHeight != mSurfaceHeight) {
+ if (videoWidth != 0 && videoHeight != 0) {
+ if (videoWidth != mVideoWidth || videoHeight != mVideoHeight) {
if (DEBUG) {
Log.i(TAG, "OnPreparedListener() : ");
- Log.i(TAG, " video size: " + mVideoWidth + "/" + mVideoHeight);
- Log.i(TAG, " surface size: " + mSurfaceWidth + "/" + mSurfaceHeight);
+ Log.i(TAG, " video size: " + videoWidth + "/" + videoHeight);
Log.i(TAG, " measuredSize: " + mInstance.getMeasuredWidth() + "/"
+ mInstance.getMeasuredHeight());
Log.i(TAG, " viewSize: " + mInstance.getWidth() + "/"
+ mInstance.getHeight());
}
- // TODO: It seems like that overriding onMeasure() is needed like legacy code.
- mSurfaceWidth = mVideoWidth;
- mSurfaceHeight = mVideoHeight;
+ mVideoWidth = videoWidth;
+ mVideoHeight = videoHeight;
mInstance.requestLayout();
}
- if (mSurfaceWidth == mVideoWidth && mSurfaceHeight == mVideoHeight) {
- // We didn't actually change the size (it was already at the size
- // we need), so we won't get a "surface changed" callback, so
- // start the video here instead of in the callback.
- if (needToStart()) {
- mInstance.start();
- if (mMediaControlView != null) {
- mMediaControlView.show();
- }
- } else if (!mInstance.isPlaying() && (seekToPosition != 0
- || mInstance.getCurrentPosition() > 0)) {
- if (mMediaControlView != null) {
- // Show the media controls when we're paused into a video and
- // make them stick.
- mMediaControlView.show(0);
- }
+ if (needToStart()) {
+ mInstance.start();
+ if (mMediaControlView != null) {
+ mMediaControlView.show();
+ }
+ } else if (!mInstance.isPlaying() && (seekToPosition != 0
+ || mInstance.getCurrentPosition() > 0)) {
+ if (mMediaControlView != null) {
+ // Show the media controls when we're paused into a video and
+ // make them stick.
+ mMediaControlView.show(0);
}
}
} else {
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..fe16583
--- /dev/null
+++ b/packages/MediaComponents/test/AndroidManifest.xml
@@ -0,0 +1,46 @@
+<?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.session.MediaSessionService2" />
+ </intent-filter>
+ <meta-data android:name="android.media.session" android:value="TestSession" />
+ </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/MediaController2Test.java b/packages/MediaComponents/test/src/android/media/MediaController2Test.java
new file mode 100644
index 0000000..38d34cc
--- /dev/null
+++ b/packages/MediaComponents/test/src/android/media/MediaController2Test.java
@@ -0,0 +1,452 @@
+/*
+ * 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";
+
+ private MediaSession2 mSession;
+ private MediaController2Wrapper mController;
+ private 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.setPlayer(null);
+ }
+ });
+ 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 {
+ 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());
+ }
+
+ @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.setPlayer(null);
+ mSession = new MediaSession2.Builder(mContext, mPlayer)
+ .setSessionCallback(sessionCallback).build();
+ });
+ MediaController2Wrapper controller = createController(mSession.getToken(), false, null);
+ assertNotNull(controller);
+ controller.waitForConnect(false);
+ controller.waitForDisconnect(true);
+ }
+
+ @Test
+ public void testControllerCallback_releaseSession() throws InterruptedException {
+ sHandler.postAndSync(() -> {
+ mSession.setPlayer(null);
+ });
+ mController.waitForDisconnect(true);
+ }
+
+ @Test
+ public void testControllerCallback_release() throws InterruptedException {
+ mController.release();
+ mController.waitForDisconnect(true);
+ }
+
+ @Test
+ public void testIsConnected() throws InterruptedException {
+ assertTrue(mController.isConnected());
+ sHandler.postAndSync(()->{
+ mSession.setPlayer(null);
+ });
+ // 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.setPlayer(null);
+ 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(() -> {
+ controller.addPlaybackListener((state) -> {
+ // no-op. Just to set a binder call path from session to controller.
+ }, sessionHandler);
+ final PlaybackState 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.setPlayer(null);
+ mSession = null;
+ });
+ }
+ if (sessionThread != null) {
+ sessionThread.quitSafely();
+ }
+ if (testThread != null) {
+ testThread.quitSafely();
+ }
+ }
+ }
+
+ @Ignore
+ @Test
+ public void testGetServiceToken() {
+ SessionToken token = TestUtils.getServiceToken(mContext, MockMediaSessionService2.ID);
+ assertNotNull(token);
+ assertEquals(mContext.getPackageName(), token.getPackageName());
+ assertEquals(MockMediaSessionService2.ID, token.getId());
+ assertNull(token.getSessionBinder());
+ assertEquals(SessionToken.TYPE_SESSION_SERVICE, token.getType());
+ }
+
+ private void connectToService(SessionToken token) throws InterruptedException {
+ mController = createController(token);
+ mSession = TestServiceRegistry.getInstance().getServiceInstance().getSession();
+ mPlayer = (MockPlayer) mSession.getPlayer();
+ }
+
+ @Ignore
+ @Test
+ public void testConnectToService() throws InterruptedException {
+ connectToService(TestUtils.getServiceToken(mContext, MockMediaSessionService2.ID));
+
+ 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
+ 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 testRelease_beforeConnected() throws InterruptedException {
+ MediaController2 controller =
+ createController(mSession.getToken(), false, null);
+ controller.release();
+ }
+
+ @Test
+ public void testRelease_twice() throws InterruptedException {
+ mController.release();
+ mController.release();
+ }
+
+ @Test
+ public void testRelease_session() throws InterruptedException {
+ final String id = mSession.getToken().getId();
+ mController.release();
+ // Release is done immediately for session.
+ testNoInteraction();
+
+ // Test whether the controller is notified about later release of the session or
+ // re-creation.
+ testControllerAfterSessionIsGone(id);
+ }
+
+ @Ignore
+ @Test
+ public void testRelease_sessionService() throws InterruptedException {
+ connectToService(TestUtils.getServiceToken(mContext, MockMediaSessionService2.ID));
+ final CountDownLatch latch = new CountDownLatch(1);
+ TestServiceRegistry.getInstance().setServiceInstanceChangedCallback((service) -> {
+ if (service == null) {
+ // Destroying..
+ latch.countDown();
+ }
+ });
+ mController.release();
+ // Wait until release triggers onDestroy() of the session service.
+ assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ assertNull(TestServiceRegistry.getInstance().getServiceInstance());
+ testNoInteraction();
+
+ // Test whether the controller is notified about later release of the session or
+ // re-creation.
+ testControllerAfterSessionIsGone(MockMediaSessionService2.ID);
+ }
+
+ private void testControllerAfterSessionIsGone(final String id) throws InterruptedException {
+ sHandler.postAndSync(() -> {
+ // TODO(jaewan): Use Session.release later when we add the API.
+ mSession.setPlayer(null);
+ });
+ mController.waitForDisconnect(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 release.");
+ latch.countDown();
+ };
+ 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..f7224ea
--- /dev/null
+++ b/packages/MediaComponents/test/src/android/media/MediaSession2Test.java
@@ -0,0 +1,266 @@
+/*
+ * 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.setPlayer(null);
+ });
+ }
+
+ @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.setPlayer(null);
+ });
+ }
+
+ @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 {
+ 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 {
+ 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.setPlayer(null);
+ mPlayer = new MockPlayer(1);
+ mSession = new MediaSession2.Builder(mContext, mPlayer)
+ .setSessionCallback(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.setPlayer(null);
+ mSession = new MediaSession2.Builder(mContext, mPlayer)
+ .setSessionCallback(sessionCallback).build();
+ });
+ MediaController2Wrapper controller = createController(mSession.getToken(), false, null);
+ assertNotNull(controller);
+ controller.waitForConnect(false);
+ controller.waitForDisconnect(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..ab842c4
--- /dev/null
+++ b/packages/MediaComponents/test/src/android/media/MediaSession2TestBase.java
@@ -0,0 +1,148 @@
+/*
+ * 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.MediaSession2.CommandGroup;
+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<>();
+
+ @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).release();
+ }
+ }
+
+ MediaController2Wrapper createController(SessionToken token) throws InterruptedException {
+ return createController(token, true, null);
+ }
+
+ MediaController2Wrapper createController(@NonNull SessionToken token, boolean waitForConnect,
+ @Nullable TestControllerCallback callback)
+ throws InterruptedException {
+ if (callback == null) {
+ callback = new TestControllerCallback();
+ }
+ MediaController2Wrapper controller = new MediaController2Wrapper(mContext, token, callback);
+ mControllers.add(controller);
+ if (waitForConnect) {
+ controller.waitForConnect(true);
+ }
+ return controller;
+ }
+
+ public static class TestControllerCallback extends MediaController2.ControllerCallback {
+ public final CountDownLatch connectLatch = new CountDownLatch(1);
+ public final CountDownLatch disconnectLatch = new CountDownLatch(1);
+
+ @CallSuper
+ @Override
+ public void onConnected(CommandGroup commands) {
+ super.onConnected(commands);
+ connectLatch.countDown();
+ }
+
+ @CallSuper
+ @Override
+ public void onDisconnected() {
+ super.onDisconnected();
+ disconnectLatch.countDown();
+ }
+ }
+
+ public class MediaController2Wrapper extends MediaController2 {
+ private final TestControllerCallback mCallback;
+
+ public MediaController2Wrapper(@NonNull Context context, @NonNull SessionToken token,
+ @NonNull TestControllerCallback callback) {
+ super(context, token, callback, sHandlerExecutor);
+ mCallback = callback;
+ }
+
+ public void waitForConnect(boolean expect) throws InterruptedException {
+ if (expect) {
+ assertTrue(mCallback.connectLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ } else {
+ assertFalse(mCallback.connectLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ }
+ }
+
+ public void waitForDisconnect(boolean expect) throws InterruptedException {
+ if (expect) {
+ assertTrue(mCallback.disconnectLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ } else {
+ assertFalse(mCallback.disconnectLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ }
+ }
+ }
+}
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..0ee37b1
--- /dev/null
+++ b/packages/MediaComponents/test/src/android/media/MediaSessionManager_MediaSession2.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 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.setPlayer(null);
+ });
+ }
+
+ // 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<SessionToken> tokens = mManager.getActiveSessionTokens();
+ assertNotNull(tokens);
+ for (int i = 0; i < tokens.size(); i++) {
+ SessionToken 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.setPlayer(null);
+ mSession = new MediaSession2.Builder(mContext, new MockPlayer(0)).setId(TAG)
+ .setSessionCallback(new SessionCallback() {
+ @Override
+ public MediaSession2.CommandGroup onConnect(ControllerInfo controller) {
+ // Reject all connection request.
+ return null;
+ }
+ }).build();
+ });
+ ensureChangeInSession();
+
+ boolean foundSession = false;
+ List<SessionToken> tokens = mManager.getActiveSessionTokens();
+ assertNotNull(tokens);
+ for (int i = 0; i < tokens.size(); i++) {
+ SessionToken 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.setPlayer(null);
+ });
+ ensureChangeInSession();
+
+ // When the mSession's player becomes null, it should lose binder connection between server.
+ // So server will forget the session.
+ List<SessionToken> tokens = mManager.getActiveSessionTokens();
+ for (int i = 0; i < tokens.size(); i++) {
+ SessionToken token = tokens.get(i);
+ assertFalse(mContext.getPackageName().equals(token.getPackageName())
+ && TAG.equals(token.getId()));
+ }
+ }
+
+ @Test
+ public void testGetMediaSessionService2Token() throws InterruptedException {
+ boolean foundTestSessionService = false;
+ List<SessionToken> tokens = mManager.getSessionServiceTokens();
+ for (int i = 0; i < tokens.size(); i++) {
+ SessionToken token = tokens.get(i);
+ if (mContext.getPackageName().equals(token.getPackageName())
+ && MockMediaSessionService2.ID.equals(token.getId())) {
+ assertFalse(foundTestSessionService);
+ assertEquals(SessionToken.TYPE_SESSION_SERVICE, token.getType());
+ assertNull(token.getSessionBinder());
+ foundTestSessionService = true;
+ }
+ }
+ assertTrue(foundTestSessionService);
+ }
+
+ @Test
+ public void testGetAllSessionTokens() throws InterruptedException {
+ boolean foundTestSession = false;
+ boolean foundTestSessionService = false;
+ List<SessionToken> tokens = mManager.getAllSessionTokens();
+ for (int i = 0; i < tokens.size(); i++) {
+ SessionToken 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(SessionToken.TYPE_SESSION_SERVICE, token.getType());
+ break;
+ default:
+ fail("Unexpected session " + token + " exists in the package");
+ }
+ }
+ assertTrue(foundTestSession);
+ assertTrue(foundTestSessionService);
+ }
+
+ // 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/MockMediaSessionService2.java b/packages/MediaComponents/test/src/android/media/MockMediaSessionService2.java
new file mode 100644
index 0000000..e4a7485
--- /dev/null
+++ b/packages/MediaComponents/test/src/android/media/MockMediaSessionService2.java
@@ -0,0 +1,72 @@
+/*
+ * 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.media.MediaSession2.ControllerInfo;
+import android.media.MediaSession2.SessionCallback;
+import android.media.TestUtils.SyncHandler;
+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";
+ public MediaSession2 mSession;
+
+ @Override
+ public MediaSession2 onCreateSession(String sessionId) {
+ final MockPlayer player = new MockPlayer(1);
+ SyncHandler handler = (SyncHandler) TestServiceRegistry.getInstance().getHandler();
+ try {
+ handler.postAndSync(() -> {
+ mSession = new MediaSession2.Builder(MockMediaSessionService2.this, player)
+ .setId(sessionId).setSessionCallback(new MySessionCallback()).build();
+ });
+ } catch (InterruptedException e) {
+ fail(e.toString());
+ }
+ return mSession;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ }
+
+ @Override
+ public void onDestroy() {
+ TestServiceRegistry.getInstance().cleanUp();
+ super.onDestroy();
+ }
+
+ 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..b0d7a69
--- /dev/null
+++ b/packages/MediaComponents/test/src/android/media/MockPlayer.java
@@ -0,0 +1,112 @@
+/*
+ * 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.session.PlaybackState;
+import android.os.Handler;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * 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 PlaybackState 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 PlaybackState getPlaybackState() {
+ return mLastPlaybackState;
+ }
+
+ @Override
+ public void addPlaybackListener(
+ @NonNull PlaybackListener listener, @NonNull Handler handler) {
+ mListeners.add(new PlaybackListenerHolder(listener, handler));
+ }
+
+ @Override
+ public void removePlaybackListener(@NonNull PlaybackListener listener) {
+ int index = PlaybackListenerHolder.indexOf(mListeners, listener);
+ if (index >= 0) {
+ mListeners.remove(index);
+ }
+ }
+
+ public void notifyPlaybackState(final PlaybackState state) {
+ mLastPlaybackState = state;
+ for (int i = 0; i < mListeners.size(); i++) {
+ mListeners.get(i).postPlaybackChange(state);
+ }
+ }
+}
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..b0b87de
--- /dev/null
+++ b/packages/MediaComponents/test/src/android/media/PlaybackListenerHolder.java
@@ -0,0 +1,83 @@
+/*
+ * 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;
+
+/**
+ * Holds {@link PlaybackListener} with the {@link Handler}.
+ */
+public class PlaybackListenerHolder extends Handler {
+ private static final int ON_PLAYBACK_CHANGED = 1;
+
+ public final PlaybackListener listener;
+
+ public PlaybackListenerHolder(
+ @NonNull PlaybackListener listener, @NonNull Handler handler) {
+ super(handler.getLooper());
+ this.listener = listener;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case ON_PLAYBACK_CHANGED:
+ listener.onPlaybackChanged((PlaybackState) msg.obj);
+ break;
+ }
+ }
+
+ public void postPlaybackChange(PlaybackState state) {
+ obtainMessage(ON_PLAYBACK_CHANGED, state).sendToTarget();
+ }
+
+ /**
+ * 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..378a6c4
--- /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().setPlayer(null);
+ } else {
+ mHandler.postAndSync(() -> {
+ mService.getSession().setPlayer(null);
+ });
+ }
+ } catch (InterruptedException e) {
+ // No-op. Service containing session will die, but shouldn't be a huge issue.
+ }
+ // stopSelf() would not kill service while the binder connection established by
+ // bindService() exists, and setPlayer(null) above will do the job instead.
+ // 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..0cca12c
--- /dev/null
+++ b/packages/MediaComponents/test/src/android/media/TestUtils.java
@@ -0,0 +1,86 @@
+/*
+ * 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.Handler;
+
+import android.os.Looper;
+
+import java.util.List;
+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 PlaybackState createPlaybackState(int state) {
+ return new PlaybackState.Builder().setState(state, 0, 1.0f).build();
+ }
+
+ public static SessionToken getServiceToken(Context context, String id) {
+ MediaSessionManager manager =
+ (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);
+ List<SessionToken> tokens = manager.getSessionServiceTokens();
+ for (int i = 0; i < tokens.size(); i++) {
+ SessionToken token = tokens.get(i);
+ if (context.getPackageName().equals(token.getPackageName())
+ && id.equals(token.getId())) {
+ return token;
+ }
+ }
+ fail("Failed to find service");
+ return null;
+ }
+
+ /**
+ * 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 a9d0054..686e3e3 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -309,7 +309,7 @@
fullConfig.format = config->format;
ret = AudioSystem::getOutputForAttr(attr, &io,
actualSessionId,
- &streamType, client.clientUid,
+ &streamType, client.clientPid, client.clientUid,
&fullConfig,
(audio_output_flags_t)(AUDIO_OUTPUT_FLAG_MMAP_NOIRQ |
AUDIO_OUTPUT_FLAG_DIRECT),
@@ -690,7 +690,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) {
diff --git a/services/audioflinger/Effects.cpp b/services/audioflinger/Effects.cpp
index b13e551..979290f 100644
--- a/services/audioflinger/Effects.cpp
+++ b/services/audioflinger/Effects.cpp
@@ -1027,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);
@@ -1071,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);
@@ -2020,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/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 8bf50b1..70ac32c 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"
@@ -2882,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);
@@ -2895,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;
@@ -7819,6 +7821,7 @@
ret = AudioSystem::getOutputForAttr(&mAttr, &io,
mSessionId,
&stream,
+ client.clientPid,
client.clientUid,
&config,
flags,
diff --git a/services/audiopolicy/AudioPolicyInterface.h b/services/audiopolicy/AudioPolicyInterface.h
index 2a8f54d..7f09e9b 100644
--- a/services/audiopolicy/AudioPolicyInterface.h
+++ b/services/audiopolicy/AudioPolicyInterface.h
@@ -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.
diff --git a/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp b/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp
index 044d6db..d5e8e1b 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;
}
diff --git a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
index 78a02b2..e058dc8 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;
}
diff --git a/services/audiopolicy/managerdefault/AudioPolicyManager.h b/services/audiopolicy/managerdefault/AudioPolicyManager.h
index 4fd73e6..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,
@@ -629,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 680d7b9..f1d7d86 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,
diff --git a/services/audiopolicy/service/AudioPolicyService.h b/services/audiopolicy/service/AudioPolicyService.h
index 9bc13c2..c21aa58 100644
--- a/services/audiopolicy/service/AudioPolicyService.h
+++ b/services/audiopolicy/service/AudioPolicyService.h
@@ -78,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,
diff --git a/services/camera/libcameraservice/CameraService.cpp b/services/camera/libcameraservice/CameraService.cpp
index 5fc1fa3..536b54d 100644
--- a/services/camera/libcameraservice/CameraService.cpp
+++ b/services/camera/libcameraservice/CameraService.cpp
@@ -316,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__,
@@ -380,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,
diff --git a/services/camera/libcameraservice/CameraService.h b/services/camera/libcameraservice/CameraService.h
index 575cebf..67db7ec 100644
--- a/services/camera/libcameraservice/CameraService.h
+++ b/services/camera/libcameraservice/CameraService.h
@@ -551,8 +551,9 @@
// 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
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..c36c7cf 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) {
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..7e71c3b 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.
@@ -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)",
@@ -735,7 +741,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 +751,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 +765,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 +803,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 +828,7 @@
}
status_t Camera3Device::submitRequestsHelper(
- const List<const CameraMetadata> &requests,
+ const List<const PhysicalCameraSettingsList> &requests,
const std::list<const SurfaceMap> &surfaceMaps,
bool repeating,
/*out*/
@@ -1069,42 +1078,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 +1234,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 +1247,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 +1262,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 +1324,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 +1333,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 +1522,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(
@@ -2075,15 +2098,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 +2132,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 +2184,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 +2226,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 +2307,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 +2400,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 +2440,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;
}
@@ -3260,10 +3330,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 +3355,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,19 +3385,20 @@
}
}
+ 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;
@@ -3338,22 +3416,28 @@
}
if (hidlSession_3_4 != nullptr) {
- // We do; use v3.4 for the call
+ // We do; use v3.4 for the call, and construct a v3.4
+ // HalStreamConfiguration
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 = hidlSession_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;
}
+ 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 (hidlSession_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 = hidlSession_3_3->configureStreams_3_3(requestedConfiguration3_2,
[&status, &finalConfiguration]
(common::V1_0::Status s, const device::V3_3::HalStreamConfiguration& halConfiguration) {
finalConfiguration = halConfiguration;
@@ -3367,7 +3451,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 +3465,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 +3619,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 +3662,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 +3689,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 +3886,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 +3903,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 +3918,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 +4211,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 +4285,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 +4340,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 +4404,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 +4412,49 @@
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) {
+ statusTracker->markComponentIdle(mStatusId, Fence::NO_FENCE);
+
+ sp<Camera3Device> parent = mParent.promote();
+ 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 +4558,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 +4583,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
@@ -4515,6 +4767,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 +4807,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 +5075,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 +5158,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 +5223,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 +5258,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 +5309,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 +5428,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 +5471,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..63e6219 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.
@@ -99,12 +99,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 +119,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;
@@ -426,7 +428,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,17 +448,17 @@
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);
@@ -541,22 +543,34 @@
* 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);
/**
+ * Internally re-configure camera device using new session parameters.
+ * This will get triggered by the request thread.
+ */
+ 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 +669,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 +677,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 +805,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 +831,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 +894,9 @@
static const int32_t kRequestLatencyBinSize = 40; // in ms
CameraLatencyHistogram mRequestLatency;
+
+ Vector<int32_t> mSessionParamKeys;
+ CameraMetadata mLatestSessionParams;
};
sp<RequestThread> mRequestThread;
@@ -1006,21 +1034,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;
diff --git a/services/camera/libcameraservice/device3/Camera3DummyStream.cpp b/services/camera/libcameraservice/device3/Camera3DummyStream.cpp
index 0a245c4..44eb68a 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) {
}
diff --git a/services/camera/libcameraservice/device3/Camera3DummyStream.h b/services/camera/libcameraservice/device3/Camera3DummyStream.h
index 684f4b0..dcf9160 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.
@@ -114,6 +114,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..b73e256 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),
diff --git a/services/camera/libcameraservice/device3/Camera3OutputStream.h b/services/camera/libcameraservice/device3/Camera3OutputStream.h
index 18b1901..824aef7 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();
@@ -194,6 +197,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/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..0d91620 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,75 @@
return mOriginalDataSpace;
}
+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 +222,7 @@
ALOGE("%s: In error state", __FUNCTION__);
return NULL;
case STATE_CONSTRUCTED:
+ case STATE_IN_IDLE:
// OK
break;
case STATE_IN_CONFIG:
@@ -179,6 +252,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 +297,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 +348,7 @@
return INVALID_OPERATION;
case STATE_IN_CONFIG:
case STATE_IN_RECONFIG:
+ case STATE_IN_IDLE:
// OK
break;
case STATE_CONSTRUCTED:
@@ -282,7 +364,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..f85dff2 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
@@ -386,6 +399,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 +440,8 @@
STATE_IN_RECONFIG,
STATE_CONFIGURED,
STATE_PREPARING,
- STATE_ABANDONED
+ STATE_ABANDONED,
+ STATE_IN_IDLE
} mState;
mutable Mutex mLock;
@@ -422,7 +449,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 +556,7 @@
bool mDataSpaceOverridden;
android_dataspace mOriginalDataSpace;
+ String8 mPhysicalCameraId;
}; // class Camera3Stream
}; // namespace camera3
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/soundtrigger/Android.mk b/services/soundtrigger/Android.mk
index ca44737..ad3666e 100644
--- a/services/soundtrigger/Android.mk
+++ b/services/soundtrigger/Android.mk
@@ -53,6 +53,7 @@
libhidltransport \
libbase \
libaudiohal \
+ libaudiohal_deathhandler \
android.hardware.soundtrigger@2.0 \
android.hardware.soundtrigger@2.1 \
android.hardware.audio.common@2.0 \