blob: 27873e7a2f13a4b9503f79a15d335b14ceb8e913 [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;
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +010065constexpr char kEnableTestCameraCmd[] = "enable_test_camera";
66constexpr char kDisableTestCameraCmd[] = "disable_test_camera";
Jan Sebechlebsky773c0142024-03-25 12:17:05 +010067constexpr char kHelp[] = "help";
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +010068constexpr char kShellCmdHelp[] = R"(
Jan Sebechlebsky773c0142024-03-25 12:17:05 +010069Usage:
70 cmd virtual_camera command [--option=value]
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +010071Available commands:
72 * enable_test_camera
Jan Sebechlebsky773c0142024-03-25 12:17:05 +010073 Options:
74 --camera_id=(ID) - override numerical ID for test camera instance
75 --lens_facing=(front|back|external) - specifies lens facing for test camera instance
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +010076 * disable_test_camera
77)";
Jan Sebechlebskyde6f16f2023-11-29 09:27:36 +010078constexpr char kCreateVirtualDevicePermission[] =
79 "android.permission.CREATE_VIRTUAL_DEVICE";
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +010080
Jan Sebechlebsky3b478c42023-11-23 13:15:56 +010081ndk::ScopedAStatus validateConfiguration(
82 const VirtualCameraConfiguration& configuration) {
83 if (configuration.supportedStreamConfigs.empty()) {
84 ALOGE("%s: No supported input configuration specified", __func__);
85 return ndk::ScopedAStatus::fromServiceSpecificError(
86 Status::EX_ILLEGAL_ARGUMENT);
87 }
88
89 for (const SupportedStreamConfiguration& config :
90 configuration.supportedStreamConfigs) {
91 if (!isFormatSupportedForInput(config.width, config.height,
Biswarup Pal6152a302023-12-19 12:44:09 +000092 config.pixelFormat, config.maxFps)) {
Jan Sebechlebsky3b478c42023-11-23 13:15:56 +010093 ALOGE("%s: Requested unsupported input format: %d x %d (%d)", __func__,
94 config.width, config.height, static_cast<int>(config.pixelFormat));
95 return ndk::ScopedAStatus::fromServiceSpecificError(
96 Status::EX_ILLEGAL_ARGUMENT);
97 }
98 }
Biswarup Pal6152a302023-12-19 12:44:09 +000099
100 if (configuration.sensorOrientation != SensorOrientation::ORIENTATION_0 &&
101 configuration.sensorOrientation != SensorOrientation::ORIENTATION_90 &&
102 configuration.sensorOrientation != SensorOrientation::ORIENTATION_180 &&
103 configuration.sensorOrientation != SensorOrientation::ORIENTATION_270) {
104 return ndk::ScopedAStatus::fromServiceSpecificError(
105 Status::EX_ILLEGAL_ARGUMENT);
106 }
107
Biswarup Pal112458f2023-12-28 19:50:17 +0000108 if (configuration.lensFacing != LensFacing::FRONT &&
109 configuration.lensFacing != LensFacing::BACK &&
110 configuration.lensFacing != LensFacing::EXTERNAL) {
111 return ndk::ScopedAStatus::fromServiceSpecificError(
112 Status::EX_ILLEGAL_ARGUMENT);
113 }
114
Jan Sebechlebsky3b478c42023-11-23 13:15:56 +0100115 return ndk::ScopedAStatus::ok();
116}
117
Jan Sebechlebsky773c0142024-03-25 12:17:05 +0100118enum class Command {
119 ENABLE_TEST_CAMERA,
120 DISABLE_TEST_CAMERA,
121 HELP,
122};
123
124struct CommandWithOptions {
125 Command command;
126 std::map<std::string, std::string> optionToValueMap;
127};
128
129std::optional<int> parseInt(const std::string& s) {
130 if (!std::all_of(s.begin(), s.end(), [](char c) { return std::isdigit(c); })) {
131 return std::nullopt;
132 }
133 int ret = atoi(s.c_str());
134 return ret > 0 ? std::optional(ret) : std::nullopt;
135}
136
137std::optional<LensFacing> parseLensFacing(const std::string& s) {
138 static const std::map<std::string, LensFacing> strToLensFacing{
139 {"front", LensFacing::FRONT},
140 {"back", LensFacing::BACK},
141 {"external", LensFacing::EXTERNAL}};
142 auto it = strToLensFacing.find(s);
143 return it == strToLensFacing.end() ? std::nullopt : std::optional(it->second);
144}
145
146std::variant<CommandWithOptions, std::string> parseCommand(
147 const char** args, const uint32_t numArgs) {
148 static const std::regex optionRegex("^--(\\w+)(?:=(.+))?$");
149 static const std::map<std::string, Command> strToCommand{
150 {kHelp, Command::HELP},
151 {kEnableTestCameraCmd, Command::ENABLE_TEST_CAMERA},
152 {kDisableTestCameraCmd, Command::DISABLE_TEST_CAMERA}};
153
154 if (numArgs < 1) {
155 return CommandWithOptions{.command = Command::HELP};
156 }
157
158 // We interpret the first argument as command;
159 auto it = strToCommand.find(args[0]);
160 if (it == strToCommand.end()) {
161 return "Unknown command: " + std::string(args[0]);
162 }
163
164 CommandWithOptions cmd{.command = it->second};
165
166 for (int i = 1; i < numArgs; i++) {
167 std::cmatch cm;
168 if (!std::regex_match(args[i], cm, optionRegex)) {
169 return "Not an option: " + std::string(args[i]);
170 }
171
172 cmd.optionToValueMap[cm[1]] = cm[2];
173 }
174
175 return cmd;
176};
177
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100178} // namespace
179
180VirtualCameraService::VirtualCameraService(
Jan Sebechlebskyde6f16f2023-11-29 09:27:36 +0100181 std::shared_ptr<VirtualCameraProvider> virtualCameraProvider,
182 const PermissionsProxy& permissionProxy)
183 : mVirtualCameraProvider(virtualCameraProvider),
184 mPermissionProxy(permissionProxy) {
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100185}
186
187ndk::ScopedAStatus VirtualCameraService::registerCamera(
188 const ::ndk::SpAIBinder& token,
189 const VirtualCameraConfiguration& configuration, bool* _aidl_return) {
Marvin Ramina8196132024-03-15 15:55:22 +0000190 return registerCamera(token, configuration, sNextId++, _aidl_return);
191}
192
193ndk::ScopedAStatus VirtualCameraService::registerCamera(
194 const ::ndk::SpAIBinder& token,
195 const VirtualCameraConfiguration& configuration, const int cameraId,
196 bool* _aidl_return) {
Jan Sebechlebskyde6f16f2023-11-29 09:27:36 +0100197 if (!mPermissionProxy.checkCallingPermission(kCreateVirtualDevicePermission)) {
198 ALOGE("%s: caller (pid %d, uid %d) doesn't hold %s permission", __func__,
199 getpid(), getuid(), kCreateVirtualDevicePermission);
200 return ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY);
201 }
202
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100203 if (_aidl_return == nullptr) {
204 return ndk::ScopedAStatus::fromServiceSpecificError(
205 Status::EX_ILLEGAL_ARGUMENT);
206 }
Jan Sebechlebskyde6f16f2023-11-29 09:27:36 +0100207
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100208 *_aidl_return = true;
209
Jan Sebechlebsky3b478c42023-11-23 13:15:56 +0100210 auto status = validateConfiguration(configuration);
211 if (!status.isOk()) {
212 *_aidl_return = false;
213 return status;
214 }
215
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100216 std::lock_guard lock(mLock);
217 if (mTokenToCameraName.find(token) != mTokenToCameraName.end()) {
218 ALOGE(
219 "Attempt to register camera corresponding to already registered binder "
220 "token: "
221 "0x%" PRIxPTR,
222 reinterpret_cast<uintptr_t>(token.get()));
223 *_aidl_return = false;
Jan Sebechlebsky3b478c42023-11-23 13:15:56 +0100224 return ndk::ScopedAStatus::ok();
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100225 }
226
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100227 std::shared_ptr<VirtualCameraDevice> camera =
Marvin Ramina8196132024-03-15 15:55:22 +0000228 mVirtualCameraProvider->createCamera(configuration, cameraId);
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100229 if (camera == nullptr) {
230 ALOGE("Failed to create camera for binder token 0x%" PRIxPTR,
231 reinterpret_cast<uintptr_t>(token.get()));
232 *_aidl_return = false;
233 return ndk::ScopedAStatus::fromServiceSpecificError(
234 Status::EX_SERVICE_SPECIFIC);
235 }
236
237 mTokenToCameraName[token] = camera->getCameraName();
238 return ndk::ScopedAStatus::ok();
239}
240
241ndk::ScopedAStatus VirtualCameraService::unregisterCamera(
242 const ::ndk::SpAIBinder& token) {
Jan Sebechlebskyde6f16f2023-11-29 09:27:36 +0100243 if (!mPermissionProxy.checkCallingPermission(kCreateVirtualDevicePermission)) {
244 ALOGE("%s: caller (pid %d, uid %d) doesn't hold %s permission", __func__,
245 getpid(), getuid(), kCreateVirtualDevicePermission);
246 return ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY);
247 }
248
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100249 std::lock_guard lock(mLock);
250
251 auto it = mTokenToCameraName.find(token);
252 if (it == mTokenToCameraName.end()) {
253 ALOGE(
254 "Attempt to unregister camera corresponding to unknown binder token: "
255 "0x%" PRIxPTR,
256 reinterpret_cast<uintptr_t>(token.get()));
257 return ndk::ScopedAStatus::ok();
258 }
259
260 mVirtualCameraProvider->removeCamera(it->second);
261
Tony Guo6cbe11b2024-03-17 02:34:23 +0000262 mTokenToCameraName.erase(it);
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100263 return ndk::ScopedAStatus::ok();
264}
265
Biswarup Pal68137fc2023-11-24 18:06:54 +0000266ndk::ScopedAStatus VirtualCameraService::getCameraId(
Marvin Ramina8196132024-03-15 15:55:22 +0000267 const ::ndk::SpAIBinder& token, int32_t* _aidl_return) {
Jan Sebechlebskyde6f16f2023-11-29 09:27:36 +0100268 if (!mPermissionProxy.checkCallingPermission(kCreateVirtualDevicePermission)) {
269 ALOGE("%s: caller (pid %d, uid %d) doesn't hold %s permission", __func__,
270 getpid(), getuid(), kCreateVirtualDevicePermission);
271 return ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY);
272 }
273
Biswarup Pal68137fc2023-11-24 18:06:54 +0000274 if (_aidl_return == nullptr) {
275 return ndk::ScopedAStatus::fromServiceSpecificError(
Marvin Ramina8196132024-03-15 15:55:22 +0000276 Status::EX_ILLEGAL_ARGUMENT);
Biswarup Pal68137fc2023-11-24 18:06:54 +0000277 }
278
279 auto camera = getCamera(token);
280 if (camera == nullptr) {
281 ALOGE(
282 "Attempt to get camera id corresponding to unknown binder token: "
283 "0x%" PRIxPTR,
284 reinterpret_cast<uintptr_t>(token.get()));
285 return ndk::ScopedAStatus::ok();
286 }
287
288 *_aidl_return = camera->getCameraId();
289
290 return ndk::ScopedAStatus::ok();
291}
292
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100293std::shared_ptr<VirtualCameraDevice> VirtualCameraService::getCamera(
294 const ::ndk::SpAIBinder& token) {
295 if (token == nullptr) {
296 return nullptr;
297 }
298
299 std::lock_guard lock(mLock);
300 auto it = mTokenToCameraName.find(token);
301 if (it == mTokenToCameraName.end()) {
302 return nullptr;
303 }
304
305 return mVirtualCameraProvider->getCamera(it->second);
306}
307
Jan Sebechlebsky773c0142024-03-25 12:17:05 +0100308binder_status_t VirtualCameraService::handleShellCommand(int, int out, int err,
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100309 const char** args,
310 uint32_t numArgs) {
311 if (numArgs <= 0) {
312 dprintf(out, kShellCmdHelp);
Jan Sebechlebsky76d7e212023-11-28 14:10:25 +0100313 fsync(out);
314 return STATUS_OK;
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100315 }
316
Jan Sebechlebsky773c0142024-03-25 12:17:05 +0100317 auto isNullptr = [](const char* ptr) { return ptr == nullptr; };
318 if (args == nullptr || std::any_of(args, args + numArgs, isNullptr)) {
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100319 return STATUS_BAD_VALUE;
320 }
Marvin Ramina8196132024-03-15 15:55:22 +0000321
Jan Sebechlebsky773c0142024-03-25 12:17:05 +0100322 std::variant<CommandWithOptions, std::string> cmdOrErrorMessage =
323 parseCommand(args, numArgs);
324 if (std::holds_alternative<std::string>(cmdOrErrorMessage)) {
325 dprintf(err, "Error: %s\n",
326 std::get<std::string>(cmdOrErrorMessage).c_str());
327 return STATUS_BAD_VALUE;
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100328 }
329
Jan Sebechlebsky773c0142024-03-25 12:17:05 +0100330 const CommandWithOptions& cmd =
331 std::get<CommandWithOptions>(cmdOrErrorMessage);
332 binder_status_t status = STATUS_OK;
333 switch (cmd.command) {
334 case Command::HELP:
335 dprintf(out, kShellCmdHelp);
336 break;
337 case Command::ENABLE_TEST_CAMERA:
338 status = enableTestCameraCmd(out, err, cmd.optionToValueMap);
339 break;
340 case Command::DISABLE_TEST_CAMERA:
341 disableTestCameraCmd(out);
342 break;
343 }
344
345 fsync(err);
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100346 fsync(out);
Jan Sebechlebsky773c0142024-03-25 12:17:05 +0100347 return status;
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100348}
349
Jan Sebechlebsky773c0142024-03-25 12:17:05 +0100350binder_status_t VirtualCameraService::enableTestCameraCmd(
351 const int out, const int err,
352 const std::map<std::string, std::string>& options) {
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100353 if (mTestCameraToken != nullptr) {
Jan Sebechlebsky773c0142024-03-25 12:17:05 +0100354 dprintf(out, "Test camera is already enabled (%s).\n",
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100355 getCamera(mTestCameraToken)->getCameraName().c_str());
Jan Sebechlebsky773c0142024-03-25 12:17:05 +0100356 return STATUS_OK;
357 }
358
359 std::optional<int> cameraId;
360 auto it = options.find("camera_id");
361 if (it != options.end()) {
362 cameraId = parseInt(it->second);
363 if (!cameraId.has_value()) {
364 dprintf(err, "Invalid camera_id: %s\n, must be number > 0",
365 it->second.c_str());
366 return STATUS_BAD_VALUE;
367 }
368 }
369
370 std::optional<LensFacing> lensFacing;
371 it = options.find("lens_facing");
372 if (it != options.end()) {
373 lensFacing = parseLensFacing(it->second);
374 if (!lensFacing.has_value()) {
375 dprintf(err, "Invalid lens_facing: %s\n, must be front|back|external",
376 it->second.c_str());
377 return STATUS_BAD_VALUE;
378 }
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100379 }
380
381 sp<BBinder> token = sp<BBinder>::make();
382 mTestCameraToken.set(AIBinder_fromPlatformBinder(token));
383
384 bool ret;
Jan Sebechlebsky3b478c42023-11-23 13:15:56 +0100385 VirtualCameraConfiguration configuration;
Biswarup Pal6152a302023-12-19 12:44:09 +0000386 configuration.supportedStreamConfigs.push_back({.width = kVgaWidth,
387 .height = kVgaHeight,
388 Format::YUV_420_888,
389 .maxFps = kMaxFps});
Jan Sebechlebsky773c0142024-03-25 12:17:05 +0100390 configuration.lensFacing = lensFacing.value_or(LensFacing::EXTERNAL);
391 registerCamera(mTestCameraToken, configuration, cameraId.value_or(sNextId++),
392 &ret);
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100393 if (ret) {
Jan Sebechlebsky773c0142024-03-25 12:17:05 +0100394 dprintf(out, "Successfully registered test camera %s\n",
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100395 getCamera(mTestCameraToken)->getCameraName().c_str());
396 } else {
Jan Sebechlebsky773c0142024-03-25 12:17:05 +0100397 dprintf(err, "Failed to create test camera\n");
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100398 }
Jan Sebechlebsky773c0142024-03-25 12:17:05 +0100399 return STATUS_OK;
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100400}
401
402void VirtualCameraService::disableTestCameraCmd(const int out) {
403 if (mTestCameraToken == nullptr) {
404 dprintf(out, "Test camera is not registered.");
405 }
406 unregisterCamera(mTestCameraToken);
407 mTestCameraToken.set(nullptr);
408}
409
410} // namespace virtualcamera
411} // namespace companion
412} // namespace android