/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define LOG_NDEBUG 0 #define LOG_TAG "CameraBinderTests" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace android; using ::android::hardware::ICameraService; using ::android::hardware::camera2::ICameraDeviceUser; #define ASSERT_NOT_NULL(x) \ ASSERT_TRUE((x) != nullptr) #define SETUP_TIMEOUT 2000000000 // ns #define IDLE_TIMEOUT 2000000000 // ns // Stub listener implementation class TestCameraServiceListener : public hardware::BnCameraServiceListener { std::map mCameraTorchStatuses; std::map mCameraStatuses; mutable Mutex mLock; mutable Condition mCondition; mutable Condition mTorchCondition; public: virtual ~TestCameraServiceListener() {}; virtual binder::Status onStatusChanged(int32_t status, const String16& cameraId) { Mutex::Autolock l(mLock); mCameraStatuses[cameraId] = status; mCondition.broadcast(); return binder::Status::ok(); }; virtual binder::Status onPhysicalCameraStatusChanged(int32_t /*status*/, const String16& /*cameraId*/, const String16& /*physicalCameraId*/) { // No op return binder::Status::ok(); }; virtual binder::Status onTorchStatusChanged(int32_t status, const String16& cameraId) { Mutex::Autolock l(mLock); mCameraTorchStatuses[cameraId] = status; mTorchCondition.broadcast(); return binder::Status::ok(); }; virtual binder::Status onTorchStrengthLevelChanged(const String16& /*cameraId*/, int32_t /*torchStrength*/) { // No op return binder::Status::ok(); } virtual binder::Status onCameraAccessPrioritiesChanged() { // No op return binder::Status::ok(); } virtual binder::Status onCameraOpened(const String16& /*cameraId*/, const String16& /*clientPackageName*/) { // No op return binder::Status::ok(); } virtual binder::Status onCameraClosed(const String16& /*cameraId*/) { // No op return binder::Status::ok(); } bool waitForNumCameras(size_t num) const { Mutex::Autolock l(mLock); if (mCameraStatuses.size() == num) { return true; } while (mCameraStatuses.size() < num) { if (mCondition.waitRelative(mLock, SETUP_TIMEOUT) != OK) { return false; } } return true; }; bool waitForTorchState(int32_t status, int32_t cameraId) const { Mutex::Autolock l(mLock); const auto& iter = mCameraTorchStatuses.find(String16(String8::format("%d", cameraId))); if (iter != mCameraTorchStatuses.end() && iter->second == status) { return true; } bool foundStatus = false; while (!foundStatus) { if (mTorchCondition.waitRelative(mLock, SETUP_TIMEOUT) != OK) { return false; } const auto& iter = mCameraTorchStatuses.find(String16(String8::format("%d", cameraId))); foundStatus = (iter != mCameraTorchStatuses.end() && iter->second == status); } return true; }; int32_t getTorchStatus(int32_t cameraId) const { Mutex::Autolock l(mLock); const auto& iter = mCameraTorchStatuses.find(String16(String8::format("%d", cameraId))); if (iter == mCameraTorchStatuses.end()) { return hardware::ICameraServiceListener::TORCH_STATUS_UNKNOWN; } return iter->second; }; int32_t getStatus(const String16& cameraId) const { Mutex::Autolock l(mLock); const auto& iter = mCameraStatuses.find(cameraId); if (iter == mCameraStatuses.end()) { return hardware::ICameraServiceListener::STATUS_UNKNOWN; } return iter->second; }; }; // Callback implementation class TestCameraDeviceCallbacks : public hardware::camera2::BnCameraDeviceCallbacks { public: enum Status { IDLE, ERROR, PREPARED, RUNNING, SENT_RESULT, UNINITIALIZED, REPEATING_REQUEST_ERROR, REQUEST_QUEUE_EMPTY, }; protected: bool mError; int32_t mLastStatus; mutable std::vector mStatusesHit; mutable Mutex mLock; mutable Condition mStatusCondition; public: TestCameraDeviceCallbacks() : mError(false), mLastStatus(UNINITIALIZED) {} virtual ~TestCameraDeviceCallbacks() {} virtual binder::Status onDeviceError(int errorCode, const CaptureResultExtras& resultExtras) { (void) resultExtras; ALOGE("%s: onDeviceError occurred with: %d", __FUNCTION__, static_cast(errorCode)); Mutex::Autolock l(mLock); mError = true; mLastStatus = ERROR; mStatusesHit.push_back(mLastStatus); mStatusCondition.broadcast(); return binder::Status::ok(); } virtual binder::Status onDeviceIdle() { Mutex::Autolock l(mLock); mLastStatus = IDLE; mStatusesHit.push_back(mLastStatus); mStatusCondition.broadcast(); return binder::Status::ok(); } virtual binder::Status onCaptureStarted(const CaptureResultExtras& resultExtras, int64_t timestamp) { (void) resultExtras; (void) timestamp; Mutex::Autolock l(mLock); mLastStatus = RUNNING; mStatusesHit.push_back(mLastStatus); mStatusCondition.broadcast(); return binder::Status::ok(); } virtual binder::Status onResultReceived(const CameraMetadata& metadata, const CaptureResultExtras& resultExtras, const std::vector& physicalResultInfos) { (void) metadata; (void) resultExtras; (void) physicalResultInfos; Mutex::Autolock l(mLock); mLastStatus = SENT_RESULT; mStatusesHit.push_back(mLastStatus); mStatusCondition.broadcast(); return binder::Status::ok(); } virtual binder::Status onPrepared(int streamId) { (void) streamId; Mutex::Autolock l(mLock); mLastStatus = PREPARED; mStatusesHit.push_back(mLastStatus); mStatusCondition.broadcast(); return binder::Status::ok(); } virtual binder::Status onRepeatingRequestError( int64_t lastFrameNumber, int32_t stoppedSequenceId) { (void) lastFrameNumber; (void) stoppedSequenceId; Mutex::Autolock l(mLock); mLastStatus = REPEATING_REQUEST_ERROR; mStatusesHit.push_back(mLastStatus); mStatusCondition.broadcast(); return binder::Status::ok(); } virtual binder::Status onRequestQueueEmpty() { Mutex::Autolock l(mLock); mLastStatus = REQUEST_QUEUE_EMPTY; mStatusesHit.push_back(mLastStatus); mStatusCondition.broadcast(); return binder::Status::ok(); } // Test helper functions: bool hadError() const { Mutex::Autolock l(mLock); return mError; } bool waitForStatus(Status status) const { Mutex::Autolock l(mLock); if (mLastStatus == status) { return true; } while (std::find(mStatusesHit.begin(), mStatusesHit.end(), status) == mStatusesHit.end()) { if (mStatusCondition.waitRelative(mLock, IDLE_TIMEOUT) != OK) { mStatusesHit.clear(); return false; } } mStatusesHit.clear(); return true; } void clearStatus() const { Mutex::Autolock l(mLock); mStatusesHit.clear(); } bool waitForIdle() const { return waitForStatus(IDLE); } }; namespace { Mutex gLock; class DeathNotifier : public IBinder::DeathRecipient { public: DeathNotifier() {} virtual void binderDied(const wp& /*who*/) { ALOGV("binderDied"); Mutex::Autolock _l(gLock); ALOGW("Camera service died!"); } }; sp gDeathNotifier; }; // anonymous namespace // Exercise basic binder calls for the camera service TEST(CameraServiceBinderTest, CheckBinderCameraService) { ProcessState::self()->startThreadPool(); sp sm = defaultServiceManager(); sp binder = sm->getService(String16("media.camera")); ASSERT_NOT_NULL(binder); if (gDeathNotifier == NULL) { gDeathNotifier = new DeathNotifier(); } binder->linkToDeath(gDeathNotifier); sp service = interface_cast(binder); binder::Status res; int32_t numCameras = 0; res = service->getNumberOfCameras(hardware::ICameraService::CAMERA_TYPE_ALL, &numCameras); EXPECT_TRUE(res.isOk()) << res; EXPECT_LE(0, numCameras); // Check listener binder calls sp listener(new TestCameraServiceListener()); std::vector statuses; res = service->addListener(listener, &statuses); EXPECT_TRUE(res.isOk()) << res; EXPECT_EQ(numCameras, static_cast(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)); bool isSupported = false; res = service->supportsCameraApi(cameraId, hardware::ICameraService::API_VERSION_2, &isSupported); EXPECT_TRUE(res.isOk()) << res; // We only care about binder calls for the Camera2 API. Camera1 is deprecated. if (!isSupported) { continue; } // Check metadata binder call CameraMetadata metadata; res = service->getCameraCharacteristics(cameraId, /*targetSdkVersion*/__ANDROID_API_FUTURE__, /*overrideToPortrait*/false, &metadata); EXPECT_TRUE(res.isOk()) << res; EXPECT_FALSE(metadata.isEmpty()); // Make sure we're available, or skip device tests otherwise int32_t s = listener->getStatus(cameraId); EXPECT_EQ(::android::hardware::ICameraServiceListener::STATUS_PRESENT, s); if (s != ::android::hardware::ICameraServiceListener::STATUS_PRESENT) { continue; } // Check connect binder calls sp callbacks(new TestCameraDeviceCallbacks()); sp device; res = service->connectDevice(callbacks, cameraId, String16("meeeeeeeee!"), {}, hardware::ICameraService::USE_CALLING_UID, /*oomScoreOffset*/ 0, /*targetSdkVersion*/__ANDROID_API_FUTURE__, /*overrideToPortrait*/false, /*out*/&device); EXPECT_TRUE(res.isOk()) << res; ASSERT_NE(nullptr, device.get()); device->disconnect(); EXPECT_FALSE(callbacks->hadError()); int32_t torchStatus = listener->getTorchStatus(i); if (torchStatus == hardware::ICameraServiceListener::TORCH_STATUS_AVAILABLE_OFF) { // Check torch calls res = service->setTorchMode(cameraId, /*enabled*/true, callbacks); EXPECT_TRUE(res.isOk()) << res; EXPECT_TRUE(listener->waitForTorchState( hardware::ICameraServiceListener::TORCH_STATUS_AVAILABLE_ON, i)); res = service->setTorchMode(cameraId, /*enabled*/false, callbacks); EXPECT_TRUE(res.isOk()) << res; EXPECT_TRUE(listener->waitForTorchState( hardware::ICameraServiceListener::TORCH_STATUS_AVAILABLE_OFF, i)); } } res = service->removeListener(listener); EXPECT_TRUE(res.isOk()) << res; } // Test fixture for client focused binder tests class CameraClientBinderTest : public testing::Test { protected: sp service; int32_t numCameras; std::vector, sp>> openDeviceList; sp serviceListener; std::pair, sp> openNewDevice(const String16& deviceId) { sp callbacks(new TestCameraDeviceCallbacks()); sp device; { SCOPED_TRACE("openNewDevice"); binder::Status res = service->connectDevice(callbacks, deviceId, String16("meeeeeeeee!"), {}, hardware::ICameraService::USE_CALLING_UID, /*oomScoreOffset*/ 0, /*targetSdkVersion*/__ANDROID_API_FUTURE__, /*overrideToPortrait*/false, /*out*/&device); EXPECT_TRUE(res.isOk()) << res; } auto p = std::make_pair(callbacks, device); openDeviceList.push_back(p); return p; } void closeDevice(std::pair, sp>& p) { if (p.second.get() != nullptr) { binder::Status res = p.second->disconnect(); EXPECT_TRUE(res.isOk()) << res; { SCOPED_TRACE("closeDevice"); EXPECT_FALSE(p.first->hadError()); } } auto iter = std::find(openDeviceList.begin(), openDeviceList.end(), p); if (iter != openDeviceList.end()) { openDeviceList.erase(iter); } } virtual void SetUp() { ProcessState::self()->startThreadPool(); sp sm = defaultServiceManager(); sp binder = sm->getService(String16("media.camera")); service = interface_cast(binder); serviceListener = new TestCameraServiceListener(); std::vector 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); } virtual void TearDown() { service = nullptr; numCameras = 0; for (auto& p : openDeviceList) { closeDevice(p); } } }; TEST_F(CameraClientBinderTest, CheckBinderCameraDeviceUser) { 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(cameraId8); int32_t s = serviceListener->getStatus(cameraId); EXPECT_EQ(hardware::ICameraServiceListener::STATUS_PRESENT, s); if (s != hardware::ICameraServiceListener::STATUS_PRESENT) { continue; } binder::Status res; auto p = openNewDevice(cameraId); sp callbacks = p.first; sp device = p.second; // Setup a buffer queue; I'm just using the vendor opaque format here as that is // guaranteed to be present sp gbProducer; sp gbConsumer; BufferQueue::createBufferQueue(&gbProducer, &gbConsumer); sp opaqueConsumer = new BufferItemConsumer(gbConsumer, GRALLOC_USAGE_SW_READ_NEVER, /*maxImages*/2, /*controlledByApp*/true); EXPECT_TRUE(opaqueConsumer.get() != nullptr); opaqueConsumer->setName(String8("nom nom nom")); // Set to VGA dimens for default, as that is guaranteed to be present EXPECT_EQ(OK, gbConsumer->setDefaultBufferSize(640, 480)); EXPECT_EQ(OK, gbConsumer->setDefaultBufferFormat(HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED)); sp surface(new Surface(gbProducer, /*controlledByApp*/false)); String16 noPhysicalId; OutputConfiguration output(gbProducer, /*rotation*/0, noPhysicalId); // Can we configure? res = device->beginConfigure(); EXPECT_TRUE(res.isOk()) << res; status_t streamId; res = device->createStream(output, &streamId); EXPECT_TRUE(res.isOk()) << res; EXPECT_LE(0, streamId); CameraMetadata sessionParams; std::vector offlineStreamIds; res = device->endConfigure(/*isConstrainedHighSpeed*/ false, sessionParams, ns2ms(systemTime()), &offlineStreamIds); EXPECT_TRUE(res.isOk()) << res; EXPECT_FALSE(callbacks->hadError()); // Session configuration must also be supported in this case SessionConfiguration sessionConfiguration = { /*inputWidth*/ 0, /*inputHeight*/0, /*inputFormat*/ -1, CAMERA3_STREAM_CONFIGURATION_NORMAL_MODE}; sessionConfiguration.addOutputConfiguration(output); bool queryStatus; res = device->isSessionConfigurationSupported(sessionConfiguration, &queryStatus); EXPECT_TRUE(res.isOk() || (res.serviceSpecificErrorCode() == ICameraService::ERROR_INVALID_OPERATION)) << res; if (res.isOk()) { EXPECT_TRUE(queryStatus); } // Can we make requests? CameraMetadata requestTemplate; res = device->createDefaultRequest(/*preview template*/1, /*out*/&requestTemplate); EXPECT_TRUE(res.isOk()) << res; hardware::camera2::CaptureRequest request; request.mPhysicalCameraSettings.push_back({cameraId8.string(), requestTemplate}); request.mSurfaceList.add(surface); request.mIsReprocess = false; int64_t lastFrameNumber = 0; int64_t lastFrameNumberPrev = 0; callbacks->clearStatus(); hardware::camera2::utils::SubmitInfo info; res = device->submitRequest(request, /*streaming*/true, /*out*/&info); EXPECT_TRUE(res.isOk()) << res; EXPECT_TRUE(callbacks->waitForStatus(TestCameraDeviceCallbacks::SENT_RESULT)); EXPECT_LE(0, info.mRequestId); // Can we stop requests? res = device->cancelRequest(info.mRequestId, /*out*/&lastFrameNumber); EXPECT_TRUE(res.isOk()) << res; EXPECT_TRUE(callbacks->waitForIdle()); EXPECT_FALSE(callbacks->hadError()); // Can we do it again? lastFrameNumberPrev = info.mLastFrameNumber; lastFrameNumber = 0; requestTemplate.clear(); res = device->createDefaultRequest(hardware::camera2::ICameraDeviceUser::TEMPLATE_PREVIEW, /*out*/&requestTemplate); EXPECT_TRUE(res.isOk()) << res; hardware::camera2::CaptureRequest request2; request2.mPhysicalCameraSettings.push_back({cameraId8.string(), requestTemplate}); request2.mSurfaceList.add(surface); request2.mIsReprocess = false; callbacks->clearStatus(); hardware::camera2::utils::SubmitInfo info2; res = device->submitRequest(request2, /*streaming*/true, /*out*/&info2); EXPECT_TRUE(res.isOk()) << res; EXPECT_EQ(hardware::camera2::ICameraDeviceUser::NO_IN_FLIGHT_REPEATING_FRAMES, info2.mLastFrameNumber); lastFrameNumber = 0; EXPECT_TRUE(callbacks->waitForStatus(TestCameraDeviceCallbacks::SENT_RESULT)); EXPECT_LE(0, info2.mRequestId); res = device->cancelRequest(info2.mRequestId, /*out*/&lastFrameNumber); EXPECT_TRUE(res.isOk()) << res; EXPECT_TRUE(callbacks->waitForIdle()); EXPECT_LE(lastFrameNumberPrev, lastFrameNumber); sleep(/*second*/1); // allow some time for errors to show up, if any EXPECT_FALSE(callbacks->hadError()); // Can we do it with a request list? lastFrameNumberPrev = lastFrameNumber; lastFrameNumber = 0; requestTemplate.clear(); CameraMetadata requestTemplate2; res = device->createDefaultRequest(hardware::camera2::ICameraDeviceUser::TEMPLATE_PREVIEW, /*out*/&requestTemplate); EXPECT_TRUE(res.isOk()) << res; res = device->createDefaultRequest(hardware::camera2::ICameraDeviceUser::TEMPLATE_PREVIEW, /*out*/&requestTemplate2); EXPECT_TRUE(res.isOk()) << res; android::hardware::camera2::CaptureRequest request3; android::hardware::camera2::CaptureRequest request4; request3.mPhysicalCameraSettings.push_back({cameraId8.string(), requestTemplate}); request3.mSurfaceList.add(surface); request3.mIsReprocess = false; request4.mPhysicalCameraSettings.push_back({cameraId8.string(), requestTemplate2}); request4.mSurfaceList.add(surface); request4.mIsReprocess = false; std::vector requestList; requestList.push_back(request3); requestList.push_back(request4); callbacks->clearStatus(); hardware::camera2::utils::SubmitInfo info3; res = device->submitRequestList(requestList, /*streaming*/false, /*out*/&info3); EXPECT_TRUE(res.isOk()) << res; EXPECT_LE(0, info3.mRequestId); EXPECT_TRUE(callbacks->waitForStatus(TestCameraDeviceCallbacks::SENT_RESULT)); EXPECT_TRUE(callbacks->waitForIdle()); EXPECT_LE(lastFrameNumberPrev, info3.mLastFrameNumber); sleep(/*second*/1); // allow some time for errors to show up, if any EXPECT_FALSE(callbacks->hadError()); // Can we unconfigure? res = device->beginConfigure(); EXPECT_TRUE(res.isOk()) << res; res = device->deleteStream(streamId); EXPECT_TRUE(res.isOk()) << res; res = device->endConfigure(/*isConstrainedHighSpeed*/ false, sessionParams, ns2ms(systemTime()), &offlineStreamIds); EXPECT_TRUE(res.isOk()) << res; sleep(/*second*/1); // allow some time for errors to show up, if any EXPECT_FALSE(callbacks->hadError()); closeDevice(p); } }; TEST_F(CameraClientBinderTest, CheckBinderCaptureRequest) { sp requestOriginal, requestParceled; sp gbProducer; sp gbConsumer; BufferQueue::createBufferQueue(&gbProducer, &gbConsumer); sp surface(new Surface(gbProducer, /*controlledByApp*/false)); Vector> 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); };