Merge "cameraservice_test: Create a new test for camera permissions."
diff --git a/services/camera/libcameraservice/CameraService.cpp b/services/camera/libcameraservice/CameraService.cpp
index 1fe47ce..a2e7f7e 100644
--- a/services/camera/libcameraservice/CameraService.cpp
+++ b/services/camera/libcameraservice/CameraService.cpp
@@ -1721,7 +1721,8 @@
// enforce system camera permissions
if (oomScoreOffset > 0 &&
- !hasPermissionsForSystemCamera(callingPid, CameraThreadState::getCallingUid())) {
+ !hasPermissionsForSystemCamera(callingPid, CameraThreadState::getCallingUid()) &&
+ !isTrustedCallingUid(CameraThreadState::getCallingUid())) {
String8 msg =
String8::format("Cannot change the priority of a client %s pid %d for "
"camera id %s without SYSTEM_CAMERA permissions",
diff --git a/services/camera/libcameraservice/tests/Android.bp b/services/camera/libcameraservice/tests/Android.bp
index 3616572..4d7798c 100644
--- a/services/camera/libcameraservice/tests/Android.bp
+++ b/services/camera/libcameraservice/tests/Android.bp
@@ -27,8 +27,13 @@
"external/dynamic_depth/internal",
],
+ header_libs: [
+ "libmedia_headers",
+ ],
+
shared_libs: [
"libbase",
+ "libbinder",
"libcutils",
"libcameraservice",
"libhidlbase",
@@ -57,6 +62,7 @@
],
srcs: [
+ "CameraPermissionsTest.cpp",
"CameraProviderManagerTest.cpp",
"ClientManagerTest.cpp",
"DepthProcessorTest.cpp",
diff --git a/services/camera/libcameraservice/tests/CameraPermissionsTest.cpp b/services/camera/libcameraservice/tests/CameraPermissionsTest.cpp
new file mode 100644
index 0000000..a2f185a
--- /dev/null
+++ b/services/camera/libcameraservice/tests/CameraPermissionsTest.cpp
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/hardware/BnCameraServiceListener.h>
+#include <android/hardware/BnCameraServiceProxy.h>
+#include <android/hardware/camera2/BnCameraDeviceCallbacks.h>
+#include <android/hardware/ICameraService.h>
+
+#include <private/android_filesystem_config.h>
+
+#include "../CameraService.h"
+#include "../utils/CameraServiceProxyWrapper.h"
+
+#include <gtest/gtest.h>
+
+#include <memory>
+#include <vector>
+
+using namespace android;
+using namespace android::hardware::camera;
+
+// Empty service listener.
+class TestCameraServiceListener : public hardware::BnCameraServiceListener {
+public:
+ virtual ~TestCameraServiceListener() {};
+
+ virtual binder::Status onStatusChanged(int32_t , const String16&) {
+ 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*/) {
+ 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();
+ }
+
+ virtual binder::Status onTorchStrengthLevelChanged(const String16& /*cameraId*/,
+ int32_t /*torchStrength*/) {
+ // No op
+ return binder::Status::ok();
+ }
+};
+
+// Empty device callback.
+class TestCameraDeviceCallbacks : public hardware::camera2::BnCameraDeviceCallbacks {
+public:
+ TestCameraDeviceCallbacks() {}
+
+ virtual ~TestCameraDeviceCallbacks() {}
+
+ virtual binder::Status onDeviceError(int /*errorCode*/,
+ const CaptureResultExtras& /*resultExtras*/) {
+ return binder::Status::ok();
+ }
+
+ virtual binder::Status onDeviceIdle() {
+ return binder::Status::ok();
+ }
+
+ virtual binder::Status onCaptureStarted(const CaptureResultExtras& /*resultExtras*/,
+ int64_t /*timestamp*/) {
+ return binder::Status::ok();
+ }
+
+ virtual binder::Status onResultReceived(const CameraMetadata& /*metadata*/,
+ const CaptureResultExtras& /*resultExtras*/,
+ const std::vector<PhysicalCaptureResultInfo>& /*physicalResultInfos*/) {
+ return binder::Status::ok();
+ }
+
+ virtual binder::Status onPrepared(int /*streamId*/) {
+ return binder::Status::ok();
+ }
+
+ virtual binder::Status onRepeatingRequestError(
+ int64_t /*lastFrameNumber*/, int32_t /*stoppedSequenceId*/) {
+ return binder::Status::ok();
+ }
+
+ virtual binder::Status onRequestQueueEmpty() {
+ return binder::Status::ok();
+ }
+};
+
+// Override isCameraDisabled from the CameraServiceProxy with a flag.
+class CameraServiceProxyOverride : public ::android::hardware::BnCameraServiceProxy {
+public:
+ CameraServiceProxyOverride() :
+ mCameraServiceProxy(CameraServiceProxyWrapper::getDefaultCameraServiceProxy()),
+ mCameraDisabled(false)
+ { }
+
+ virtual binder::Status getRotateAndCropOverride(const String16& packageName, int lensFacing,
+ int userId, int *ret) override {
+ return mCameraServiceProxy->getRotateAndCropOverride(packageName, lensFacing,
+ userId, ret);
+ }
+
+ virtual binder::Status pingForUserUpdate() override {
+ return mCameraServiceProxy->pingForUserUpdate();
+ }
+
+ virtual binder::Status notifyCameraState(
+ const hardware::CameraSessionStats& cameraSessionStats) override {
+ return mCameraServiceProxy->notifyCameraState(cameraSessionStats);
+ }
+
+ virtual binder::Status isCameraDisabled(bool *ret) override {
+ if (mOverrideCameraDisabled) {
+ *ret = mCameraDisabled;
+ return binder::Status::ok();
+ }
+ return mCameraServiceProxy->isCameraDisabled(ret);
+ }
+
+ void setCameraDisabled(bool cameraDisabled) {
+ mCameraDisabled = cameraDisabled;
+ }
+
+ void setOverrideCameraDisabled(bool overrideCameraDisabled) {
+ mOverrideCameraDisabled = overrideCameraDisabled;
+ }
+
+protected:
+ sp<hardware::ICameraServiceProxy> mCameraServiceProxy;
+ bool mCameraDisabled;
+ bool mOverrideCameraDisabled;
+};
+
+class AutoDisconnectDevice {
+public:
+ AutoDisconnectDevice(sp<hardware::camera2::ICameraDeviceUser> device) :
+ mDevice(device)
+ { }
+
+ ~AutoDisconnectDevice() {
+ if (mDevice != nullptr) {
+ mDevice->disconnect();
+ }
+ }
+
+private:
+ sp<hardware::camera2::ICameraDeviceUser> mDevice;
+};
+
+class CameraPermissionsTest : public ::testing::Test {
+protected:
+ static sp<CameraService> sCameraService;
+ static sp<CameraServiceProxyOverride> sCameraServiceProxy;
+ static std::shared_ptr<CameraServiceProxyWrapper> sCameraServiceProxyWrapper;
+ static uid_t sOldUid;
+
+ static void SetUpTestSuite() {
+ sOldUid = getuid();
+ setuid(AID_CAMERASERVER);
+ sCameraServiceProxy = new CameraServiceProxyOverride();
+ sCameraServiceProxyWrapper =
+ std::make_shared<CameraServiceProxyWrapper>(sCameraServiceProxy);
+ sCameraService = new CameraService(sCameraServiceProxyWrapper);
+ sCameraService->clearCachedVariables();
+ }
+
+ static void TearDownTestSuite() {
+ sCameraServiceProxyWrapper = nullptr;
+ sCameraServiceProxy = nullptr;
+ sCameraService = nullptr;
+ setuid(sOldUid);
+ }
+};
+
+sp<CameraService> CameraPermissionsTest::sCameraService = nullptr;
+sp<CameraServiceProxyOverride> CameraPermissionsTest::sCameraServiceProxy = nullptr;
+std::shared_ptr<CameraServiceProxyWrapper>
+CameraPermissionsTest::sCameraServiceProxyWrapper = nullptr;
+uid_t CameraPermissionsTest::sOldUid = 0;
+
+// Test that camera connections fail with ERROR_DISABLED when the camera is disabled via device
+// policy, and succeed when it isn't.
+TEST_F(CameraPermissionsTest, TestCameraDisabled) {
+ std::vector<hardware::CameraStatus> statuses;
+ sp<TestCameraServiceListener> serviceListener = new TestCameraServiceListener();
+ sCameraService->addListenerTest(serviceListener, &statuses);
+ sCameraServiceProxy->setOverrideCameraDisabled(true);
+
+ sCameraServiceProxy->setCameraDisabled(true);
+ for (auto s : statuses) {
+ sp<TestCameraDeviceCallbacks> callbacks = new TestCameraDeviceCallbacks();
+ sp<hardware::camera2::ICameraDeviceUser> device;
+ binder::Status status =
+ sCameraService->connectDevice(callbacks, String16(s.cameraId), String16(), {},
+ android::CameraService::USE_CALLING_UID, 0/*oomScoreDiff*/,
+ /*targetSdkVersion*/__ANDROID_API_FUTURE__, &device);
+ AutoDisconnectDevice autoDisconnect(device);
+ ASSERT_TRUE(!status.isOk()) << "connectDevice returned OK status";
+ ASSERT_EQ(status.serviceSpecificErrorCode(), hardware::ICameraService::ERROR_DISABLED)
+ << "connectDevice returned exception code " << status.exceptionCode();
+ }
+
+ sCameraServiceProxy->setCameraDisabled(false);
+ for (auto s : statuses) {
+ sp<TestCameraDeviceCallbacks> callbacks = new TestCameraDeviceCallbacks();
+ sp<hardware::camera2::ICameraDeviceUser> device;
+ binder::Status status =
+ sCameraService->connectDevice(callbacks, String16(s.cameraId), String16(), {},
+ android::CameraService::USE_CALLING_UID, 0/*oomScoreDiff*/,
+ /*targetSdkVersion*/__ANDROID_API_FUTURE__, &device);
+ AutoDisconnectDevice autoDisconnect(device);
+ ASSERT_TRUE(status.isOk());
+ }
+}
+
+// Test that consecutive camera connections succeed.
+TEST_F(CameraPermissionsTest, TestConsecutiveConnections) {
+ std::vector<hardware::CameraStatus> statuses;
+ sp<TestCameraServiceListener> serviceListener = new TestCameraServiceListener();
+ sCameraService->addListenerTest(serviceListener, &statuses);
+ sCameraServiceProxy->setOverrideCameraDisabled(false);
+
+ for (auto s : statuses) {
+ sp<TestCameraDeviceCallbacks> callbacks = new TestCameraDeviceCallbacks();
+ sp<hardware::camera2::ICameraDeviceUser> deviceA, deviceB;
+ binder::Status status =
+ sCameraService->connectDevice(callbacks, String16(s.cameraId), String16(), {},
+ android::CameraService::USE_CALLING_UID, 0/*oomScoreDiff*/,
+ /*targetSdkVersion*/__ANDROID_API_FUTURE__, &deviceA);
+ AutoDisconnectDevice autoDisconnectA(deviceA);
+ ASSERT_TRUE(status.isOk()) << "Exception code " << status.exceptionCode() <<
+ " service specific error code " << status.serviceSpecificErrorCode();
+ status =
+ sCameraService->connectDevice(callbacks, String16(s.cameraId), String16(), {},
+ android::CameraService::USE_CALLING_UID, 0/*oomScoreDiff*/,
+ /*targetSdkVersion*/__ANDROID_API_FUTURE__, &deviceB);
+ AutoDisconnectDevice autoDisconnectB(deviceB);
+ ASSERT_TRUE(status.isOk()) << "Exception code " << status.exceptionCode() <<
+ " service specific error code " << status.serviceSpecificErrorCode();
+ }
+}
+
+// Test that consecutive camera connections succeed even when a nonzero oomScoreOffset is provided
+// in the second call.
+TEST_F(CameraPermissionsTest, TestConflictingOomScoreOffset) {
+ std::vector<hardware::CameraStatus> statuses;
+ sp<TestCameraServiceListener> serviceListener = new TestCameraServiceListener();
+ sCameraService->addListenerTest(serviceListener, &statuses);
+ sCameraServiceProxy->setOverrideCameraDisabled(false);
+
+ for (auto s : statuses) {
+ sp<TestCameraDeviceCallbacks> callbacks = new TestCameraDeviceCallbacks();
+ sp<hardware::camera2::ICameraDeviceUser> deviceA, deviceB;
+ binder::Status status =
+ sCameraService->connectDevice(callbacks, String16(s.cameraId), String16(), {},
+ android::CameraService::USE_CALLING_UID, 0/*oomScoreDiff*/,
+ /*targetSdkVersion*/__ANDROID_API_FUTURE__, &deviceA);
+ AutoDisconnectDevice autoDisconnectA(deviceA);
+ ASSERT_TRUE(status.isOk()) << "Exception code " << status.exceptionCode() <<
+ " service specific error code " << status.serviceSpecificErrorCode();
+ status =
+ sCameraService->connectDevice(callbacks, String16(s.cameraId), String16(), {},
+ android::CameraService::USE_CALLING_UID, 1/*oomScoreDiff*/,
+ /*targetSdkVersion*/__ANDROID_API_FUTURE__, &deviceB);
+ AutoDisconnectDevice autoDisconnectB(deviceB);
+ ASSERT_TRUE(status.isOk()) << "Exception code " << status.exceptionCode() <<
+ " service specific error code " << status.serviceSpecificErrorCode();
+ }
+}