| /* |
| * 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 "CarDisplayProxy.h" |
| |
| #include <aidlcommonsupport/NativeHandle.h> |
| #include <android-base/logging.h> |
| #include <android-base/scopeguard.h> |
| #include <gui/ISurfaceComposer.h> |
| #include <gui/SurfaceComposerClient.h> |
| #include <gui/bufferqueue/2.0/B2HGraphicBufferProducer.h> |
| #include <gui/view/Surface.h> |
| #include <ui/Rotation.h> |
| |
| #include <cinttypes> |
| |
| namespace { |
| |
| using ::aidl::android::frameworks::automotive::display::DisplayDesc; |
| using ::aidl::android::frameworks::automotive::display::Rotation; |
| using ::aidl::android::hardware::common::NativeHandle; |
| using ::android::SurfaceComposerClient; |
| using ::ndk::ScopedAStatus; |
| |
| // We're using the highest Z-order. |
| constexpr int32_t kSurfaceZOrder = 0x7FFFFFFF; |
| |
| Rotation convert(::android::ui::Rotation uiRotation) { |
| switch (uiRotation) { |
| case ::android::ui::ROTATION_0: |
| return Rotation::ROTATION_0; |
| case ::android::ui::ROTATION_90: |
| return Rotation::ROTATION_90; |
| case ::android::ui::ROTATION_180: |
| return Rotation::ROTATION_180; |
| case ::android::ui::ROTATION_270: |
| return Rotation::ROTATION_270; |
| } |
| } |
| |
| constexpr size_t kMaxWindowSize = 256; |
| |
| native_handle_t* convertHalTokenToNativeHandle(const ::android::HalToken& halToken) { |
| // Attempts to store halToken in the ints of the native_handle_t after its |
| // size. |
| auto nhDataByteSize = halToken.size(); |
| if (nhDataByteSize > kMaxWindowSize) { |
| return nullptr; |
| } |
| |
| auto numInts = ceil(nhDataByteSize / sizeof(int)) + 1; |
| native_handle_t* nh = native_handle_create(/* numFds = */ 0, numInts); |
| if (nh == nullptr) { |
| return nullptr; |
| } |
| |
| // Stores the size of the token in the first int |
| nh->data[0] = nhDataByteSize; |
| memcpy(&(nh->data[1]), halToken.data(), nhDataByteSize); |
| return nh; |
| } |
| |
| } // namespace |
| |
| namespace aidl::android::frameworks::automotive::display::implementation { |
| |
| ScopedAStatus CarDisplayProxy::getDisplayIdList(std::vector<int64_t>* _aidl_return) { |
| std::vector<::android::PhysicalDisplayId> displayIds = |
| SurfaceComposerClient::getPhysicalDisplayIds(); |
| std::for_each(displayIds.begin(), displayIds.end(), |
| [_aidl_return](const ::android::PhysicalDisplayId& id) { |
| _aidl_return->push_back(id.value); |
| }); |
| |
| return ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus CarDisplayProxy::getDisplayInfo(int64_t id, DisplayDesc* _aidl_return) { |
| ::android::ui::DisplayMode displayMode; |
| ::android::ui::DisplayState displayState; |
| if (!getDisplayInfoFromSurfaceComposerClient(id, &displayMode, &displayState)) { |
| LOG(ERROR) << "Invalid display id = " << id; |
| return ScopedAStatus::fromStatus(STATUS_BAD_VALUE); |
| } |
| |
| DisplayDesc desc = { |
| .width = displayMode.resolution.width, |
| .height = displayMode.resolution.height, |
| .layer = displayState.layerStack.id, |
| .orientation = convert(displayState.orientation), |
| }; |
| *_aidl_return = std::move(desc); |
| return ScopedAStatus::ok(); |
| } |
| |
| ::android::sp<::android::IBinder> CarDisplayProxy::getDisplayInfoFromSurfaceComposerClient( |
| int64_t id, ::android::ui::DisplayMode* displayMode, |
| ::android::ui::DisplayState* displayState) { |
| ::android::sp<::android::IBinder> displayToken; |
| std::optional<::android::PhysicalDisplayId> displayId = |
| ::android::DisplayId::fromValue<::android::PhysicalDisplayId>(id); |
| if (!displayId) { |
| LOG(ERROR) << "Failed to get a valid display name"; |
| return nullptr; |
| } |
| |
| displayToken = SurfaceComposerClient::getPhysicalDisplayToken(*displayId); |
| if (!displayToken) { |
| LOG(ERROR) << "Failed to get a valid display token"; |
| return nullptr; |
| } |
| |
| ::android::status_t status = |
| SurfaceComposerClient::getActiveDisplayMode(displayToken, displayMode); |
| if (status != ::android::NO_ERROR) { |
| LOG(WARNING) << "Failed to read current mode of the display " << id; |
| } |
| |
| status = SurfaceComposerClient::getDisplayState(displayToken, displayState); |
| if (status != ::android::NO_ERROR) { |
| LOG(WARNING) << "Failed to read current state of the display " << id; |
| } |
| |
| return displayToken; |
| } |
| |
| ScopedAStatus CarDisplayProxy::getHGraphicBufferProducer(int64_t id, NativeHandle* _aidl_return) { |
| ::android::sp<::android::IBinder> displayToken; |
| ::android::sp<::android::SurfaceControl> surfaceControl; |
| |
| if (auto status = getDisplayRecord(id, &displayToken, &surfaceControl); !status.isOk()) { |
| return status; |
| } |
| |
| // SurfaceControl::getSurface() is guaranteed to be non-null. |
| auto targetSurface = surfaceControl->getSurface(); |
| auto igbp = targetSurface->getIGraphicBufferProducer(); |
| auto hgbp = |
| new ::android::hardware::graphics::bufferqueue::V2_0::utils::B2HGraphicBufferProducer( |
| igbp); |
| |
| ::android::HalToken halToken; |
| if (!::android::createHalToken(hgbp, &halToken)) { |
| LOG(ERROR) << "Failed to create a hal token"; |
| return ScopedAStatus::fromStatus(STATUS_BAD_VALUE); |
| } |
| |
| native_handle_t* handle = convertHalTokenToNativeHandle(halToken); |
| auto scope_guard = ::android::base::make_scope_guard([handle]() { |
| native_handle_close(handle); |
| native_handle_delete(handle); |
| }); |
| if (handle == nullptr) { |
| LOG(ERROR) << "Failed to create a handle, errno = " << strerror(errno); |
| return ScopedAStatus::fromStatus(STATUS_BAD_VALUE); |
| } |
| |
| *_aidl_return = ::android::dupToAidl(handle); |
| return ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus CarDisplayProxy::getSurface(int64_t id, android::view::Surface* _aidl_return) { |
| if (_aidl_return == nullptr) { |
| LOG(ERROR) << __FUNCTION__ |
| << " is called with an invalid aidl::android::view::Surface object."; |
| return ScopedAStatus::fromStatus(STATUS_BAD_VALUE); |
| } |
| |
| auto it = mSurfaceList.find(id); |
| if (it != mSurfaceList.end() && ::android::Surface::isValid(mSurfaceList[id])) { |
| LOG(DEBUG) << __FUNCTION__ << " reuses an existing surface for a display " << id; |
| _aidl_return->reset(mSurfaceList[id].get()); |
| return ScopedAStatus::ok(); |
| } |
| |
| ::android::sp<::android::IBinder> displayToken; |
| ::android::sp<::android::SurfaceControl> surfaceControl; |
| if (auto status = getDisplayRecord(id, &displayToken, &surfaceControl); !status.isOk()) { |
| return status; |
| } |
| |
| // Retrieve a Surface associated with a target display. |
| ::android::sp<::android::Surface> targetSurface = surfaceControl->getSurface(); |
| if (!targetSurface || !::android::Surface::isValid(targetSurface)) { |
| LOG(ERROR) << "Created Surface is invalid, " << targetSurface.get(); |
| return ScopedAStatus::fromStatus(STATUS_FAILED_TRANSACTION); |
| } |
| |
| // Bookkeep a retrieved Surface object and pass it to the client as a format |
| // of ::aidl::android::view::Surface, which is alias to |
| // ::aidl::android::hardware::NativeWindow. |
| mSurfaceList.insert_or_assign(id, targetSurface); |
| _aidl_return->reset(targetSurface.get()); |
| return ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus CarDisplayProxy::hideWindow(int64_t id) { |
| auto it = mDisplays.find(id); |
| if (it == mDisplays.end()) { |
| LOG(DEBUG) << __FUNCTION__ << ": Invalid display id, " << id; |
| return ScopedAStatus::ok(); |
| } |
| |
| auto status = SurfaceComposerClient::Transaction{}.hide(it->second.surfaceControl).apply(); |
| if (status != ::android::NO_ERROR) { |
| LOG(DEBUG) << __FUNCTION__ |
| << ": Failed to hide a surface, status = " << ::android::statusToString(status); |
| } |
| |
| return ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus CarDisplayProxy::showWindow(int64_t id) { |
| auto it = mDisplays.find(id); |
| if (it == mDisplays.end()) { |
| LOG(ERROR) << __FUNCTION__ << ": Invalid display id, " << id; |
| return ScopedAStatus::fromStatus(STATUS_BAD_VALUE); |
| } |
| |
| const auto& displayToken = it->second.token; |
| const auto& surfaceControl = it->second.surfaceControl; |
| ::android::ui::DisplayState displayState; |
| ::android::status_t status = |
| SurfaceComposerClient::getDisplayState(displayToken, &displayState); |
| if (status != ::android::NO_ERROR) { |
| LOG(ERROR) << "Failed to read current state of the display " << id |
| << ", status = " << ::android::statusToString(status); |
| return ScopedAStatus::fromStatus(status); |
| } |
| |
| SurfaceComposerClient::Transaction t; |
| t.setDisplayLayerStack(displayToken, displayState.layerStack); |
| t.setLayerStack(surfaceControl, displayState.layerStack); |
| |
| status = t.setLayer(surfaceControl, kSurfaceZOrder).show(surfaceControl).apply(); |
| if (status != ::android::NO_ERROR) { |
| LOG(ERROR) << "Failed to set a layer"; |
| return ScopedAStatus::fromStatus(status); |
| } |
| |
| return ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus CarDisplayProxy::getDisplayRecord( |
| int64_t id, ::android::sp<::android::IBinder>* outDisplayToken, |
| ::android::sp<::android::SurfaceControl>* outSurfaceControl) { |
| auto it = mDisplays.find(id); |
| if (it != mDisplays.end()) { |
| *outDisplayToken = mDisplays[id].token; |
| *outSurfaceControl = mDisplays[id].surfaceControl; |
| return ScopedAStatus::ok(); |
| } |
| |
| ::android::sp<::android::IBinder> displayToken; |
| ::android::sp<::android::SurfaceControl> surfaceControl; |
| ::android::ui::DisplayMode displayMode; |
| ::android::ui::DisplayState displayState; |
| displayToken = getDisplayInfoFromSurfaceComposerClient(id, &displayMode, &displayState); |
| if (!displayToken) { |
| LOG(ERROR) << "Failed to read display information from SurfaceComposerClient."; |
| return ScopedAStatus::fromStatus(STATUS_FAILED_TRANSACTION); |
| } |
| |
| auto displayWidth = displayMode.resolution.getWidth(); |
| auto displayHeight = displayMode.resolution.getHeight(); |
| if ((displayState.orientation != ::android::ui::ROTATION_0) && |
| (displayState.orientation != ::android::ui::ROTATION_180)) { |
| std::swap(displayWidth, displayHeight); |
| } |
| |
| ::android::sp<SurfaceComposerClient> client = new SurfaceComposerClient(); |
| ::android::status_t status = client->initCheck(); |
| if (status != ::android::NO_ERROR) { |
| LOG(ERROR) << "SurfaceComposerClient::initCheck() fails, error = " |
| << ::android::statusToString(status); |
| return ScopedAStatus::fromStatus(status); |
| } |
| |
| surfaceControl = |
| client->createSurface(::android::String8::format("CarDisplayProxy::%" PRIx64, id), |
| displayWidth, displayHeight, ::android::PIXEL_FORMAT_RGBX_8888, |
| ::android::ISurfaceComposerClient::eOpaque); |
| if (!surfaceControl || !surfaceControl->isValid()) { |
| LOG(ERROR) << "Failed to create a SurfaceControl"; |
| return ScopedAStatus::fromStatus(STATUS_FAILED_TRANSACTION); |
| } |
| |
| *outDisplayToken = displayToken; |
| *outSurfaceControl = surfaceControl; |
| |
| DisplayRecord rec = {displayToken, surfaceControl}; |
| mDisplays.insert_or_assign(id, std::move(rec)); |
| |
| return ScopedAStatus::ok(); |
| } |
| |
| } // namespace aidl::android::frameworks::automotive::display::implementation |