blob: 5e3a6d9d2416d733444f22c36aff69011c4094ba [file] [log] [blame]
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +01001/*
Biswarup Pal6152a302023-12-19 12:44:09 +00002 * Copyright 2023 The Android Open Source Project
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +01003 *
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
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +010018#define LOG_TAG "VirtualCameraService"
19#include "VirtualCameraService.h"
20
Jan Sebechlebsky773c0142024-03-25 12:17:05 +010021#include <algorithm>
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +010022#include <cinttypes>
23#include <cstdint>
24#include <cstdio>
Jan Sebechlebsky773c0142024-03-25 12:17:05 +010025#include <iterator>
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +010026#include <memory>
27#include <mutex>
Jan Sebechlebsky773c0142024-03-25 12:17:05 +010028#include <optional>
29#include <regex>
30#include <variant>
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +010031
32#include "VirtualCameraDevice.h"
33#include "VirtualCameraProvider.h"
Jan Sebechlebsky3b478c42023-11-23 13:15:56 +010034#include "aidl/android/companion/virtualcamera/Format.h"
Jan Sebechlebsky773c0142024-03-25 12:17:05 +010035#include "aidl/android/companion/virtualcamera/LensFacing.h"
Jan Sebechlebsky3b478c42023-11-23 13:15:56 +010036#include "aidl/android/companion/virtualcamera/VirtualCameraConfiguration.h"
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +010037#include "android/binder_auto_utils.h"
38#include "android/binder_libbinder.h"
Jan Sebechlebsky773c0142024-03-25 12:17:05 +010039#include "android/binder_status.h"
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +010040#include "binder/Status.h"
Jan Sebechlebskyde6f16f2023-11-29 09:27:36 +010041#include "util/Permissions.h"
Jan Sebechlebsky3b478c42023-11-23 13:15:56 +010042#include "util/Util.h"
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +010043
44using ::android::binder::Status;
45
46namespace android {
47namespace companion {
48namespace virtualcamera {
49
Jan Sebechlebsky3b478c42023-11-23 13:15:56 +010050using ::aidl::android::companion::virtualcamera::Format;
Biswarup Pal112458f2023-12-28 19:50:17 +000051using ::aidl::android::companion::virtualcamera::LensFacing;
Biswarup Pal6152a302023-12-19 12:44:09 +000052using ::aidl::android::companion::virtualcamera::SensorOrientation;
Jan Sebechlebsky3b478c42023-11-23 13:15:56 +010053using ::aidl::android::companion::virtualcamera::SupportedStreamConfiguration;
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +010054using ::aidl::android::companion::virtualcamera::VirtualCameraConfiguration;
55
Marvin Ramina8196132024-03-15 15:55:22 +000056// TODO(b/301023410) Make camera id range configurable / dynamic
57// based on already registered devices.
58std::atomic_int VirtualCameraService::sNextId{1000};
59
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +010060namespace {
61
Jan Sebechlebsky3b478c42023-11-23 13:15:56 +010062constexpr int kVgaWidth = 640;
63constexpr int kVgaHeight = 480;
Biswarup Pal6152a302023-12-19 12:44:09 +000064constexpr int kMaxFps = 60;
Biswarup Pal37a75182024-01-16 15:53:35 +000065constexpr int kDefaultDeviceId = 0;
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +010066constexpr char kEnableTestCameraCmd[] = "enable_test_camera";
67constexpr char kDisableTestCameraCmd[] = "disable_test_camera";
Jan Sebechlebsky773c0142024-03-25 12:17:05 +010068constexpr char kHelp[] = "help";
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +010069constexpr char kShellCmdHelp[] = R"(
Jan Sebechlebsky773c0142024-03-25 12:17:05 +010070Usage:
71 cmd virtual_camera command [--option=value]
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +010072Available commands:
73 * enable_test_camera
Jan Sebechlebsky773c0142024-03-25 12:17:05 +010074 Options:
75 --camera_id=(ID) - override numerical ID for test camera instance
76 --lens_facing=(front|back|external) - specifies lens facing for test camera instance
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +010077 * disable_test_camera
78)";
Jan Sebechlebskyde6f16f2023-11-29 09:27:36 +010079constexpr char kCreateVirtualDevicePermission[] =
80 "android.permission.CREATE_VIRTUAL_DEVICE";
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +010081
Jan Sebechlebsky3b478c42023-11-23 13:15:56 +010082ndk::ScopedAStatus validateConfiguration(
83 const VirtualCameraConfiguration& configuration) {
84 if (configuration.supportedStreamConfigs.empty()) {
85 ALOGE("%s: No supported input configuration specified", __func__);
86 return ndk::ScopedAStatus::fromServiceSpecificError(
87 Status::EX_ILLEGAL_ARGUMENT);
88 }
89
90 for (const SupportedStreamConfiguration& config :
91 configuration.supportedStreamConfigs) {
92 if (!isFormatSupportedForInput(config.width, config.height,
Biswarup Pal6152a302023-12-19 12:44:09 +000093 config.pixelFormat, config.maxFps)) {
Jan Sebechlebsky3b478c42023-11-23 13:15:56 +010094 ALOGE("%s: Requested unsupported input format: %d x %d (%d)", __func__,
95 config.width, config.height, static_cast<int>(config.pixelFormat));
96 return ndk::ScopedAStatus::fromServiceSpecificError(
97 Status::EX_ILLEGAL_ARGUMENT);
98 }
99 }
Biswarup Pal6152a302023-12-19 12:44:09 +0000100
101 if (configuration.sensorOrientation != SensorOrientation::ORIENTATION_0 &&
102 configuration.sensorOrientation != SensorOrientation::ORIENTATION_90 &&
103 configuration.sensorOrientation != SensorOrientation::ORIENTATION_180 &&
104 configuration.sensorOrientation != SensorOrientation::ORIENTATION_270) {
105 return ndk::ScopedAStatus::fromServiceSpecificError(
106 Status::EX_ILLEGAL_ARGUMENT);
107 }
108
Biswarup Pal112458f2023-12-28 19:50:17 +0000109 if (configuration.lensFacing != LensFacing::FRONT &&
110 configuration.lensFacing != LensFacing::BACK &&
111 configuration.lensFacing != LensFacing::EXTERNAL) {
112 return ndk::ScopedAStatus::fromServiceSpecificError(
113 Status::EX_ILLEGAL_ARGUMENT);
114 }
115
Jan Sebechlebsky3b478c42023-11-23 13:15:56 +0100116 return ndk::ScopedAStatus::ok();
117}
118
Jan Sebechlebsky773c0142024-03-25 12:17:05 +0100119enum class Command {
120 ENABLE_TEST_CAMERA,
121 DISABLE_TEST_CAMERA,
122 HELP,
123};
124
125struct CommandWithOptions {
126 Command command;
127 std::map<std::string, std::string> optionToValueMap;
128};
129
130std::optional<int> parseInt(const std::string& s) {
131 if (!std::all_of(s.begin(), s.end(), [](char c) { return std::isdigit(c); })) {
132 return std::nullopt;
133 }
134 int ret = atoi(s.c_str());
135 return ret > 0 ? std::optional(ret) : std::nullopt;
136}
137
138std::optional<LensFacing> parseLensFacing(const std::string& s) {
139 static const std::map<std::string, LensFacing> strToLensFacing{
140 {"front", LensFacing::FRONT},
141 {"back", LensFacing::BACK},
142 {"external", LensFacing::EXTERNAL}};
143 auto it = strToLensFacing.find(s);
144 return it == strToLensFacing.end() ? std::nullopt : std::optional(it->second);
145}
146
147std::variant<CommandWithOptions, std::string> parseCommand(
148 const char** args, const uint32_t numArgs) {
149 static const std::regex optionRegex("^--(\\w+)(?:=(.+))?$");
150 static const std::map<std::string, Command> strToCommand{
151 {kHelp, Command::HELP},
152 {kEnableTestCameraCmd, Command::ENABLE_TEST_CAMERA},
153 {kDisableTestCameraCmd, Command::DISABLE_TEST_CAMERA}};
154
155 if (numArgs < 1) {
156 return CommandWithOptions{.command = Command::HELP};
157 }
158
159 // We interpret the first argument as command;
160 auto it = strToCommand.find(args[0]);
161 if (it == strToCommand.end()) {
162 return "Unknown command: " + std::string(args[0]);
163 }
164
165 CommandWithOptions cmd{.command = it->second};
166
167 for (int i = 1; i < numArgs; i++) {
168 std::cmatch cm;
169 if (!std::regex_match(args[i], cm, optionRegex)) {
170 return "Not an option: " + std::string(args[i]);
171 }
172
173 cmd.optionToValueMap[cm[1]] = cm[2];
174 }
175
176 return cmd;
177};
178
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100179} // namespace
180
181VirtualCameraService::VirtualCameraService(
Jan Sebechlebskyde6f16f2023-11-29 09:27:36 +0100182 std::shared_ptr<VirtualCameraProvider> virtualCameraProvider,
183 const PermissionsProxy& permissionProxy)
184 : mVirtualCameraProvider(virtualCameraProvider),
185 mPermissionProxy(permissionProxy) {
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100186}
187
188ndk::ScopedAStatus VirtualCameraService::registerCamera(
189 const ::ndk::SpAIBinder& token,
Biswarup Pal37a75182024-01-16 15:53:35 +0000190 const VirtualCameraConfiguration& configuration, const int32_t deviceId,
191 bool* _aidl_return) {
192 return registerCamera(token, configuration, sNextId++, deviceId, _aidl_return);
Marvin Ramina8196132024-03-15 15:55:22 +0000193}
194
195ndk::ScopedAStatus VirtualCameraService::registerCamera(
196 const ::ndk::SpAIBinder& token,
197 const VirtualCameraConfiguration& configuration, const int cameraId,
Biswarup Pal37a75182024-01-16 15:53:35 +0000198 const int32_t deviceId, bool* _aidl_return) {
Jan Sebechlebskyde6f16f2023-11-29 09:27:36 +0100199 if (!mPermissionProxy.checkCallingPermission(kCreateVirtualDevicePermission)) {
200 ALOGE("%s: caller (pid %d, uid %d) doesn't hold %s permission", __func__,
201 getpid(), getuid(), kCreateVirtualDevicePermission);
202 return ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY);
203 }
204
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100205 if (_aidl_return == nullptr) {
206 return ndk::ScopedAStatus::fromServiceSpecificError(
207 Status::EX_ILLEGAL_ARGUMENT);
208 }
Jan Sebechlebskyde6f16f2023-11-29 09:27:36 +0100209
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100210 *_aidl_return = true;
211
Jan Sebechlebsky3b478c42023-11-23 13:15:56 +0100212 auto status = validateConfiguration(configuration);
213 if (!status.isOk()) {
214 *_aidl_return = false;
215 return status;
216 }
217
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100218 std::lock_guard lock(mLock);
219 if (mTokenToCameraName.find(token) != mTokenToCameraName.end()) {
220 ALOGE(
221 "Attempt to register camera corresponding to already registered binder "
222 "token: "
223 "0x%" PRIxPTR,
224 reinterpret_cast<uintptr_t>(token.get()));
225 *_aidl_return = false;
Jan Sebechlebsky3b478c42023-11-23 13:15:56 +0100226 return ndk::ScopedAStatus::ok();
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100227 }
228
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100229 std::shared_ptr<VirtualCameraDevice> camera =
Biswarup Pal37a75182024-01-16 15:53:35 +0000230 mVirtualCameraProvider->createCamera(configuration, cameraId, deviceId);
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100231 if (camera == nullptr) {
232 ALOGE("Failed to create camera for binder token 0x%" PRIxPTR,
233 reinterpret_cast<uintptr_t>(token.get()));
234 *_aidl_return = false;
235 return ndk::ScopedAStatus::fromServiceSpecificError(
236 Status::EX_SERVICE_SPECIFIC);
237 }
238
239 mTokenToCameraName[token] = camera->getCameraName();
240 return ndk::ScopedAStatus::ok();
241}
242
243ndk::ScopedAStatus VirtualCameraService::unregisterCamera(
244 const ::ndk::SpAIBinder& token) {
Jan Sebechlebskyde6f16f2023-11-29 09:27:36 +0100245 if (!mPermissionProxy.checkCallingPermission(kCreateVirtualDevicePermission)) {
246 ALOGE("%s: caller (pid %d, uid %d) doesn't hold %s permission", __func__,
247 getpid(), getuid(), kCreateVirtualDevicePermission);
248 return ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY);
249 }
250
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100251 std::lock_guard lock(mLock);
252
253 auto it = mTokenToCameraName.find(token);
254 if (it == mTokenToCameraName.end()) {
255 ALOGE(
256 "Attempt to unregister camera corresponding to unknown binder token: "
257 "0x%" PRIxPTR,
258 reinterpret_cast<uintptr_t>(token.get()));
259 return ndk::ScopedAStatus::ok();
260 }
261
262 mVirtualCameraProvider->removeCamera(it->second);
263
Tony Guo6cbe11b2024-03-17 02:34:23 +0000264 mTokenToCameraName.erase(it);
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100265 return ndk::ScopedAStatus::ok();
266}
267
Biswarup Pal68137fc2023-11-24 18:06:54 +0000268ndk::ScopedAStatus VirtualCameraService::getCameraId(
Marvin Ramina8196132024-03-15 15:55:22 +0000269 const ::ndk::SpAIBinder& token, int32_t* _aidl_return) {
Jan Sebechlebskyde6f16f2023-11-29 09:27:36 +0100270 if (!mPermissionProxy.checkCallingPermission(kCreateVirtualDevicePermission)) {
271 ALOGE("%s: caller (pid %d, uid %d) doesn't hold %s permission", __func__,
272 getpid(), getuid(), kCreateVirtualDevicePermission);
273 return ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY);
274 }
275
Biswarup Pal68137fc2023-11-24 18:06:54 +0000276 if (_aidl_return == nullptr) {
277 return ndk::ScopedAStatus::fromServiceSpecificError(
Marvin Ramina8196132024-03-15 15:55:22 +0000278 Status::EX_ILLEGAL_ARGUMENT);
Biswarup Pal68137fc2023-11-24 18:06:54 +0000279 }
280
281 auto camera = getCamera(token);
282 if (camera == nullptr) {
283 ALOGE(
284 "Attempt to get camera id corresponding to unknown binder token: "
285 "0x%" PRIxPTR,
286 reinterpret_cast<uintptr_t>(token.get()));
287 return ndk::ScopedAStatus::ok();
288 }
289
290 *_aidl_return = camera->getCameraId();
291
292 return ndk::ScopedAStatus::ok();
293}
294
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100295std::shared_ptr<VirtualCameraDevice> VirtualCameraService::getCamera(
296 const ::ndk::SpAIBinder& token) {
297 if (token == nullptr) {
298 return nullptr;
299 }
300
301 std::lock_guard lock(mLock);
302 auto it = mTokenToCameraName.find(token);
303 if (it == mTokenToCameraName.end()) {
304 return nullptr;
305 }
306
307 return mVirtualCameraProvider->getCamera(it->second);
308}
309
Jan Sebechlebsky773c0142024-03-25 12:17:05 +0100310binder_status_t VirtualCameraService::handleShellCommand(int, int out, int err,
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100311 const char** args,
312 uint32_t numArgs) {
313 if (numArgs <= 0) {
314 dprintf(out, kShellCmdHelp);
Jan Sebechlebsky76d7e212023-11-28 14:10:25 +0100315 fsync(out);
316 return STATUS_OK;
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100317 }
318
Jan Sebechlebsky773c0142024-03-25 12:17:05 +0100319 auto isNullptr = [](const char* ptr) { return ptr == nullptr; };
320 if (args == nullptr || std::any_of(args, args + numArgs, isNullptr)) {
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100321 return STATUS_BAD_VALUE;
322 }
Marvin Ramina8196132024-03-15 15:55:22 +0000323
Jan Sebechlebsky773c0142024-03-25 12:17:05 +0100324 std::variant<CommandWithOptions, std::string> cmdOrErrorMessage =
325 parseCommand(args, numArgs);
326 if (std::holds_alternative<std::string>(cmdOrErrorMessage)) {
327 dprintf(err, "Error: %s\n",
328 std::get<std::string>(cmdOrErrorMessage).c_str());
329 return STATUS_BAD_VALUE;
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100330 }
331
Jan Sebechlebsky773c0142024-03-25 12:17:05 +0100332 const CommandWithOptions& cmd =
333 std::get<CommandWithOptions>(cmdOrErrorMessage);
334 binder_status_t status = STATUS_OK;
335 switch (cmd.command) {
336 case Command::HELP:
337 dprintf(out, kShellCmdHelp);
338 break;
339 case Command::ENABLE_TEST_CAMERA:
340 status = enableTestCameraCmd(out, err, cmd.optionToValueMap);
341 break;
342 case Command::DISABLE_TEST_CAMERA:
343 disableTestCameraCmd(out);
344 break;
345 }
346
347 fsync(err);
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100348 fsync(out);
Jan Sebechlebsky773c0142024-03-25 12:17:05 +0100349 return status;
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100350}
351
Jan Sebechlebsky773c0142024-03-25 12:17:05 +0100352binder_status_t VirtualCameraService::enableTestCameraCmd(
353 const int out, const int err,
354 const std::map<std::string, std::string>& options) {
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100355 if (mTestCameraToken != nullptr) {
Jan Sebechlebsky773c0142024-03-25 12:17:05 +0100356 dprintf(out, "Test camera is already enabled (%s).\n",
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100357 getCamera(mTestCameraToken)->getCameraName().c_str());
Jan Sebechlebsky773c0142024-03-25 12:17:05 +0100358 return STATUS_OK;
359 }
360
361 std::optional<int> cameraId;
362 auto it = options.find("camera_id");
363 if (it != options.end()) {
364 cameraId = parseInt(it->second);
365 if (!cameraId.has_value()) {
366 dprintf(err, "Invalid camera_id: %s\n, must be number > 0",
367 it->second.c_str());
368 return STATUS_BAD_VALUE;
369 }
370 }
371
372 std::optional<LensFacing> lensFacing;
373 it = options.find("lens_facing");
374 if (it != options.end()) {
375 lensFacing = parseLensFacing(it->second);
376 if (!lensFacing.has_value()) {
377 dprintf(err, "Invalid lens_facing: %s\n, must be front|back|external",
378 it->second.c_str());
379 return STATUS_BAD_VALUE;
380 }
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100381 }
382
383 sp<BBinder> token = sp<BBinder>::make();
384 mTestCameraToken.set(AIBinder_fromPlatformBinder(token));
385
386 bool ret;
Jan Sebechlebsky3b478c42023-11-23 13:15:56 +0100387 VirtualCameraConfiguration configuration;
Biswarup Pal6152a302023-12-19 12:44:09 +0000388 configuration.supportedStreamConfigs.push_back({.width = kVgaWidth,
389 .height = kVgaHeight,
390 Format::YUV_420_888,
391 .maxFps = kMaxFps});
Jan Sebechlebsky773c0142024-03-25 12:17:05 +0100392 configuration.lensFacing = lensFacing.value_or(LensFacing::EXTERNAL);
393 registerCamera(mTestCameraToken, configuration, cameraId.value_or(sNextId++),
Biswarup Pal37a75182024-01-16 15:53:35 +0000394 kDefaultDeviceId, &ret);
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100395 if (ret) {
Jan Sebechlebsky773c0142024-03-25 12:17:05 +0100396 dprintf(out, "Successfully registered test camera %s\n",
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100397 getCamera(mTestCameraToken)->getCameraName().c_str());
398 } else {
Jan Sebechlebsky773c0142024-03-25 12:17:05 +0100399 dprintf(err, "Failed to create test camera\n");
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100400 }
Jan Sebechlebsky773c0142024-03-25 12:17:05 +0100401 return STATUS_OK;
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100402}
403
404void VirtualCameraService::disableTestCameraCmd(const int out) {
405 if (mTestCameraToken == nullptr) {
406 dprintf(out, "Test camera is not registered.");
407 }
408 unregisterCamera(mTestCameraToken);
409 mTestCameraToken.set(nullptr);
410}
411
412} // namespace virtualcamera
413} // namespace companion
414} // namespace android