1 /*
2 * Copyright 2023 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 // #define LOG_NDEBUG 0
18 #define LOG_TAG "VirtualCameraService"
19 #include "VirtualCameraService.h"
20
21 #include <algorithm>
22 #include <array>
23 #include <cinttypes>
24 #include <cstdint>
25 #include <memory>
26 #include <mutex>
27 #include <optional>
28 #include <regex>
29 #include <variant>
30
31 #include "VirtualCameraDevice.h"
32 #include "VirtualCameraProvider.h"
33 #include "VirtualCameraTestInstance.h"
34 #include "aidl/android/companion/virtualcamera/Format.h"
35 #include "aidl/android/companion/virtualcamera/LensFacing.h"
36 #include "aidl/android/companion/virtualcamera/VirtualCameraConfiguration.h"
37 #include "android/binder_auto_utils.h"
38 #include "android/binder_interface_utils.h"
39 #include "android/binder_libbinder.h"
40 #include "android/binder_status.h"
41 #include "binder/Status.h"
42 #include "fmt/format.h"
43 #include "util/EglDisplayContext.h"
44 #include "util/EglUtil.h"
45 #include "util/Permissions.h"
46 #include "util/Util.h"
47
48 using ::android::binder::Status;
49
50 namespace android {
51 namespace companion {
52 namespace virtualcamera {
53
54 using ::aidl::android::companion::virtualcamera::Format;
55 using ::aidl::android::companion::virtualcamera::LensFacing;
56 using ::aidl::android::companion::virtualcamera::SensorOrientation;
57 using ::aidl::android::companion::virtualcamera::SupportedStreamConfiguration;
58 using ::aidl::android::companion::virtualcamera::VirtualCameraConfiguration;
59
60 namespace {
61
62 constexpr char kCameraIdPrefix[] = "v";
63 constexpr int kVgaWidth = 640;
64 constexpr int kVgaHeight = 480;
65 constexpr int kMaxFps = 60;
66 constexpr int kTestCameraDefaultInputFps = 30;
67 constexpr char kEnableTestCameraCmd[] = "enable_test_camera";
68 constexpr char kDisableTestCameraCmd[] = "disable_test_camera";
69 constexpr char kHelp[] = "help";
70 constexpr char kShellCmdHelp[] = R"(
71 Usage:
72 cmd virtual_camera command [--option=value]
73 Available commands:
74 * enable_test_camera
75 Options:
76 --camera_id=(ID) - override numerical ID for test camera instance
77 --lens_facing=(front|back|external) - specifies lens facing for test camera instance
78 --input_fps=(fps) - specify input fps for test camera, valid values are from 1 to 1000
79 --sensor_orientation=(0|90|180|270) - Clockwise angle through which the output image
80 needs to be rotated to be upright on the device screen in its native orientation
81 * disable_test_camera
82 )";
83 constexpr char kCreateVirtualDevicePermission[] =
84 "android.permission.CREATE_VIRTUAL_DEVICE";
85
86 constexpr std::array<const char*, 3> kRequiredEglExtensions = {
87 "GL_OES_EGL_image_external",
88 "GL_OES_EGL_image_external_essl3",
89 "GL_EXT_YUV_target",
90 };
91
92 // Numerical portion for id to assign to next created camera.
93 static std::atomic_int sNextIdNumericalPortion{1000};
94
validateConfiguration(const VirtualCameraConfiguration & configuration)95 ndk::ScopedAStatus validateConfiguration(
96 const VirtualCameraConfiguration& configuration) {
97 if (configuration.supportedStreamConfigs.empty()) {
98 ALOGE("%s: No supported input configuration specified", __func__);
99 return ndk::ScopedAStatus::fromServiceSpecificError(
100 Status::EX_ILLEGAL_ARGUMENT);
101 }
102
103 if (configuration.virtualCameraCallback == nullptr) {
104 ALOGE("%s: Input configuration is missing virtual camera callback",
105 __func__);
106 return ndk::ScopedAStatus::fromServiceSpecificError(
107 Status::EX_ILLEGAL_ARGUMENT);
108 }
109
110 for (const SupportedStreamConfiguration& config :
111 configuration.supportedStreamConfigs) {
112 if (!isFormatSupportedForInput(config.width, config.height,
113 config.pixelFormat, config.maxFps)) {
114 ALOGE("%s: Requested unsupported input format: %d x %d (%d)", __func__,
115 config.width, config.height, static_cast<int>(config.pixelFormat));
116 return ndk::ScopedAStatus::fromServiceSpecificError(
117 Status::EX_ILLEGAL_ARGUMENT);
118 }
119 }
120
121 if (configuration.sensorOrientation != SensorOrientation::ORIENTATION_0 &&
122 configuration.sensorOrientation != SensorOrientation::ORIENTATION_90 &&
123 configuration.sensorOrientation != SensorOrientation::ORIENTATION_180 &&
124 configuration.sensorOrientation != SensorOrientation::ORIENTATION_270) {
125 return ndk::ScopedAStatus::fromServiceSpecificError(
126 Status::EX_ILLEGAL_ARGUMENT);
127 }
128
129 if (configuration.lensFacing != LensFacing::FRONT &&
130 configuration.lensFacing != LensFacing::BACK &&
131 configuration.lensFacing != LensFacing::EXTERNAL) {
132 return ndk::ScopedAStatus::fromServiceSpecificError(
133 Status::EX_ILLEGAL_ARGUMENT);
134 }
135
136 return ndk::ScopedAStatus::ok();
137 }
138
139 enum class Command {
140 ENABLE_TEST_CAMERA,
141 DISABLE_TEST_CAMERA,
142 HELP,
143 };
144
145 struct CommandWithOptions {
146 Command command;
147 std::map<std::string, std::string> optionToValueMap;
148 };
149
parseInt(const std::string & s)150 std::optional<int> parseInt(const std::string& s) {
151 if (!std::all_of(s.begin(), s.end(), [](char c) { return std::isdigit(c); })) {
152 return std::nullopt;
153 }
154 int ret = atoi(s.c_str());
155 return ret > 0 ? std::optional(ret) : std::nullopt;
156 }
157
parseLensFacing(const std::string & s)158 std::optional<LensFacing> parseLensFacing(const std::string& s) {
159 static const std::map<std::string, LensFacing> strToLensFacing{
160 {"front", LensFacing::FRONT},
161 {"back", LensFacing::BACK},
162 {"external", LensFacing::EXTERNAL}};
163 auto it = strToLensFacing.find(s);
164 return it == strToLensFacing.end() ? std::nullopt : std::optional(it->second);
165 }
166
parseCommand(const char ** args,const uint32_t numArgs)167 std::variant<CommandWithOptions, std::string> parseCommand(
168 const char** args, const uint32_t numArgs) {
169 static const std::regex optionRegex("^--(\\w+)(?:=(.+))?$");
170 static const std::map<std::string, Command> strToCommand{
171 {kHelp, Command::HELP},
172 {kEnableTestCameraCmd, Command::ENABLE_TEST_CAMERA},
173 {kDisableTestCameraCmd, Command::DISABLE_TEST_CAMERA}};
174
175 if (numArgs < 1) {
176 return CommandWithOptions{.command = Command::HELP};
177 }
178
179 // We interpret the first argument as command;
180 auto it = strToCommand.find(args[0]);
181 if (it == strToCommand.end()) {
182 return "Unknown command: " + std::string(args[0]);
183 }
184
185 CommandWithOptions cmd{.command = it->second};
186
187 for (int i = 1; i < numArgs; i++) {
188 std::cmatch cm;
189 if (!std::regex_match(args[i], cm, optionRegex)) {
190 return "Not an option: " + std::string(args[i]);
191 }
192
193 cmd.optionToValueMap[cm[1]] = cm[2];
194 }
195
196 return cmd;
197 }
198
verifyRequiredEglExtensions()199 ndk::ScopedAStatus verifyRequiredEglExtensions() {
200 EglDisplayContext context;
201 for (const char* eglExtension : kRequiredEglExtensions) {
202 if (!isGlExtensionSupported(eglExtension)) {
203 ALOGE("%s not supported", eglExtension);
204 return ndk::ScopedAStatus::fromExceptionCodeWithMessage(
205 EX_UNSUPPORTED_OPERATION,
206 fmt::format(
207 "Cannot create virtual camera, because required EGL extension {} "
208 "is not supported on this system",
209 eglExtension)
210 .c_str());
211 }
212 }
213 return ndk::ScopedAStatus::ok();
214 }
215
createCameraId(const int32_t deviceId)216 std::string createCameraId(const int32_t deviceId) {
217 return kCameraIdPrefix + std::to_string(deviceId) + "_" +
218 std::to_string(sNextIdNumericalPortion++);
219 }
220
221 } // namespace
222
VirtualCameraService(std::shared_ptr<VirtualCameraProvider> virtualCameraProvider,const PermissionsProxy & permissionProxy)223 VirtualCameraService::VirtualCameraService(
224 std::shared_ptr<VirtualCameraProvider> virtualCameraProvider,
225 const PermissionsProxy& permissionProxy)
226 : mVirtualCameraProvider(virtualCameraProvider),
227 mPermissionProxy(permissionProxy) {
228 }
229
registerCamera(const::ndk::SpAIBinder & token,const VirtualCameraConfiguration & configuration,const int32_t deviceId,bool * _aidl_return)230 ndk::ScopedAStatus VirtualCameraService::registerCamera(
231 const ::ndk::SpAIBinder& token,
232 const VirtualCameraConfiguration& configuration, const int32_t deviceId,
233 bool* _aidl_return) {
234 return registerCamera(token, configuration, createCameraId(deviceId),
235 deviceId, _aidl_return);
236 }
237
registerCamera(const::ndk::SpAIBinder & token,const VirtualCameraConfiguration & configuration,const std::string & cameraId,const int32_t deviceId,bool * _aidl_return)238 ndk::ScopedAStatus VirtualCameraService::registerCamera(
239 const ::ndk::SpAIBinder& token,
240 const VirtualCameraConfiguration& configuration,
241 const std::string& cameraId, const int32_t deviceId, bool* _aidl_return) {
242 if (!mPermissionProxy.checkCallingPermission(kCreateVirtualDevicePermission)) {
243 ALOGE("%s: caller (pid %d, uid %d) doesn't hold %s permission", __func__,
244 getpid(), getuid(), kCreateVirtualDevicePermission);
245 return ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY);
246 }
247
248 if (_aidl_return == nullptr) {
249 return ndk::ScopedAStatus::fromServiceSpecificError(
250 Status::EX_ILLEGAL_ARGUMENT);
251 }
252
253 if (mVerifyEglExtensions) {
254 auto status = verifyRequiredEglExtensions();
255 if (!status.isOk()) {
256 *_aidl_return = false;
257 return status;
258 }
259 }
260
261 auto status = validateConfiguration(configuration);
262 if (!status.isOk()) {
263 *_aidl_return = false;
264 return status;
265 }
266
267 std::lock_guard lock(mLock);
268 if (mTokenToCameraName.find(token) != mTokenToCameraName.end()) {
269 ALOGE(
270 "Attempt to register camera corresponding to already registered binder "
271 "token: "
272 "0x%" PRIxPTR,
273 reinterpret_cast<uintptr_t>(token.get()));
274 *_aidl_return = false;
275 return ndk::ScopedAStatus::ok();
276 }
277
278 std::shared_ptr<VirtualCameraDevice> camera =
279 mVirtualCameraProvider->createCamera(configuration, cameraId, deviceId);
280 if (camera == nullptr) {
281 ALOGE("Failed to create camera for binder token 0x%" PRIxPTR,
282 reinterpret_cast<uintptr_t>(token.get()));
283 *_aidl_return = false;
284 return ndk::ScopedAStatus::fromServiceSpecificError(
285 Status::EX_SERVICE_SPECIFIC);
286 }
287
288 mTokenToCameraName[token] = camera->getCameraName();
289 *_aidl_return = true;
290 return ndk::ScopedAStatus::ok();
291 }
292
unregisterCamera(const::ndk::SpAIBinder & token)293 ndk::ScopedAStatus VirtualCameraService::unregisterCamera(
294 const ::ndk::SpAIBinder& token) {
295 if (!mPermissionProxy.checkCallingPermission(kCreateVirtualDevicePermission)) {
296 ALOGE("%s: caller (pid %d, uid %d) doesn't hold %s permission", __func__,
297 getpid(), getuid(), kCreateVirtualDevicePermission);
298 return ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY);
299 }
300
301 std::lock_guard lock(mLock);
302
303 auto it = mTokenToCameraName.find(token);
304 if (it == mTokenToCameraName.end()) {
305 ALOGE(
306 "Attempt to unregister camera corresponding to unknown binder token: "
307 "0x%" PRIxPTR,
308 reinterpret_cast<uintptr_t>(token.get()));
309 return ndk::ScopedAStatus::ok();
310 }
311
312 mVirtualCameraProvider->removeCamera(it->second);
313
314 mTokenToCameraName.erase(it);
315 return ndk::ScopedAStatus::ok();
316 }
317
getCameraId(const::ndk::SpAIBinder & token,std::string * _aidl_return)318 ndk::ScopedAStatus VirtualCameraService::getCameraId(
319 const ::ndk::SpAIBinder& token, std::string* _aidl_return) {
320 if (!mPermissionProxy.checkCallingPermission(kCreateVirtualDevicePermission)) {
321 ALOGE("%s: caller (pid %d, uid %d) doesn't hold %s permission", __func__,
322 getpid(), getuid(), kCreateVirtualDevicePermission);
323 return ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY);
324 }
325
326 if (_aidl_return == nullptr) {
327 return ndk::ScopedAStatus::fromServiceSpecificError(
328 Status::EX_ILLEGAL_ARGUMENT);
329 }
330
331 auto camera = getCamera(token);
332 if (camera == nullptr) {
333 ALOGE(
334 "Attempt to get camera id corresponding to unknown binder token: "
335 "0x%" PRIxPTR,
336 reinterpret_cast<uintptr_t>(token.get()));
337 return ndk::ScopedAStatus::ok();
338 }
339
340 *_aidl_return = camera->getCameraId();
341
342 return ndk::ScopedAStatus::ok();
343 }
344
getCamera(const::ndk::SpAIBinder & token)345 std::shared_ptr<VirtualCameraDevice> VirtualCameraService::getCamera(
346 const ::ndk::SpAIBinder& token) {
347 if (token == nullptr) {
348 return nullptr;
349 }
350
351 std::lock_guard lock(mLock);
352 auto it = mTokenToCameraName.find(token);
353 if (it == mTokenToCameraName.end()) {
354 return nullptr;
355 }
356
357 return mVirtualCameraProvider->getCamera(it->second);
358 }
359
handleShellCommand(int,int out,int err,const char ** args,uint32_t numArgs)360 binder_status_t VirtualCameraService::handleShellCommand(int, int out, int err,
361 const char** args,
362 uint32_t numArgs) {
363 if (numArgs <= 0) {
364 dprintf(out, kShellCmdHelp);
365 fsync(out);
366 return STATUS_OK;
367 }
368
369 auto isNullptr = [](const char* ptr) { return ptr == nullptr; };
370 if (args == nullptr || std::any_of(args, args + numArgs, isNullptr)) {
371 return STATUS_BAD_VALUE;
372 }
373
374 std::variant<CommandWithOptions, std::string> cmdOrErrorMessage =
375 parseCommand(args, numArgs);
376 if (std::holds_alternative<std::string>(cmdOrErrorMessage)) {
377 dprintf(err, "Error: %s\n",
378 std::get<std::string>(cmdOrErrorMessage).c_str());
379 return STATUS_BAD_VALUE;
380 }
381
382 const CommandWithOptions& cmd =
383 std::get<CommandWithOptions>(cmdOrErrorMessage);
384 binder_status_t status = STATUS_OK;
385 switch (cmd.command) {
386 case Command::HELP:
387 dprintf(out, kShellCmdHelp);
388 break;
389 case Command::ENABLE_TEST_CAMERA:
390 status = enableTestCameraCmd(out, err, cmd.optionToValueMap);
391 break;
392 case Command::DISABLE_TEST_CAMERA:
393 disableTestCameraCmd(out);
394 break;
395 }
396
397 fsync(err);
398 fsync(out);
399 return status;
400 }
401
enableTestCameraCmd(const int out,const int err,const std::map<std::string,std::string> & options)402 binder_status_t VirtualCameraService::enableTestCameraCmd(
403 const int out, const int err,
404 const std::map<std::string, std::string>& options) {
405 if (mTestCameraToken != nullptr) {
406 dprintf(out, "Test camera is already enabled (%s).\n",
407 getCamera(mTestCameraToken)->getCameraName().c_str());
408 return STATUS_OK;
409 }
410
411 std::optional<std::string> cameraId;
412 auto it = options.find("camera_id");
413 if (it != options.end()) {
414 cameraId = it->second;
415 if (!cameraId.has_value()) {
416 dprintf(err, "Invalid camera_id: %s", it->second.c_str());
417 return STATUS_BAD_VALUE;
418 }
419 }
420
421 std::optional<LensFacing> lensFacing;
422 it = options.find("lens_facing");
423 if (it != options.end()) {
424 lensFacing = parseLensFacing(it->second);
425 if (!lensFacing.has_value()) {
426 dprintf(err, "Invalid lens_facing: %s\n, must be front|back|external",
427 it->second.c_str());
428 return STATUS_BAD_VALUE;
429 }
430 }
431
432 std::optional<int> inputFps;
433 it = options.find("input_fps");
434 if (it != options.end()) {
435 inputFps = parseInt(it->second);
436 if (!inputFps.has_value() || inputFps.value() < 1 ||
437 inputFps.value() > 1000) {
438 dprintf(err, "Invalid input fps: %s\n, must be integer in <1,1000> range.",
439 it->second.c_str());
440 return STATUS_BAD_VALUE;
441 }
442 }
443
444 std::optional<SensorOrientation> sensorOrientation;
445 std::optional<int> sensorOrientationInt;
446 it = options.find("sensor_orientation");
447 if (it != options.end()) {
448 sensorOrientationInt = parseInt(it->second);
449 switch (sensorOrientationInt.value_or(0)) {
450 case 0:
451 sensorOrientation = SensorOrientation::ORIENTATION_0;
452 break;
453 case 90:
454 sensorOrientation = SensorOrientation::ORIENTATION_90;
455 break;
456 case 180:
457 sensorOrientation = SensorOrientation::ORIENTATION_180;
458 break;
459 case 270:
460 sensorOrientation = SensorOrientation::ORIENTATION_270;
461 break;
462 default:
463 dprintf(err, "Invalid sensor rotation: %s\n, must be 0, 90, 180 or 270.",
464 it->second.c_str());
465 return STATUS_BAD_VALUE;
466 }
467 }
468
469 sp<BBinder> token = sp<BBinder>::make();
470 mTestCameraToken.set(AIBinder_fromPlatformBinder(token));
471
472 bool ret;
473 VirtualCameraConfiguration configuration;
474 configuration.supportedStreamConfigs.push_back({.width = kVgaWidth,
475 .height = kVgaHeight,
476 Format::RGBA_8888,
477 .maxFps = kMaxFps});
478 configuration.lensFacing = lensFacing.value_or(LensFacing::EXTERNAL);
479 configuration.sensorOrientation =
480 sensorOrientation.value_or(SensorOrientation::ORIENTATION_0);
481 configuration.virtualCameraCallback =
482 ndk::SharedRefBase::make<VirtualCameraTestInstance>(
483 inputFps.value_or(kTestCameraDefaultInputFps));
484 registerCamera(mTestCameraToken, configuration,
485 cameraId.value_or(std::to_string(sNextIdNumericalPortion++)),
486 kDefaultDeviceId, &ret);
487 if (ret) {
488 dprintf(out, "Successfully registered test camera %s\n",
489 getCamera(mTestCameraToken)->getCameraName().c_str());
490 } else {
491 dprintf(err, "Failed to create test camera\n");
492 }
493 return STATUS_OK;
494 }
495
disableTestCameraCmd(const int out)496 void VirtualCameraService::disableTestCameraCmd(const int out) {
497 if (mTestCameraToken == nullptr) {
498 dprintf(out, "Test camera is not registered.");
499 }
500 unregisterCamera(mTestCameraToken);
501 mTestCameraToken.set(nullptr);
502 }
503
504 } // namespace virtualcamera
505 } // namespace companion
506 } // namespace android
507