/* * Copyright (C) 2019 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 "Enumerator.h" #include "HalDisplay.h" #include #include #include #include #include #include #include #include namespace { const char* kSingleIndent = "\t"; const char* kDumpOptionAll = "all"; const char* kDumpDeviceCamera = "camera"; const char* kDumpDeviceDisplay = "display"; const char* kDumpCameraCommandCurrent = "--current"; const char* kDumpCameraCommandCollected = "--collected"; const char* kDumpCameraCommandCustom = "--custom"; const char* kDumpCameraCommandCustomStart = "start"; const char* kDumpCameraCommandCustomStop = "stop"; const int kDumpCameraMinNumArgs = 4; const int kOptionDumpDeviceTypeIndex = 1; const int kOptionDumpCameraTypeIndex = 2; const int kOptionDumpCameraCommandIndex = 3; const int kOptionDumpCameraArgsStartIndex = 4; } namespace android { namespace automotive { namespace evs { namespace V1_1 { namespace implementation { using ::android::base::Error; using ::android::base::EqualsIgnoreCase; using ::android::base::StringAppendF; using ::android::base::StringPrintf; using ::android::base::WriteStringToFd; using CameraDesc_1_0 = ::android::hardware::automotive::evs::V1_0::CameraDesc; using CameraDesc_1_1 = ::android::hardware::automotive::evs::V1_1::CameraDesc; bool Enumerator::init(const char* hardwareServiceName) { LOG(DEBUG) << "init"; // Connect with the underlying hardware enumerator mHwEnumerator = IEvsEnumerator::getService(hardwareServiceName); bool result = (mHwEnumerator.get() != nullptr); if (result) { // Get an internal display identifier. mHwEnumerator->getDisplayIdList( [this](const auto& displayPorts) { for (auto& port : displayPorts) { mDisplayPorts.push_back(port); } // The first element is the internal display mInternalDisplayPort = mDisplayPorts.front(); if (mDisplayPorts.size() < 1) { LOG(WARNING) << "No display is available to EVS service."; } } ); } // Starts the statistics collection mMonitorEnabled = false; mClientsMonitor = new StatsCollector(); if (mClientsMonitor != nullptr) { auto result = mClientsMonitor->startCollection(); if (!result.ok()) { LOG(ERROR) << "Failed to start the usage monitor: " << result.error(); } else { mMonitorEnabled = true; } } return result; } bool Enumerator::checkPermission() { hardware::IPCThreadState *ipc = hardware::IPCThreadState::self(); const auto userId = ipc->getCallingUid() / AID_USER_OFFSET; const auto appId = ipc->getCallingUid() % AID_USER_OFFSET; #ifdef EVS_DEBUG if (AID_AUTOMOTIVE_EVS != appId && AID_ROOT != appId && AID_SYSTEM != appId) { #else if (AID_AUTOMOTIVE_EVS != appId && AID_SYSTEM != appId) { #endif LOG(ERROR) << "EVS access denied? " << "pid = " << ipc->getCallingPid() << ", userId = " << userId << ", appId = " << appId; return false; } return true; } bool Enumerator::isLogicalCamera(const camera_metadata_t *metadata) { bool found = false; if (metadata == nullptr) { LOG(ERROR) << "Metadata is null"; return found; } camera_metadata_ro_entry_t entry; int rc = find_camera_metadata_ro_entry(metadata, ANDROID_REQUEST_AVAILABLE_CAPABILITIES, &entry); if (0 != rc) { // No capabilities are found in metadata. LOG(DEBUG) << __FUNCTION__ << " does not find a target entry"; return found; } for (size_t i = 0; i < entry.count; ++i) { uint8_t capability = entry.data.u8[i]; if (capability == ANDROID_REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA) { found = true; break; } } if (!found) { LOG(DEBUG) << __FUNCTION__ << " does not find a logical multi camera cap"; } return found; } std::unordered_set Enumerator::getPhysicalCameraIds(const std::string& id) { std::unordered_set physicalCameras; if (mCameraDevices.find(id) == mCameraDevices.end()) { LOG(ERROR) << "Queried device " << id << " does not exist!"; return physicalCameras; } const camera_metadata_t *metadata = reinterpret_cast(&mCameraDevices[id].metadata[0]); if (!isLogicalCamera(metadata)) { // EVS assumes that the device w/o a valid metadata is a physical // device. LOG(INFO) << id << " is not a logical camera device."; physicalCameras.emplace(id); return physicalCameras; } camera_metadata_ro_entry entry; int rc = find_camera_metadata_ro_entry(metadata, ANDROID_LOGICAL_MULTI_CAMERA_PHYSICAL_IDS, &entry); if (0 != rc) { LOG(ERROR) << "No physical camera ID is found for a logical camera device " << id; return physicalCameras; } const uint8_t *ids = entry.data.u8; size_t start = 0; for (size_t i = 0; i < entry.count; ++i) { if (ids[i] == '\0') { if (start != i) { std::string id(reinterpret_cast(ids + start)); physicalCameras.emplace(id); } start = i + 1; } } LOG(INFO) << id << " consists of " << physicalCameras.size() << " physical camera devices."; return physicalCameras; } // Methods from ::android::hardware::automotive::evs::V1_0::IEvsEnumerator follow. Return Enumerator::getCameraList(getCameraList_cb list_cb) { hardware::hidl_vec cameraList; mHwEnumerator->getCameraList_1_1([&cameraList](auto cameraList_1_1) { cameraList.resize(cameraList_1_1.size()); unsigned i = 0; for (auto&& cam : cameraList_1_1) { cameraList[i++] = cam.v1; } }); list_cb(cameraList); return Void(); } Return> Enumerator::openCamera(const hidl_string& cameraId) { LOG(DEBUG) << __FUNCTION__; if (!checkPermission()) { return nullptr; } // Is the underlying hardware camera already open? sp hwCamera; if (mActiveCameras.find(cameraId) != mActiveCameras.end()) { hwCamera = mActiveCameras[cameraId]; } else { // Is the hardware camera available? sp device = IEvsCamera_1_1::castFrom(mHwEnumerator->openCamera(cameraId)) .withDefault(nullptr); if (device == nullptr) { LOG(ERROR) << "Failed to open hardware camera " << cameraId; } else { // Calculates the usage statistics record identifier auto fn = mCameraDevices.hash_function(); auto recordId = fn(cameraId) & 0xFF; hwCamera = new HalCamera(device, cameraId, recordId); if (hwCamera == nullptr) { LOG(ERROR) << "Failed to allocate camera wrapper object"; mHwEnumerator->closeCamera(device); } } } // Construct a virtual camera wrapper for this hardware camera sp clientCamera; if (hwCamera != nullptr) { clientCamera = hwCamera->makeVirtualCamera(); } // Add the hardware camera to our list, which will keep it alive via ref count if (clientCamera != nullptr) { mActiveCameras.try_emplace(cameraId, hwCamera); } else { LOG(ERROR) << "Requested camera " << cameraId << " not found or not available"; } // Send the virtual camera object back to the client by strong pointer which will keep it alive return clientCamera; } Return Enumerator::closeCamera(const ::android::sp& clientCamera) { LOG(DEBUG) << __FUNCTION__; if (clientCamera.get() == nullptr) { LOG(ERROR) << "Ignoring call with null camera pointer."; return Void(); } // All our client cameras are actually VirtualCamera objects sp virtualCamera = reinterpret_cast(clientCamera.get()); // Find the parent camera that backs this virtual camera for (auto&& halCamera : virtualCamera->getHalCameras()) { // Tell the virtual camera's parent to clean it up and drop it // NOTE: The camera objects will only actually destruct when the sp<> ref counts get to // zero, so it is important to break all cyclic references. halCamera->disownVirtualCamera(virtualCamera); // Did we just remove the last client of this camera? if (halCamera->getClientCount() == 0) { // Take this now unused camera out of our list // NOTE: This should drop our last reference to the camera, resulting in its // destruction. mActiveCameras.erase(halCamera->getId()); if (mMonitorEnabled) { mClientsMonitor->unregisterClientToMonitor(halCamera->getId()); } } } // Make sure the virtual camera's stream is stopped virtualCamera->stopVideoStream(); return Void(); } // Methods from ::android::hardware::automotive::evs::V1_1::IEvsEnumerator follow. Return> Enumerator::openCamera_1_1(const hidl_string& cameraId, const Stream& streamCfg) { LOG(DEBUG) << __FUNCTION__; if (!checkPermission()) { return nullptr; } // If hwCamera is null, a requested camera device is either a logical camera // device or a hardware camera, which is not being used now. std::unordered_set physicalCameras = getPhysicalCameraIds(cameraId); std::vector> sourceCameras; sp hwCamera; bool success = true; // 1. Try to open inactive camera devices. for (auto&& id : physicalCameras) { auto it = mActiveCameras.find(id); if (it == mActiveCameras.end()) { // Try to open a hardware camera. sp device = IEvsCamera_1_1::castFrom(mHwEnumerator->openCamera_1_1(id, streamCfg)) .withDefault(nullptr); if (device == nullptr) { LOG(ERROR) << "Failed to open hardware camera " << cameraId; success = false; break; } else { // Calculates the usage statistics record identifier auto fn = mCameraDevices.hash_function(); auto recordId = fn(id) & 0xFF; hwCamera = new HalCamera(device, id, recordId, streamCfg); if (hwCamera == nullptr) { LOG(ERROR) << "Failed to allocate camera wrapper object"; mHwEnumerator->closeCamera(device); success = false; break; } else if (!hwCamera->isSyncSupported()) { LOG(INFO) << id << " does not support a sw_sync."; if (physicalCameras.size() > 1) { LOG(ERROR) << "sw_sync is required for logical camera devices."; success = false; break; } } } // Add the hardware camera to our list, which will keep it alive via ref count mActiveCameras.try_emplace(id, hwCamera); if (mMonitorEnabled) { mClientsMonitor->registerClientToMonitor(hwCamera); } sourceCameras.push_back(hwCamera); } else { if (it->second->getStreamConfig().id != streamCfg.id) { LOG(WARNING) << "Requested camera is already active in different configuration."; } else { sourceCameras.push_back(it->second); } } } if (!success || sourceCameras.size() < 1) { LOG(ERROR) << "Failed to open any physical camera device"; return nullptr; } // TODO(b/147170360): Implement a logic to handle a failure. // 3. Create a proxy camera object sp clientCamera = new VirtualCamera(sourceCameras); if (clientCamera == nullptr) { // TODO: Any resource needs to be cleaned up explicitly? LOG(ERROR) << "Failed to create a client camera object"; } else { if (physicalCameras.size() > 1) { // VirtualCamera, which represents a logical device, caches its // descriptor. clientCamera->setDescriptor(&mCameraDevices[cameraId]); } // 4. Owns created proxy camera object for (auto&& hwCamera : sourceCameras) { if (!hwCamera->ownVirtualCamera(clientCamera)) { // TODO: Remove a referece to this camera from a virtual camera // object. LOG(ERROR) << hwCamera->getId() << " failed to own a created proxy camera object."; } } } // Send the virtual camera object back to the client by strong pointer which will keep it alive return clientCamera; } Return Enumerator::getCameraList_1_1(getCameraList_1_1_cb list_cb) { LOG(DEBUG) << __FUNCTION__; if (!checkPermission()) { return Void(); } hardware::hidl_vec hidlCameras; mHwEnumerator->getCameraList_1_1( [&hidlCameras](hardware::hidl_vec enumeratedCameras) { hidlCameras.resize(enumeratedCameras.size()); unsigned count = 0; for (auto&& camdesc : enumeratedCameras) { hidlCameras[count++] = camdesc; } } ); // Update the cached device list mCameraDevices.clear(); for (auto&& desc : hidlCameras) { mCameraDevices.insert_or_assign(desc.v1.cameraId, desc); } list_cb(hidlCameras); return Void(); } Return> Enumerator::openDisplay() { LOG(DEBUG) << __FUNCTION__; if (!checkPermission()) { return nullptr; } // We simply keep track of the most recently opened display instance. // In the underlying layers we expect that a new open will cause the previous // object to be destroyed. This avoids any race conditions associated with // create/destroy order and provides a cleaner restart sequence if the previous owner // is non-responsive for some reason. // Request exclusive access to the EVS display sp pActiveDisplay = mHwEnumerator->openDisplay(); if (pActiveDisplay == nullptr) { LOG(ERROR) << "EVS Display unavailable"; return nullptr; } // Remember (via weak pointer) who we think the most recently opened display is so that // we can proxy state requests from other callers to it. // TODO: Because of b/129284474, an additional class, HalDisplay, has been defined and // wraps the IEvsDisplay object the driver returns. We may want to remove this // additional class when it is fixed properly. sp pHalDisplay = new HalDisplay(pActiveDisplay, mInternalDisplayPort); mActiveDisplay = pHalDisplay; return pHalDisplay; } Return Enumerator::closeDisplay(const ::android::sp& display) { LOG(DEBUG) << __FUNCTION__; sp pActiveDisplay = mActiveDisplay.promote(); // Drop the active display if (display.get() != pActiveDisplay.get()) { LOG(WARNING) << "Ignoring call to closeDisplay with unrecognized display object."; } else { // Pass this request through to the hardware layer sp halDisplay = reinterpret_cast(pActiveDisplay.get()); mHwEnumerator->closeDisplay(halDisplay->getHwDisplay()); mActiveDisplay = nullptr; } return Void(); } Return Enumerator::getDisplayState() { LOG(DEBUG) << __FUNCTION__; if (!checkPermission()) { return EvsDisplayState::DEAD; } // Do we have a display object we think should be active? sp pActiveDisplay = mActiveDisplay.promote(); if (pActiveDisplay != nullptr) { // Pass this request through to the hardware layer return pActiveDisplay->getDisplayState(); } else { // We don't have a live display right now mActiveDisplay = nullptr; return EvsDisplayState::NOT_OPEN; } } Return> Enumerator::openDisplay_1_1(uint8_t id) { LOG(DEBUG) << __FUNCTION__; if (!checkPermission()) { return nullptr; } if (std::find(mDisplayPorts.begin(), mDisplayPorts.end(), id) == mDisplayPorts.end()) { LOG(ERROR) << "No display is available on the port " << static_cast(id); return nullptr; } // We simply keep track of the most recently opened display instance. // In the underlying layers we expect that a new open will cause the previous // object to be destroyed. This avoids any race conditions associated with // create/destroy order and provides a cleaner restart sequence if the previous owner // is non-responsive for some reason. // Request exclusive access to the EVS display sp pActiveDisplay = mHwEnumerator->openDisplay_1_1(id); if (pActiveDisplay == nullptr) { LOG(ERROR) << "EVS Display unavailable"; return nullptr; } // Remember (via weak pointer) who we think the most recently opened display is so that // we can proxy state requests from other callers to it. // TODO: Because of b/129284474, an additional class, HalDisplay, has been defined and // wraps the IEvsDisplay object the driver returns. We may want to remove this // additional class when it is fixed properly. sp pHalDisplay = new HalDisplay(pActiveDisplay, id); mActiveDisplay = pHalDisplay; return pHalDisplay; } Return Enumerator::getDisplayIdList(getDisplayIdList_cb _list_cb) { return mHwEnumerator->getDisplayIdList(_list_cb); } // TODO(b/149874793): Add implementation for EVS Manager and Sample driver Return Enumerator::getUltrasonicsArrayList(getUltrasonicsArrayList_cb _hidl_cb) { hardware::hidl_vec ultrasonicsArrayDesc; _hidl_cb(ultrasonicsArrayDesc); return Void(); } // TODO(b/149874793): Add implementation for EVS Manager and Sample driver Return> Enumerator::openUltrasonicsArray( const hidl_string& ultrasonicsArrayId) { (void)ultrasonicsArrayId; sp pEvsUltrasonicsArray; return pEvsUltrasonicsArray; } // TODO(b/149874793): Add implementation for EVS Manager and Sample driver Return Enumerator::closeUltrasonicsArray( const ::android::sp& evsUltrasonicsArray) { (void)evsUltrasonicsArray; return Void(); } Return Enumerator::debug(const hidl_handle& fd, const hidl_vec& options) { if (fd.getNativeHandle() != nullptr && fd->numFds > 0) { cmdDump(fd->data[0], options); } else { LOG(ERROR) << "Given file descriptor is not valid."; } return {}; } void Enumerator::cmdDump(int fd, const hidl_vec& options) { if (options.size() == 0) { WriteStringToFd("No option is given.\n", fd); cmdHelp(fd); return; } const std::string option = options[0]; if (EqualsIgnoreCase(option, "--help")) { cmdHelp(fd); } else if (EqualsIgnoreCase(option, "--list")) { cmdList(fd, options); } else if (EqualsIgnoreCase(option, "--dump")) { cmdDumpDevice(fd, options); } else { WriteStringToFd(StringPrintf("Invalid option: %s\n", option.c_str()), fd); } } void Enumerator::cmdHelp(int fd) { WriteStringToFd("--help: shows this help.\n" "--list [all|camera|display]: lists camera or display devices or both " "available to EVS manager.\n" "--dump camera [all|device_id] --[current|collected|custom] [args]\n" "\tcurrent: shows the current status\n" "\tcollected: shows 10 most recent periodically collected camera usage " "statistics\n" "\tcustom: starts/stops collecting the camera usage statistics\n" "\t\tstart [interval] [duration]: starts collecting usage statistics " "at every [interval] during [duration]. Interval and duration are in " "milliseconds.\n" "\t\tstop: stops collecting usage statistics and shows collected records.\n" "--dump display: shows current status of the display\n", fd); } void Enumerator::cmdList(int fd, const hidl_vec& options) { bool listCameras = true; bool listDisplays = true; if (options.size() > 1) { const std::string option = options[1]; const bool listAll = EqualsIgnoreCase(option, kDumpOptionAll); listCameras = listAll || EqualsIgnoreCase(option, kDumpDeviceCamera); listDisplays = listAll || EqualsIgnoreCase(option, kDumpDeviceDisplay); if (!listCameras && !listDisplays) { WriteStringToFd(StringPrintf("Unrecognized option, %s, is ignored.\n", option.c_str()), fd); // Nothing to show, return return; } } std::string buffer; if (listCameras) { StringAppendF(&buffer,"Camera devices available to EVS service:\n"); if (mCameraDevices.size() < 1) { // Camera devices may not be enumerated yet. This may fail if the // user is not permitted to use EVS service. getCameraList_1_1( [](const auto cameras) { if (cameras.size() < 1) { LOG(WARNING) << "No camera device is available to EVS."; } }); } for (auto& [id, desc] : mCameraDevices) { StringAppendF(&buffer, "%s%s\n", kSingleIndent, id.c_str()); } StringAppendF(&buffer, "%sCamera devices currently in use:\n", kSingleIndent); for (auto& [id, ptr] : mActiveCameras) { StringAppendF(&buffer, "%s%s\n", kSingleIndent, id.c_str()); } StringAppendF(&buffer, "\n"); } if (listDisplays) { if (mHwEnumerator != nullptr) { StringAppendF(&buffer, "Display devices available to EVS service:\n"); // Get an internal display identifier. mHwEnumerator->getDisplayIdList( [&](const auto& displayPorts) { for (auto&& port : displayPorts) { StringAppendF(&buffer, "%sdisplay port %u\n", kSingleIndent, static_cast(port)); } } ); } else { LOG(WARNING) << "EVS HAL implementation is not available."; } } WriteStringToFd(buffer, fd); } void Enumerator::cmdDumpDevice(int fd, const hidl_vec& options) { // Dumps both cameras and displays if the target device type is not given bool dumpCameras = false; bool dumpDisplays = false; const auto numOptions = options.size(); if (numOptions > kOptionDumpDeviceTypeIndex) { const std::string target = options[kOptionDumpDeviceTypeIndex]; dumpCameras = EqualsIgnoreCase(target, kDumpDeviceCamera); dumpDisplays = EqualsIgnoreCase(target, kDumpDeviceDisplay); if (!dumpCameras && !dumpDisplays) { WriteStringToFd(StringPrintf("Unrecognized option, %s, is ignored.\n", target.c_str()), fd); cmdHelp(fd); return; } } else { WriteStringToFd(StringPrintf("Necessary arguments are missing. " "Please check the usages:\n"), fd); cmdHelp(fd); return; } if (dumpCameras) { // --dump camera [all|device_id] --[current|collected|custom] [args] if (numOptions < kDumpCameraMinNumArgs) { WriteStringToFd(StringPrintf("Necessary arguments are missing. " "Please check the usages:\n"), fd); cmdHelp(fd); return; } const std::string deviceId = options[kOptionDumpCameraTypeIndex]; auto target = mActiveCameras.find(deviceId); const bool dumpAllCameras = EqualsIgnoreCase(deviceId, kDumpOptionAll); if (!dumpAllCameras && target == mActiveCameras.end()) { // Unknown camera identifier WriteStringToFd(StringPrintf("Given camera ID %s is unknown or not active.\n", deviceId.c_str()), fd); return; } const std::string command = options[kOptionDumpCameraCommandIndex]; std::string cameraInfo; if (EqualsIgnoreCase(command, kDumpCameraCommandCurrent)) { // Active stream configuration from each active HalCamera objects if (!dumpAllCameras) { StringAppendF(&cameraInfo, "HalCamera: %s\n%s", deviceId.c_str(), target->second->toString(kSingleIndent).c_str()); } else { for (auto&& [id, handle] : mActiveCameras) { // Appends the current status cameraInfo += handle->toString(kSingleIndent); } } } else if (EqualsIgnoreCase(command, kDumpCameraCommandCollected)) { // Reads the usage statistics from active HalCamera objects std::unordered_map usageStrings; if (mMonitorEnabled) { auto result = mClientsMonitor->toString(&usageStrings, kSingleIndent); if (!result.ok()) { LOG(ERROR) << "Failed to get the monitoring result"; return; } if (!dumpAllCameras) { cameraInfo += usageStrings[deviceId]; } else { for (auto&& [id, stats] : usageStrings) { cameraInfo += stats; } } } else { WriteStringToFd(StringPrintf("Client monitor is not available.\n"), fd); return; } } else if (EqualsIgnoreCase(command, kDumpCameraCommandCustom)) { // Additional arguments are expected for this command: // --dump camera device_id --custom start [interval] [duration] // or, --dump camera device_id --custom stop if (numOptions < kDumpCameraMinNumArgs + 1) { WriteStringToFd(StringPrintf("Necessary arguments are missing. " "Please check the usages:\n"), fd); cmdHelp(fd); return; } if (!mMonitorEnabled) { WriteStringToFd(StringPrintf("Client monitor is not available."), fd); return; } const std::string subcommand = options[kOptionDumpCameraArgsStartIndex]; if (EqualsIgnoreCase(subcommand, kDumpCameraCommandCustomStart)) { using std::chrono::nanoseconds; using std::chrono::milliseconds; using std::chrono::duration_cast; nanoseconds interval = 0ns; nanoseconds duration = 0ns; if (numOptions > kOptionDumpCameraArgsStartIndex + 2) { duration = duration_cast( milliseconds( std::stoi(options[kOptionDumpCameraArgsStartIndex + 2]) )); } if (numOptions > kOptionDumpCameraArgsStartIndex + 1) { interval = duration_cast( milliseconds( std::stoi(options[kOptionDumpCameraArgsStartIndex + 1]) )); } // Starts a custom collection auto result = mClientsMonitor->startCustomCollection(interval, duration); if (!result) { LOG(ERROR) << "Failed to start a custom collection. " << result.error(); StringAppendF(&cameraInfo, "Failed to start a custom collection. %s\n", result.error().message().c_str()); } } else if (EqualsIgnoreCase(subcommand, kDumpCameraCommandCustomStop)) { if (!mMonitorEnabled) { WriteStringToFd(StringPrintf("Client monitor is not available."), fd); return; } auto result = mClientsMonitor->stopCustomCollection(deviceId); if (!result) { LOG(ERROR) << "Failed to stop a custom collection. " << result.error(); StringAppendF(&cameraInfo, "Failed to stop a custom collection. %s\n", result.error().message().c_str()); } else { // Pull the custom collection cameraInfo += *result; } } else { WriteStringToFd(StringPrintf("Unknown argument: %s\n", subcommand.c_str()), fd); cmdHelp(fd); return; } } else { WriteStringToFd(StringPrintf("Unknown command: %s\n" "Please check the usages:\n", command.c_str()), fd); cmdHelp(fd); return; } // Outputs the report WriteStringToFd(cameraInfo, fd); } if (dumpDisplays) { HalDisplay* pDisplay = reinterpret_cast(mActiveDisplay.promote().get()); if (!pDisplay) { WriteStringToFd("No active display is found.\n", fd); } else { WriteStringToFd(pDisplay->toString(kSingleIndent), fd); } } } } // namespace implementation } // namespace V1_1 } // namespace evs } // namespace automotive } // namespace android