diff options
author | Matt Lee <matthewhlee@google.com> | 2023-03-02 14:24:05 -0800 |
---|---|---|
committer | Matt Lee <matthewhlee@google.com> | 2023-03-06 00:42:42 -0800 |
commit | e728bd444557e8ca3248a008ea4bf5344853e446 (patch) | |
tree | 59fdb65e7b2a78fe20be90555d5c93dbb44ed12e | |
parent | 413f79aa2b18f8989146a903d34701d2c6d0b5ee (diff) | |
parent | 81672f23cbceb684e5690dafb3ef96e551ef5fe4 (diff) |
Merge t-qpr-2023-03
Change-Id: I5b496c4b0544de7907e0780460b0907a97850926
41 files changed, 935 insertions, 187 deletions
diff --git a/cmds/installd/InstalldNativeService.cpp b/cmds/installd/InstalldNativeService.cpp index a49f563060..6d3f7c3bab 100644 --- a/cmds/installd/InstalldNativeService.cpp +++ b/cmds/installd/InstalldNativeService.cpp @@ -100,6 +100,8 @@ static constexpr const char* kXattrDefault = "user.default"; static constexpr const char* kDataMirrorCePath = "/data_mirror/data_ce"; static constexpr const char* kDataMirrorDePath = "/data_mirror/data_de"; +static constexpr const char* kMiscMirrorCePath = "/data_mirror/misc_ce"; +static constexpr const char* kMiscMirrorDePath = "/data_mirror/misc_de"; static constexpr const int MIN_RESTRICTED_HOME_SDK_VERSION = 24; // > M @@ -3539,16 +3541,28 @@ binder::Status InstalldNativeService::tryMountDataMirror( std::string mirrorVolCePath(StringPrintf("%s/%s", kDataMirrorCePath, uuid_)); if (fs_prepare_dir(mirrorVolCePath.c_str(), 0711, AID_SYSTEM, AID_SYSTEM) != 0) { - return error("Failed to create CE mirror"); + return error("Failed to create CE data mirror"); } std::string mirrorVolDePath(StringPrintf("%s/%s", kDataMirrorDePath, uuid_)); if (fs_prepare_dir(mirrorVolDePath.c_str(), 0711, AID_SYSTEM, AID_SYSTEM) != 0) { - return error("Failed to create DE mirror"); + return error("Failed to create DE data mirror"); + } + + std::string mirrorVolMiscCePath(StringPrintf("%s/%s", kMiscMirrorCePath, uuid_)); + if (fs_prepare_dir(mirrorVolMiscCePath.c_str(), 0711, AID_SYSTEM, AID_SYSTEM) != 0) { + return error("Failed to create CE misc mirror"); + } + + std::string mirrorVolMiscDePath(StringPrintf("%s/%s", kMiscMirrorDePath, uuid_)); + if (fs_prepare_dir(mirrorVolMiscDePath.c_str(), 0711, AID_SYSTEM, AID_SYSTEM) != 0) { + return error("Failed to create DE misc mirror"); } auto cePath = StringPrintf("%s/user", create_data_path(uuid_).c_str()); auto dePath = StringPrintf("%s/user_de", create_data_path(uuid_).c_str()); + auto miscCePath = StringPrintf("%s/misc_ce", create_data_path(uuid_).c_str()); + auto miscDePath = StringPrintf("%s/misc_de", create_data_path(uuid_).c_str()); if (access(cePath.c_str(), F_OK) != 0) { return error("Cannot access CE path: " + cePath); @@ -3556,6 +3570,12 @@ binder::Status InstalldNativeService::tryMountDataMirror( if (access(dePath.c_str(), F_OK) != 0) { return error("Cannot access DE path: " + dePath); } + if (access(miscCePath.c_str(), F_OK) != 0) { + return error("Cannot access misc CE path: " + cePath); + } + if (access(miscDePath.c_str(), F_OK) != 0) { + return error("Cannot access misc DE path: " + dePath); + } struct stat ceStat, mirrorCeStat; if (stat(cePath.c_str(), &ceStat) != 0) { @@ -3583,6 +3603,21 @@ binder::Status InstalldNativeService::tryMountDataMirror( MS_NOSUID | MS_NODEV | MS_NOATIME | MS_BIND | MS_NOEXEC, nullptr)) == -1) { return error("Failed to mount " + mirrorVolDePath); } + + // Mount misc CE mirror + if (TEMP_FAILURE_RETRY(mount(miscCePath.c_str(), mirrorVolMiscCePath.c_str(), NULL, + MS_NOSUID | MS_NODEV | MS_NOATIME | MS_BIND | MS_NOEXEC, + nullptr)) == -1) { + return error("Failed to mount " + mirrorVolMiscCePath); + } + + // Mount misc DE mirror + if (TEMP_FAILURE_RETRY(mount(miscDePath.c_str(), mirrorVolMiscDePath.c_str(), NULL, + MS_NOSUID | MS_NODEV | MS_NOATIME | MS_BIND | MS_NOEXEC, + nullptr)) == -1) { + return error("Failed to mount " + mirrorVolMiscDePath); + } + return ok(); } @@ -3605,6 +3640,8 @@ binder::Status InstalldNativeService::onPrivateVolumeRemoved( std::string mirrorCeVolPath(StringPrintf("%s/%s", kDataMirrorCePath, uuid_)); std::string mirrorDeVolPath(StringPrintf("%s/%s", kDataMirrorDePath, uuid_)); + std::string mirrorMiscCeVolPath(StringPrintf("%s/%s", kMiscMirrorCePath, uuid_)); + std::string mirrorMiscDeVolPath(StringPrintf("%s/%s", kMiscMirrorDePath, uuid_)); std::lock_guard<std::recursive_mutex> lock(mMountsLock); @@ -3629,6 +3666,29 @@ binder::Status InstalldNativeService::onPrivateVolumeRemoved( if (delete_dir_contents_and_dir(mirrorDeVolPath, true) != 0) { res = error("Failed to delete " + mirrorDeVolPath); } + + // Unmount misc CE storage + if (TEMP_FAILURE_RETRY(umount(mirrorMiscCeVolPath.c_str())) != 0) { + if (errno != ENOENT) { + res = error(StringPrintf("Failed to umount %s %s", mirrorMiscCeVolPath.c_str(), + strerror(errno))); + } + } + if (delete_dir_contents_and_dir(mirrorMiscCeVolPath, true) != 0) { + res = error("Failed to delete " + mirrorMiscCeVolPath); + } + + // Unmount misc DE storage + if (TEMP_FAILURE_RETRY(umount(mirrorMiscDeVolPath.c_str())) != 0) { + if (errno != ENOENT) { + res = error(StringPrintf("Failed to umount %s %s", mirrorMiscDeVolPath.c_str(), + strerror(errno))); + } + } + if (delete_dir_contents_and_dir(mirrorMiscDeVolPath, true) != 0) { + res = error("Failed to delete " + mirrorMiscDeVolPath); + } + return res; } diff --git a/include/android/keycodes.h b/include/android/keycodes.h index 214559d683..3357660f5c 100644 --- a/include/android/keycodes.h +++ b/include/android/keycodes.h @@ -776,7 +776,39 @@ enum { AKEYCODE_THUMBS_DOWN = 287, /** Used to switch current account that is consuming content. * May be consumed by system to switch current viewer profile. */ - AKEYCODE_PROFILE_SWITCH = 288 + AKEYCODE_PROFILE_SWITCH = 288, + /** Video Application key #1. */ + AKEYCODE_VIDEO_APP_1 = 289, + /** Video Application key #2. */ + AKEYCODE_VIDEO_APP_2 = 290, + /** Video Application key #3. */ + AKEYCODE_VIDEO_APP_3 = 291, + /** Video Application key #4. */ + AKEYCODE_VIDEO_APP_4 = 292, + /** Video Application key #5. */ + AKEYCODE_VIDEO_APP_5 = 293, + /** Video Application key #6. */ + AKEYCODE_VIDEO_APP_6 = 294, + /** Video Application key #7. */ + AKEYCODE_VIDEO_APP_7 = 295, + /** Video Application key #8. */ + AKEYCODE_VIDEO_APP_8 = 296, + /** Featured Application key #1. */ + AKEYCODE_FEATURED_APP_1 = 297, + /** Featured Application key #2. */ + AKEYCODE_FEATURED_APP_2 = 298, + /** Featured Application key #3. */ + AKEYCODE_FEATURED_APP_3 = 299, + /** Featured Application key #4. */ + AKEYCODE_FEATURED_APP_4 = 300, + /** Demo Application key #1. */ + AKEYCODE_DEMO_APP_1 = 301, + /** Demo Application key #2. */ + AKEYCODE_DEMO_APP_2 = 302, + /** Demo Application key #3. */ + AKEYCODE_DEMO_APP_3 = 303, + /** Demo Application key #4. */ + AKEYCODE_DEMO_APP_4 = 304, // NOTE: If you add a new keycode here you must also add it to several other files. // Refer to frameworks/base/core/java/android/view/KeyEvent.java for the full list. diff --git a/libs/graphicsenv/Android.bp b/libs/graphicsenv/Android.bp index a96a07a9b8..af50a2980c 100644 --- a/libs/graphicsenv/Android.bp +++ b/libs/graphicsenv/Android.bp @@ -27,10 +27,13 @@ cc_library_shared { srcs: [ "GpuStatsInfo.cpp", "GraphicsEnv.cpp", - "IGpuService.cpp" + "IGpuService.cpp", ], - cflags: ["-Wall", "-Werror"], + cflags: [ + "-Wall", + "-Werror", + ], shared_libs: [ "libbase", @@ -46,4 +49,13 @@ cc_library_shared { ], export_include_dirs: ["include"], + + product_variables: { + // `debuggable` is set for eng and userdebug builds + debuggable: { + cflags: [ + "-DANDROID_DEBUGGABLE", + ], + }, + }, } diff --git a/libs/graphicsenv/GraphicsEnv.cpp b/libs/graphicsenv/GraphicsEnv.cpp index 7f0cac5d4f..e86881a880 100644 --- a/libs/graphicsenv/GraphicsEnv.cpp +++ b/libs/graphicsenv/GraphicsEnv.cpp @@ -126,7 +126,20 @@ static const std::string getSystemNativeLibraries(NativeLibrary type) { } bool GraphicsEnv::isDebuggable() { - return prctl(PR_GET_DUMPABLE, 0, 0, 0, 0) > 0; + // This flag determines if the application is marked debuggable + bool appDebuggable = prctl(PR_GET_DUMPABLE, 0, 0, 0, 0) > 0; + + // This flag is set only in `debuggable` builds of the platform +#if defined(ANDROID_DEBUGGABLE) + bool platformDebuggable = true; +#else + bool platformDebuggable = false; +#endif + + ALOGV("GraphicsEnv::isDebuggable returning appDebuggable=%s || platformDebuggable=%s", + appDebuggable ? "true" : "false", platformDebuggable ? "true" : "false"); + + return appDebuggable || platformDebuggable; } void GraphicsEnv::setDriverPathAndSphalLibraries(const std::string path, diff --git a/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h b/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h index 56d1139f57..098e4a6507 100644 --- a/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h +++ b/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h @@ -35,7 +35,7 @@ public: // Check if the process is debuggable. It returns false except in any of the // following circumstances: - // 1. ro.debuggable=1 (global debuggable enabled). + // 1. ANDROID_DEBUGGABLE is defined (global debuggable enabled). // 2. android:debuggable="true" in the manifest for an individual app. // 3. An app which explicitly calls prctl(PR_SET_DUMPABLE, 1). // 4. GraphicsEnv calls prctl(PR_SET_DUMPABLE, 1) in the presence of diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp index c05b542d42..9915f6a9ea 100644 --- a/libs/gui/BLASTBufferQueue.cpp +++ b/libs/gui/BLASTBufferQueue.cpp @@ -424,11 +424,12 @@ void BLASTBufferQueue::transactionCallback(nsecs_t /*latchTime*/, const sp<Fence } } for (const auto& staleRelease : staleReleases) { - BQA_LOGE("Faking releaseBufferCallback from transactionCompleteCallback"); - BBQ_TRACE("FakeReleaseCallback"); releaseBufferCallbackLocked(staleRelease, - stat.previousReleaseFence ? stat.previousReleaseFence : Fence::NO_FENCE, - stat.currentMaxAcquiredBufferCount); + stat.previousReleaseFence + ? stat.previousReleaseFence + : Fence::NO_FENCE, + stat.currentMaxAcquiredBufferCount, + true /* fakeRelease */); } } else { BQA_LOGE("Failed to find matching SurfaceControl in transactionCallback"); @@ -472,11 +473,13 @@ void BLASTBufferQueue::releaseBufferCallback( BBQ_TRACE(); std::unique_lock _lock{mMutex}; - releaseBufferCallbackLocked(id, releaseFence, currentMaxAcquiredBufferCount); + releaseBufferCallbackLocked(id, releaseFence, currentMaxAcquiredBufferCount, + false /* fakeRelease */); } -void BLASTBufferQueue::releaseBufferCallbackLocked(const ReleaseCallbackId& id, - const sp<Fence>& releaseFence, std::optional<uint32_t> currentMaxAcquiredBufferCount) { +void BLASTBufferQueue::releaseBufferCallbackLocked( + const ReleaseCallbackId& id, const sp<Fence>& releaseFence, + std::optional<uint32_t> currentMaxAcquiredBufferCount, bool fakeRelease) { ATRACE_CALL(); BQA_LOGV("releaseBufferCallback %s", id.to_string().c_str()); @@ -499,6 +502,11 @@ void BLASTBufferQueue::releaseBufferCallbackLocked(const ReleaseCallbackId& id, auto rb = ReleasedBuffer{id, releaseFence}; if (std::find(mPendingRelease.begin(), mPendingRelease.end(), rb) == mPendingRelease.end()) { mPendingRelease.emplace_back(rb); + if (fakeRelease) { + BQA_LOGE("Faking releaseBufferCallback from transactionCompleteCallback %" PRIu64, + id.framenumber); + BBQ_TRACE("FakeReleaseCallback"); + } } // Release all buffers that are beyond the ones that we need to hold diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp index 502031c8d8..74e6ae6a9b 100644 --- a/libs/gui/LayerState.cpp +++ b/libs/gui/LayerState.cpp @@ -353,6 +353,27 @@ void DisplayState::merge(const DisplayState& other) { } } +void DisplayState::sanitize(int32_t permissions) { + if (what & DisplayState::eLayerStackChanged) { + if (!(permissions & layer_state_t::Permission::ACCESS_SURFACE_FLINGER)) { + what &= ~DisplayState::eLayerStackChanged; + ALOGE("Stripped attempt to set eLayerStackChanged in sanitize"); + } + } + if (what & DisplayState::eDisplayProjectionChanged) { + if (!(permissions & layer_state_t::Permission::ACCESS_SURFACE_FLINGER)) { + what &= ~DisplayState::eDisplayProjectionChanged; + ALOGE("Stripped attempt to set eDisplayProjectionChanged in sanitize"); + } + } + if (what & DisplayState::eSurfaceChanged) { + if (!(permissions & layer_state_t::Permission::ACCESS_SURFACE_FLINGER)) { + what &= ~DisplayState::eSurfaceChanged; + ALOGE("Stripped attempt to set eSurfaceChanged in sanitize"); + } + } +} + void layer_state_t::sanitize(int32_t permissions) { // TODO: b/109894387 // diff --git a/libs/gui/include/gui/BLASTBufferQueue.h b/libs/gui/include/gui/BLASTBufferQueue.h index 75951e6050..91bf51ef58 100644 --- a/libs/gui/include/gui/BLASTBufferQueue.h +++ b/libs/gui/include/gui/BLASTBufferQueue.h @@ -103,7 +103,8 @@ public: void releaseBufferCallback(const ReleaseCallbackId& id, const sp<Fence>& releaseFence, std::optional<uint32_t> currentMaxAcquiredBufferCount); void releaseBufferCallbackLocked(const ReleaseCallbackId& id, const sp<Fence>& releaseFence, - std::optional<uint32_t> currentMaxAcquiredBufferCount); + std::optional<uint32_t> currentMaxAcquiredBufferCount, + bool fakeRelease); void syncNextTransaction(std::function<void(SurfaceComposerClient::Transaction*)> callback, bool acquireSingleBuffer = true); void stopContinuousSyncTransaction(); diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h index adb7b9495d..91e65abc25 100644 --- a/libs/gui/include/gui/LayerState.h +++ b/libs/gui/include/gui/LayerState.h @@ -321,6 +321,7 @@ struct DisplayState { DisplayState(); void merge(const DisplayState& other); + void sanitize(int32_t permissions); uint32_t what = 0; uint32_t flags = 0; diff --git a/libs/input/InputEventLabels.cpp b/libs/input/InputEventLabels.cpp index c0aa2e26a2..2d768ce573 100644 --- a/libs/input/InputEventLabels.cpp +++ b/libs/input/InputEventLabels.cpp @@ -314,7 +314,23 @@ namespace android { DEFINE_KEYCODE(REFRESH), \ DEFINE_KEYCODE(THUMBS_UP), \ DEFINE_KEYCODE(THUMBS_DOWN), \ - DEFINE_KEYCODE(PROFILE_SWITCH) + DEFINE_KEYCODE(PROFILE_SWITCH), \ + DEFINE_KEYCODE(VIDEO_APP_1), \ + DEFINE_KEYCODE(VIDEO_APP_2), \ + DEFINE_KEYCODE(VIDEO_APP_3), \ + DEFINE_KEYCODE(VIDEO_APP_4), \ + DEFINE_KEYCODE(VIDEO_APP_5), \ + DEFINE_KEYCODE(VIDEO_APP_6), \ + DEFINE_KEYCODE(VIDEO_APP_7), \ + DEFINE_KEYCODE(VIDEO_APP_8), \ + DEFINE_KEYCODE(FEATURED_APP_1), \ + DEFINE_KEYCODE(FEATURED_APP_2), \ + DEFINE_KEYCODE(FEATURED_APP_3), \ + DEFINE_KEYCODE(FEATURED_APP_4), \ + DEFINE_KEYCODE(DEMO_APP_1), \ + DEFINE_KEYCODE(DEMO_APP_2), \ + DEFINE_KEYCODE(DEMO_APP_3), \ + DEFINE_KEYCODE(DEMO_APP_4) // NOTE: If you add a new axis here you must also add it to several other files. // Refer to frameworks/base/core/java/android/view/MotionEvent.java for the full list. diff --git a/libs/renderengine/gl/GLESRenderEngine.cpp b/libs/renderengine/gl/GLESRenderEngine.cpp index 22dd86698b..6dc01b916e 100644 --- a/libs/renderengine/gl/GLESRenderEngine.cpp +++ b/libs/renderengine/gl/GLESRenderEngine.cpp @@ -921,7 +921,8 @@ void GLESRenderEngine::handleRoundedCorners(const DisplaySettings& display, // Finally, we cut the layer into 3 parts, with top and bottom parts having rounded corners // and the middle part without rounded corners. - const int32_t radius = ceil(layer.geometry.roundedCornersRadius); + const int32_t radius = ceil( + (layer.geometry.roundedCornersRadius.x + layer.geometry.roundedCornersRadius.y) / 2.0); const Rect topRect(bounds.left, bounds.top, bounds.right, bounds.top + radius); setScissor(topRect); drawMesh(mesh); @@ -1266,23 +1267,24 @@ void GLESRenderEngine::drawLayersInternal( const half3 solidColor = layer.source.solidColor; const half4 color = half4(solidColor.r, solidColor.g, solidColor.b, layer.alpha); + const float radius = + (layer.geometry.roundedCornersRadius.x + layer.geometry.roundedCornersRadius.y) / + 2.0f; // Buffer sources will have a black solid color ignored in the shader, // so in that scenario the solid color passed here is arbitrary. - setupLayerBlending(usePremultipliedAlpha, isOpaque, disableTexture, color, - layer.geometry.roundedCornersRadius); + setupLayerBlending(usePremultipliedAlpha, isOpaque, disableTexture, color, radius); if (layer.disableBlending) { glDisable(GL_BLEND); } setSourceDataSpace(layer.sourceDataspace); if (layer.shadow.length > 0.0f) { - handleShadow(layer.geometry.boundaries, layer.geometry.roundedCornersRadius, - layer.shadow); + handleShadow(layer.geometry.boundaries, radius, layer.shadow); } // We only want to do a special handling for rounded corners when having rounded corners // is the only reason it needs to turn on blending, otherwise, we handle it like the // usual way since it needs to turn on blending anyway. - else if (layer.geometry.roundedCornersRadius > 0.0 && color.a >= 1.0f && isOpaque) { + else if (radius > 0.0 && color.a >= 1.0f && isOpaque) { handleRoundedCorners(display, layer, mesh); } else { drawMesh(mesh); diff --git a/libs/renderengine/include/renderengine/LayerSettings.h b/libs/renderengine/include/renderengine/LayerSettings.h index 154e5269f0..b3a617c04b 100644 --- a/libs/renderengine/include/renderengine/LayerSettings.h +++ b/libs/renderengine/include/renderengine/LayerSettings.h @@ -87,7 +87,7 @@ struct Geometry { // rectangle to figure out how to apply the radius for this layer. The crop rectangle will be // in local layer coordinate space, so we have to take the layer transform into account when // walking up the tree. - float roundedCornersRadius = 0.0; + vec2 roundedCornersRadius = vec2(0.0f, 0.0f); // Rectangle within which corners will be rounded. FloatRect roundedCornersCrop = FloatRect(); @@ -258,7 +258,8 @@ static inline void PrintTo(const Geometry& settings, ::std::ostream* os) { PrintTo(settings.boundaries, os); *os << "\n .positionTransform = "; PrintMatrix(settings.positionTransform, os); - *os << "\n .roundedCornersRadius = " << settings.roundedCornersRadius; + *os << "\n .roundedCornersRadiusX = " << settings.roundedCornersRadius.x; + *os << "\n .roundedCornersRadiusY = " << settings.roundedCornersRadius.y; *os << "\n .roundedCornersCrop = "; PrintTo(settings.roundedCornersCrop, os); *os << "\n}"; diff --git a/libs/renderengine/skia/Cache.cpp b/libs/renderengine/skia/Cache.cpp index f3064f3c69..c39f0a97fd 100644 --- a/libs/renderengine/skia/Cache.cpp +++ b/libs/renderengine/skia/Cache.cpp @@ -66,7 +66,7 @@ static void drawShadowLayers(SkiaRenderEngine* renderengine, const DisplaySettin Geometry{ .boundaries = rect, .roundedCornersCrop = rect, - .roundedCornersRadius = 50.f, + .roundedCornersRadius = {50.f, 50.f}, }, // drawShadow ignores alpha .shadow = @@ -87,7 +87,7 @@ static void drawShadowLayers(SkiaRenderEngine* renderengine, const DisplaySettin Geometry{ .boundaries = smallerRect, .roundedCornersCrop = rect, - .roundedCornersRadius = 50.f, + .roundedCornersRadius = {50.f, 50.f}, }, .source = PixelSource{ @@ -148,7 +148,7 @@ static void drawImageLayers(SkiaRenderEngine* renderengine, const DisplaySetting // In reduced shader mode, all non-zero round rect radii get the same code path. for (float roundedCornersRadius : {0.0f, 50.0f}) { // roundedCornersCrop is always set, but the radius triggers the behavior - layer.geometry.roundedCornersRadius = roundedCornersRadius; + layer.geometry.roundedCornersRadius = {roundedCornersRadius, roundedCornersRadius}; for (bool isOpaque : {true, false}) { layer.source.buffer.isOpaque = isOpaque; for (auto alpha : {half(.2f), half(1.0f)}) { @@ -181,7 +181,7 @@ static void drawSolidLayers(SkiaRenderEngine* renderengine, const DisplaySetting for (auto transform : {mat4(), kScaleAndTranslate}) { layer.geometry.positionTransform = transform; for (float roundedCornersRadius : {0.0f, 50.f}) { - layer.geometry.roundedCornersRadius = roundedCornersRadius; + layer.geometry.roundedCornersRadius = {roundedCornersRadius, roundedCornersRadius}; auto layers = std::vector<LayerSettings>{layer}; renderengine->drawLayers(display, layers, dstTexture, kUseFrameBufferCache, base::unique_fd()); @@ -238,7 +238,7 @@ static void drawClippedLayers(SkiaRenderEngine* renderengine, const DisplaySetti .geometry = Geometry{ .boundaries = rect, - .roundedCornersRadius = 27, // larger than the 20 above. + .roundedCornersRadius = {27.f, 27.f}, .roundedCornersCrop = FloatRect(0, 0, displayRect.width(), displayRect.height()), }, @@ -275,9 +275,9 @@ static void drawPIPImageLayer(SkiaRenderEngine* renderengine, const DisplaySetti // larger than the layer bounds. .positionTransform = kFlip, .boundaries = rect, - .roundedCornersRadius = 94.2551, - .roundedCornersCrop = FloatRect( - -93.75, 0, displayRect.width() + 93.75, displayRect.height()), + .roundedCornersRadius = {94.2551f, 94.2551f}, + .roundedCornersCrop = FloatRect(-93.75, 0, displayRect.width() + 93.75, + displayRect.height()), }, .source = PixelSource{.buffer = Buffer{ @@ -307,10 +307,11 @@ static void drawHolePunchLayer(SkiaRenderEngine* renderengine, const DisplaySett // the boundaries have to be smaller than the rounded crop so that // clipRRect is used instead of drawRRect .boundaries = small, - .roundedCornersRadius = 50.f, + .roundedCornersRadius = {50.f, 50.f}, .roundedCornersCrop = rect, }, - .source = PixelSource{ + .source = + PixelSource{ .solidColor = half3(0.f, 0.f, 0.f), }, .sourceDataspace = kDestDataSpace, diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp index 97271cb32d..0caa9f2fbd 100644 --- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp @@ -1276,7 +1276,7 @@ inline SkRect SkiaGLRenderEngine::getSkRect(const Rect& rect) { * produce the insected roundRect. If false, the returned state of the radii param is undefined. */ static bool intersectionIsRoundRect(const SkRect& bounds, const SkRect& crop, - const SkRect& insetCrop, float cornerRadius, + const SkRect& insetCrop, const vec2& cornerRadius, SkVector radii[4]) { const bool leftEqual = bounds.fLeft == crop.fLeft; const bool topEqual = bounds.fTop == crop.fTop; @@ -1288,8 +1288,8 @@ static bool intersectionIsRoundRect(const SkRect& bounds, const SkRect& crop, // In particular the round rect implementation will scale the value of all corner radii // if the sum of the radius along any edge is greater than the length of that edge. // See https://www.w3.org/TR/css-backgrounds-3/#corner-overlap - const bool requiredWidth = bounds.width() > (cornerRadius * 2); - const bool requiredHeight = bounds.height() > (cornerRadius * 2); + const bool requiredWidth = bounds.width() > (cornerRadius.x * 2); + const bool requiredHeight = bounds.height() > (cornerRadius.y * 2); if (!requiredWidth || !requiredHeight) { return false; } @@ -1298,7 +1298,7 @@ static bool intersectionIsRoundRect(const SkRect& bounds, const SkRect& crop, // contained within the cropped shape and does not need rounded. // compute the UpperLeft corner radius if (leftEqual && topEqual) { - radii[0].set(cornerRadius, cornerRadius); + radii[0].set(cornerRadius.x, cornerRadius.y); } else if ((leftEqual && bounds.fTop >= insetCrop.fTop) || (topEqual && bounds.fLeft >= insetCrop.fLeft)) { radii[0].set(0, 0); @@ -1307,7 +1307,7 @@ static bool intersectionIsRoundRect(const SkRect& bounds, const SkRect& crop, } // compute the UpperRight corner radius if (rightEqual && topEqual) { - radii[1].set(cornerRadius, cornerRadius); + radii[1].set(cornerRadius.x, cornerRadius.y); } else if ((rightEqual && bounds.fTop >= insetCrop.fTop) || (topEqual && bounds.fRight <= insetCrop.fRight)) { radii[1].set(0, 0); @@ -1316,7 +1316,7 @@ static bool intersectionIsRoundRect(const SkRect& bounds, const SkRect& crop, } // compute the BottomRight corner radius if (rightEqual && bottomEqual) { - radii[2].set(cornerRadius, cornerRadius); + radii[2].set(cornerRadius.x, cornerRadius.y); } else if ((rightEqual && bounds.fBottom <= insetCrop.fBottom) || (bottomEqual && bounds.fRight <= insetCrop.fRight)) { radii[2].set(0, 0); @@ -1325,7 +1325,7 @@ static bool intersectionIsRoundRect(const SkRect& bounds, const SkRect& crop, } // compute the BottomLeft corner radius if (leftEqual && bottomEqual) { - radii[3].set(cornerRadius, cornerRadius); + radii[3].set(cornerRadius.x, cornerRadius.y); } else if ((leftEqual && bounds.fBottom <= insetCrop.fBottom) || (bottomEqual && bounds.fLeft >= insetCrop.fLeft)) { radii[3].set(0, 0); @@ -1338,22 +1338,22 @@ static bool intersectionIsRoundRect(const SkRect& bounds, const SkRect& crop, inline std::pair<SkRRect, SkRRect> SkiaGLRenderEngine::getBoundsAndClip(const FloatRect& boundsRect, const FloatRect& cropRect, - const float cornerRadius) { + const vec2& cornerRadius) { const SkRect bounds = getSkRect(boundsRect); const SkRect crop = getSkRect(cropRect); SkRRect clip; - if (cornerRadius > 0) { + if (cornerRadius.x > 0 && cornerRadius.y > 0) { // it the crop and the bounds are equivalent or there is no crop then we don't need a clip if (bounds == crop || crop.isEmpty()) { - return {SkRRect::MakeRectXY(bounds, cornerRadius, cornerRadius), clip}; + return {SkRRect::MakeRectXY(bounds, cornerRadius.x, cornerRadius.y), clip}; } // This makes an effort to speed up common, simple bounds + clip combinations by // converting them to a single RRect draw. It is possible there are other cases // that can be converted. if (crop.contains(bounds)) { - const auto insetCrop = crop.makeInset(cornerRadius, cornerRadius); + const auto insetCrop = crop.makeInset(cornerRadius.x, cornerRadius.y); if (insetCrop.contains(bounds)) { return {SkRRect::MakeRect(bounds), clip}; // clip is empty - no rounding required } @@ -1367,7 +1367,7 @@ inline std::pair<SkRRect, SkRRect> SkiaGLRenderEngine::getBoundsAndClip(const Fl } // we didn't hit any of our fast paths so set the clip to the cropRect - clip.setRectXY(crop, cornerRadius, cornerRadius); + clip.setRectXY(crop, cornerRadius.x, cornerRadius.y); } // if we hit this point then we either don't have rounded corners or we are going to rely diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.h b/libs/renderengine/skia/SkiaGLRenderEngine.h index 0f6332226e..9d5fda6894 100644 --- a/libs/renderengine/skia/SkiaGLRenderEngine.h +++ b/libs/renderengine/skia/SkiaGLRenderEngine.h @@ -91,7 +91,8 @@ private: inline SkRect getSkRect(const FloatRect& layer); inline SkRect getSkRect(const Rect& layer); inline std::pair<SkRRect, SkRRect> getBoundsAndClip(const FloatRect& bounds, - const FloatRect& crop, float cornerRadius); + const FloatRect& crop, + const vec2& cornerRadius); inline bool layerHasBlur(const LayerSettings& layer, bool colorTransformModifiesAlpha); inline SkColor getSkColor(const vec4& color); inline SkM44 getSkM44(const mat4& matrix); diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp index 7c70a748b5..8889f76ccf 100644 --- a/libs/renderengine/tests/RenderEngineTest.cpp +++ b/libs/renderengine/tests/RenderEngineTest.cpp @@ -444,7 +444,9 @@ public: const ubyte4& backgroundColor) { const Rect casterRect(castingLayer.geometry.boundaries); Region casterRegion = Region(casterRect); - const float casterCornerRadius = castingLayer.geometry.roundedCornersRadius; + const float casterCornerRadius = (castingLayer.geometry.roundedCornersRadius.x + + castingLayer.geometry.roundedCornersRadius.y) / + 2.0; if (casterCornerRadius > 0.0f) { // ignore the corners if a corner radius is set Rect cornerRect(casterCornerRadius, casterCornerRadius); @@ -1129,7 +1131,7 @@ void RenderEngineTest::fillRedBufferWithRoundedCorners() { renderengine::LayerSettings layer; layer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR; layer.geometry.boundaries = fullscreenRect().toFloatRect(); - layer.geometry.roundedCornersRadius = 5.0f; + layer.geometry.roundedCornersRadius = {5.0f, 5.0f}; layer.geometry.roundedCornersCrop = fullscreenRect().toFloatRect(); SourceVariant::fillColor(layer, 1.0f, 0.0f, 0.0f, this); layer.alpha = 1.0f; @@ -2131,7 +2133,7 @@ TEST_P(RenderEngineTest, drawLayers_fillShadow_casterWithRoundedCorner) { casterBounds.offsetBy(shadowLength + 1, shadowLength + 1); renderengine::LayerSettings castingLayer; castingLayer.geometry.boundaries = casterBounds.toFloatRect(); - castingLayer.geometry.roundedCornersRadius = 3.0f; + castingLayer.geometry.roundedCornersRadius = {3.0f, 3.0f}; castingLayer.geometry.roundedCornersCrop = casterBounds.toFloatRect(); castingLayer.alpha = 1.0f; renderengine::ShadowSettings settings = @@ -2219,7 +2221,8 @@ TEST_P(RenderEngineTest, testRoundedCornersCrop) { renderengine::LayerSettings redLayer; redLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR; redLayer.geometry.boundaries = fullscreenRect().toFloatRect(); - redLayer.geometry.roundedCornersRadius = 5.0f; + redLayer.geometry.roundedCornersRadius = {5.0f, 5.0f}; + redLayer.geometry.roundedCornersCrop = fullscreenRect().toFloatRect(); // Red background. redLayer.source.solidColor = half3(1.0f, 0.0f, 0.0f); @@ -2231,7 +2234,7 @@ TEST_P(RenderEngineTest, testRoundedCornersCrop) { renderengine::LayerSettings greenLayer; greenLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR; greenLayer.geometry.boundaries = fullscreenRect().toFloatRect(); - greenLayer.geometry.roundedCornersRadius = 5.0f; + greenLayer.geometry.roundedCornersRadius = {5.0f, 5.0f}; // Bottom right corner is not going to be rounded. greenLayer.geometry.roundedCornersCrop = Rect(DEFAULT_DISPLAY_WIDTH / 3, DEFAULT_DISPLAY_HEIGHT / 3, DEFAULT_DISPLAY_HEIGHT, @@ -2268,7 +2271,7 @@ TEST_P(RenderEngineTest, testRoundedCornersParentCrop) { renderengine::LayerSettings redLayer; redLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR; redLayer.geometry.boundaries = fullscreenRect().toFloatRect(); - redLayer.geometry.roundedCornersRadius = 5.0f; + redLayer.geometry.roundedCornersRadius = {5.0f, 5.0f}; redLayer.geometry.roundedCornersCrop = fullscreenRect().toFloatRect(); // Red background. redLayer.source.solidColor = half3(1.0f, 0.0f, 0.0f); @@ -2313,7 +2316,7 @@ TEST_P(RenderEngineTest, testRoundedCornersParentCropSmallBounds) { renderengine::LayerSettings redLayer; redLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR; redLayer.geometry.boundaries = FloatRect(0, 0, DEFAULT_DISPLAY_WIDTH, 32); - redLayer.geometry.roundedCornersRadius = 64; + redLayer.geometry.roundedCornersRadius = {64.0f, 64.0f}; redLayer.geometry.roundedCornersCrop = FloatRect(0, 0, DEFAULT_DISPLAY_WIDTH, 128); // Red background. redLayer.source.solidColor = half3(1.0f, 0.0f, 0.0f); @@ -2334,6 +2337,49 @@ TEST_P(RenderEngineTest, testRoundedCornersParentCropSmallBounds) { expectBufferColor(Point(DEFAULT_DISPLAY_WIDTH / 2, 31), 255, 0, 0, 255); } +TEST_P(RenderEngineTest, testRoundedCornersXY) { + if (GetParam()->type() != renderengine::RenderEngine::RenderEngineType::SKIA_GL) { + GTEST_SKIP(); + } + + initializeRenderEngine(); + + renderengine::DisplaySettings settings; + settings.physicalDisplay = fullscreenRect(); + settings.clip = fullscreenRect(); + settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR; + + std::vector<renderengine::LayerSettings> layers; + + renderengine::LayerSettings redLayer; + redLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR; + redLayer.geometry.boundaries = fullscreenRect().toFloatRect(); + redLayer.geometry.roundedCornersRadius = {5.0f, 20.0f}; + redLayer.geometry.roundedCornersCrop = fullscreenRect().toFloatRect(); + // Red background. + redLayer.source.solidColor = half3(1.0f, 0.0f, 0.0f); + redLayer.alpha = 1.0f; + + layers.push_back(redLayer); + + invokeDraw(settings, layers); + + // Due to roundedCornersRadius, the corners are untouched. + expectBufferColor(Point(0, 0), 0, 0, 0, 0); + expectBufferColor(Point(DEFAULT_DISPLAY_WIDTH - 1, 0), 0, 0, 0, 0); + expectBufferColor(Point(0, DEFAULT_DISPLAY_HEIGHT - 1), 0, 0, 0, 0); + expectBufferColor(Point(DEFAULT_DISPLAY_WIDTH - 1, DEFAULT_DISPLAY_HEIGHT - 1), 0, 0, 0, 0); + + // Y-axis draws a larger radius, check that its untouched as well + expectBufferColor(Point(0, DEFAULT_DISPLAY_HEIGHT - 5), 0, 0, 0, 0); + expectBufferColor(Point(DEFAULT_DISPLAY_WIDTH - 1, DEFAULT_DISPLAY_HEIGHT - 5), 0, 0, 0, 0); + expectBufferColor(Point(DEFAULT_DISPLAY_WIDTH - 1, 5), 0, 0, 0, 0); + expectBufferColor(Point(0, 5), 0, 0, 0, 0); + + // middle should be red + expectBufferColor(Point(DEFAULT_DISPLAY_WIDTH / 2, DEFAULT_DISPLAY_HEIGHT / 2), 255, 0, 0, 255); +} + TEST_P(RenderEngineTest, testClear) { initializeRenderEngine(); diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp index 41a8426e42..cc323ad42e 100644 --- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp @@ -48,11 +48,8 @@ void MultiTouchMotionAccumulator::configure(InputDeviceContext& deviceContext, s delete[] mSlots; mSlots = new Slot[slotCount]; -} -void MultiTouchMotionAccumulator::reset(InputDeviceContext& deviceContext) { - // Unfortunately there is no way to read the initial contents of the slots. - // So when we reset the accumulator, we must assume they are all zeroes. + mCurrentSlot = -1; if (mUsingSlotsProtocol) { // Query the driver for the current slot index and use it as the initial slot // before we start reading events from the device. It is possible that the @@ -64,24 +61,22 @@ void MultiTouchMotionAccumulator::reset(InputDeviceContext& deviceContext) { // This can cause the touch point to "jump", but at least there will be // no stuck touches. int32_t initialSlot; - status_t status = deviceContext.getAbsoluteAxisValue(ABS_MT_SLOT, &initialSlot); - if (status) { - ALOGD("Could not retrieve current multitouch slot index. status=%d", status); - initialSlot = -1; + if (const auto status = deviceContext.getAbsoluteAxisValue(ABS_MT_SLOT, &initialSlot); + status == OK) { + mCurrentSlot = initialSlot; + } else { + ALOGD("Could not retrieve current multi-touch slot index. status=%d", status); } - clearSlots(initialSlot); - } else { - clearSlots(-1); } } -void MultiTouchMotionAccumulator::clearSlots(int32_t initialSlot) { +void MultiTouchMotionAccumulator::resetSlots() { if (mSlots) { for (size_t i = 0; i < mSlotCount; i++) { mSlots[i].clear(); } } - mCurrentSlot = initialSlot; + mCurrentSlot = -1; } void MultiTouchMotionAccumulator::process(const RawEvent* rawEvent) { @@ -169,7 +164,7 @@ void MultiTouchMotionAccumulator::process(const RawEvent* rawEvent) { void MultiTouchMotionAccumulator::finishSync() { if (!mUsingSlotsProtocol) { - clearSlots(-1); + resetSlots(); } } @@ -230,10 +225,12 @@ MultiTouchInputMapper::MultiTouchInputMapper(InputDeviceContext& deviceContext) MultiTouchInputMapper::~MultiTouchInputMapper() {} void MultiTouchInputMapper::reset(nsecs_t when) { - mMultiTouchMotionAccumulator.reset(getDeviceContext()); - - mPointerIdBits.clear(); - + // The evdev multi-touch protocol does not allow userspace applications to query the initial or + // current state of the pointers at any time. This means if we clear our accumulated state when + // resetting the input mapper, there's no way to rebuild the full initial state of the pointers. + // We can only wait for updates to all the pointers and axes. Rather than clearing the state and + // rebuilding the state from scratch, we work around this kernel API limitation by never + // fully clearing any state specific to the multi-touch protocol. TouchInputMapper::reset(when); } diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h index b7c3457285..e7d935082a 100644 --- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h +++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h @@ -71,7 +71,6 @@ public: ~MultiTouchMotionAccumulator(); void configure(InputDeviceContext& deviceContext, size_t slotCount, bool usingSlotsProtocol); - void reset(InputDeviceContext& deviceContext); void process(const RawEvent* rawEvent); void finishSync(); bool hasStylus() const; @@ -86,7 +85,7 @@ private: bool mUsingSlotsProtocol; bool mHaveStylus; - void clearSlots(int32_t initialSlot); + void resetSlots(); void warnIfNotInUse(const RawEvent& event, const Slot& slot); }; diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index 428fe10156..5a7016780a 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -1468,6 +1468,10 @@ void TouchInputMapper::process(const RawEvent* rawEvent) { } void TouchInputMapper::sync(nsecs_t when, nsecs_t readTime) { + if (mDeviceMode == DeviceMode::DISABLED) { + // Only save the last pending state when the device is disabled. + mRawStatesPending.clear(); + } // Push a new state. mRawStatesPending.emplace_back(); @@ -3517,6 +3521,8 @@ void TouchInputMapper::abortPointerMouse(nsecs_t when, nsecs_t readTime, uint32_ void TouchInputMapper::dispatchPointerSimple(nsecs_t when, nsecs_t readTime, uint32_t policyFlags, bool down, bool hovering) { + LOG_ALWAYS_FATAL_IF(mDeviceMode != DeviceMode::POINTER, + "%s cannot be used when the device is not in POINTER mode.", __func__); int32_t metaState = getContext()->getGlobalMetaState(); if (down || hovering) { @@ -3639,6 +3645,10 @@ void TouchInputMapper::dispatchPointerSimple(nsecs_t when, nsecs_t readTime, uin if (down || hovering) { mPointerSimple.lastCoords.copyFrom(mPointerSimple.currentCoords); mPointerSimple.lastProperties.copyFrom(mPointerSimple.currentProperties); + mPointerSimple.displayId = displayId; + mPointerSimple.source = mSource; + mPointerSimple.lastCursorX = xCursorPosition; + mPointerSimple.lastCursorY = yCursorPosition; } else { mPointerSimple.reset(); } @@ -3648,7 +3658,23 @@ void TouchInputMapper::abortPointerSimple(nsecs_t when, nsecs_t readTime, uint32 mPointerSimple.currentCoords.clear(); mPointerSimple.currentProperties.clear(); - dispatchPointerSimple(when, readTime, policyFlags, false, false); + if (mPointerSimple.down || mPointerSimple.hovering) { + int32_t metaState = getContext()->getGlobalMetaState(); + NotifyMotionArgs args(getContext()->getNextId(), when, readTime, getDeviceId(), + mPointerSimple.source, mPointerSimple.displayId, policyFlags, + AMOTION_EVENT_ACTION_CANCEL, 0, AMOTION_EVENT_FLAG_CANCELED, + metaState, mLastRawState.buttonState, MotionClassification::NONE, + AMOTION_EVENT_EDGE_FLAG_NONE, 1, &mPointerSimple.lastProperties, + &mPointerSimple.lastCoords, mOrientedXPrecision, mOrientedYPrecision, + mPointerSimple.lastCursorX, mPointerSimple.lastCursorY, + mPointerSimple.downTime, + /* videoFrames */ {}); + getListener().notifyMotion(&args); + if (mPointerController != nullptr) { + mPointerController->fade(PointerControllerInterface::Transition::GRADUAL); + } + } + mPointerSimple.reset(); } void TouchInputMapper::dispatchMotion(nsecs_t when, nsecs_t readTime, uint32_t policyFlags, diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h index c948f565d9..2937bf88d4 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchInputMapper.h @@ -327,6 +327,8 @@ protected: int32_t rawVScroll; int32_t rawHScroll; + explicit inline RawState() { clear(); } + void copyFrom(const RawState& other) { when = other.when; readTime = other.readTime; @@ -712,6 +714,12 @@ private: // Time the pointer last went down. nsecs_t downTime; + // Values reported for the last pointer event. + uint32_t source; + int32_t displayId; + float lastCursorX; + float lastCursorY; + void reset() { currentCoords.clear(); currentProperties.clear(); @@ -720,6 +728,10 @@ private: down = false; hovering = false; downTime = 0; + source = 0; + displayId = ADISPLAY_ID_NONE; + lastCursorX = 0.f; + lastCursorY = 0.f; } } mPointerSimple; diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index 74d4f3b99f..8a97901926 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -111,6 +111,13 @@ MATCHER_P2(WithCoords, x, y, "MotionEvent with specified action") { return arg.pointerCoords[0].getX() == x && arg.pointerCoords[0].getY(); } +MATCHER_P(WithToolType, toolType, "InputEvent with specified tool type") { + const auto argToolType = arg.pointerProperties[0].toolType; + *result_listener << "expected tool type " << motionToolTypeToString(toolType) << ", but got " + << motionToolTypeToString(argToolType); + return argToolType == toolType; +} + template<typename T> static inline T min(T a, T b) { return a < b ? a : b; @@ -6785,6 +6792,82 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenAbsPressureIsPresent_HoversIfItsV toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0)); } +TEST_F(SingleTouchInputMapperTest, Reset_RecreatesTouchState) { + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareButtons(); + prepareAxes(POSITION | PRESSURE); + SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>(); + NotifyMotionArgs motionArgs; + + // Set the initial state for the touch pointer. + mFakeEventHub->setAbsoluteAxisValue(EVENTHUB_ID, ABS_X, 100); + mFakeEventHub->setAbsoluteAxisValue(EVENTHUB_ID, ABS_Y, 200); + mFakeEventHub->setAbsoluteAxisValue(EVENTHUB_ID, ABS_PRESSURE, RAW_PRESSURE_MAX); + mFakeEventHub->setScanCodeState(EVENTHUB_ID, BTN_TOUCH, 1); + + // Reset the mapper. When the mapper is reset, we expect it to attempt to recreate the touch + // state by reading the current axis values. + mapper.reset(ARBITRARY_TIME); + + // Send a sync to simulate an empty touch frame where nothing changes. The mapper should use + // the recreated touch state to generate a down event. + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); +} + +TEST_F(SingleTouchInputMapperTest, WhenViewportActiveStatusChanged_PointerGestureIsReset) { + std::shared_ptr<FakePointerController> fakePointerController = + std::make_shared<FakePointerController>(); + fakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1); + fakePointerController->setPosition(100, 200); + fakePointerController->setButtonState(0); + mFakePolicy->setPointerController(fakePointerController); + + addConfigurationProperty("touch.deviceType", "pointer"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareButtons(); + mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOOL_PEN, 0, AKEYCODE_UNKNOWN, 0); + prepareAxes(POSITION); + SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>(); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled()); + + // Start a stylus gesture. + processKey(mapper, BTN_TOOL_PEN, 1); + processDown(mapper, 100, 200); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithAction(AMOTION_EVENT_ACTION_DOWN), + WithSource(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS), + WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS)))); + // TODO(b/257078296): Pointer mode generates extra event. + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithAction(AMOTION_EVENT_ACTION_MOVE), + WithSource(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS), + WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS)))); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); + + // Make the viewport inactive. This will put the device in disabled mode, and the ongoing stylus + // gesture should be disabled. + auto viewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL); + viewport->isActive = false; + mFakePolicy->updateViewport(*viewport); + configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithAction(AMOTION_EVENT_ACTION_CANCEL), + WithSource(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS), + WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS)))); + // TODO(b/257078296): Pointer mode generates extra event. + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithAction(AMOTION_EVENT_ACTION_CANCEL), + WithSource(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS), + WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS)))); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); +} + TEST_F(SingleTouchInputMapperTest, Process_WhenViewportDisplayIdChanged_TouchIsCanceledAndDeviceIsReset) { addConfigurationProperty("touch.deviceType", "touchScreen"); @@ -6844,10 +6927,17 @@ TEST_F(SingleTouchInputMapperTest, // No events are generated while the viewport is inactive. processMove(mapper, 101, 201); processSync(mapper); - processDown(mapper, 102, 202); + processUp(mapper); processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); + // Start a new gesture while the viewport is still inactive. + processDown(mapper, 300, 400); + mFakeEventHub->setAbsoluteAxisValue(EVENTHUB_ID, ABS_X, 300); + mFakeEventHub->setAbsoluteAxisValue(EVENTHUB_ID, ABS_Y, 400); + mFakeEventHub->setScanCodeState(EVENTHUB_ID, BTN_TOUCH, 1); + processSync(mapper); + // Make the viewport active again. The device should resume processing events. viewport->isActive = true; mFakePolicy->updateViewport(*viewport); @@ -6857,8 +6947,7 @@ TEST_F(SingleTouchInputMapperTest, ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled()); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); - // Start a new gesture. - processDown(mapper, 100, 200); + // In the next sync, the touch state that was recreated when the device was reset is reported. processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); @@ -9385,6 +9474,80 @@ TEST_F(MultiTouchInputMapperTest, Process_MultiTouch_WithInvalidTrackingId) { ASSERT_EQ(uint32_t(1), motionArgs.pointerCount); } +TEST_F(MultiTouchInputMapperTest, Reset_PreservesLastTouchState) { + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareAxes(POSITION | ID | SLOT | PRESSURE); + MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>(); + + NotifyMotionArgs motionArgs; + + // First finger down. + processId(mapper, FIRST_TRACKING_ID); + processPosition(mapper, 100, 200); + processPressure(mapper, RAW_PRESSURE_MAX); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); + + // Second finger down. + processSlot(mapper, SECOND_SLOT); + processId(mapper, SECOND_TRACKING_ID); + processPosition(mapper, 300, 400); + processPressure(mapper, RAW_PRESSURE_MAX); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE( + mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(ACTION_POINTER_1_DOWN, motionArgs.action); + + // Reset the mapper. When the mapper is reset, we expect the current multi-touch state to be + // preserved. Resetting should not generate any events. + mapper.reset(ARBITRARY_TIME); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); + + // Send a sync to simulate an empty touch frame where nothing changes. The mapper should use + // the existing touch state to generate a down event. + processPosition(mapper, 301, 302); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(ACTION_POINTER_1_DOWN, motionArgs.action); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); +} + +TEST_F(MultiTouchInputMapperTest, Reset_PreservesLastTouchState_NoPointersDown) { + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareAxes(POSITION | ID | SLOT | PRESSURE); + MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>(); + + NotifyMotionArgs motionArgs; + + // First finger touches down and releases. + processId(mapper, FIRST_TRACKING_ID); + processPosition(mapper, 100, 200); + processPressure(mapper, RAW_PRESSURE_MAX); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); + processId(mapper, INVALID_TRACKING_ID); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE( + mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action); + + // Reset the mapper. When the mapper is reset, we expect it to restore the latest + // raw state where no pointers are down. + mapper.reset(ARBITRARY_TIME); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); + + // Send an empty sync frame. Since there are no pointers, no events are generated. + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); +} + // --- MultiTouchInputMapperTest_ExternalDevice --- class MultiTouchInputMapperTest_ExternalDevice : public MultiTouchInputMapperTest { diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h index c869336bc2..f9224dfe6c 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h @@ -154,6 +154,8 @@ protected: virtual const compositionengine::CompositionEngine& getCompositionEngine() const = 0; virtual void dumpState(std::string& out) const = 0; + bool mustRecompose() const; + private: void dirtyEntireOutput(); compositionengine::OutputLayer* findLayerRequestingBackgroundComposition() const; @@ -172,6 +174,9 @@ private: std::unique_ptr<ClientCompositionRequestCache> mClientCompositionRequestCache; std::unique_ptr<planner::Planner> mPlanner; std::unique_ptr<HwcAsyncWorker> mHwComposerAsyncWorker; + + // Whether the content must be recomposed this frame. + bool mMustRecompose = false; }; // This template factory function standardizes the implementation details of the diff --git a/services/surfaceflinger/CompositionEngine/src/Display.cpp b/services/surfaceflinger/CompositionEngine/src/Display.cpp index 747edc5e52..0260fcf353 100644 --- a/services/surfaceflinger/CompositionEngine/src/Display.cpp +++ b/services/surfaceflinger/CompositionEngine/src/Display.cpp @@ -528,7 +528,7 @@ void Display::finishFrame(const compositionengine::CompositionRefreshArgs& refre // 1) It is being handled by hardware composer, which may need this to // keep its virtual display state machine in sync, or // 2) There is work to be done (the dirty region isn't empty) - if (GpuVirtualDisplayId::tryCast(mId) && getDirtyRegion().isEmpty()) { + if (GpuVirtualDisplayId::tryCast(mId) && !mustRecompose()) { ALOGV("Skipping display composition"); return; } diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp index 149a46e200..a9d8e3851e 100644 --- a/services/surfaceflinger/CompositionEngine/src/Output.cpp +++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp @@ -1028,17 +1028,17 @@ void Output::beginFrame() { // frame, then nothing more until we get new layers. // - When a display is created with a private layer stack, we won't // emit any black frames until a layer is added to the layer stack. - const bool mustRecompose = dirty && !(empty && wasEmpty); + mMustRecompose = dirty && !(empty && wasEmpty); const char flagPrefix[] = {'-', '+'}; static_cast<void>(flagPrefix); - ALOGV_IF("%s: %s composition for %s (%cdirty %cempty %cwasEmpty)", __FUNCTION__, - mustRecompose ? "doing" : "skipping", getName().c_str(), flagPrefix[dirty], - flagPrefix[empty], flagPrefix[wasEmpty]); + ALOGV("%s: %s composition for %s (%cdirty %cempty %cwasEmpty)", __func__, + mMustRecompose ? "doing" : "skipping", getName().c_str(), flagPrefix[dirty], + flagPrefix[empty], flagPrefix[wasEmpty]); - mRenderSurface->beginFrame(mustRecompose); + mRenderSurface->beginFrame(mMustRecompose); - if (mustRecompose) { + if (mMustRecompose) { outputState.lastCompositionHadVisibleLayers = !empty; } } @@ -1659,5 +1659,9 @@ void Output::getVisibleLayerInfo(std::vector<std::string> *layerName, } } +bool Output::mustRecompose() const { + return mMustRecompose; +} + } // namespace impl } // namespace android::compositionengine diff --git a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp index 344fea3331..5369642a1b 100644 --- a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp @@ -971,16 +971,40 @@ TEST_F(DisplayFinishFrameTest, skipsCompositionIfNotDirty) { // We expect no calls to queueBuffer if composition was skipped. EXPECT_CALL(*renderSurface, queueBuffer(_)).Times(0); + EXPECT_CALL(*renderSurface, beginFrame(false)); gpuDisplay->editState().isEnabled = true; gpuDisplay->editState().usesClientComposition = false; gpuDisplay->editState().layerStackSpace.setContent(Rect(0, 0, 1, 1)); gpuDisplay->editState().dirtyRegion = Region::INVALID_REGION; + gpuDisplay->editState().lastCompositionHadVisibleLayers = true; + gpuDisplay->beginFrame(); gpuDisplay->finishFrame({}, std::move(mResultWithoutBuffer)); } -TEST_F(DisplayFinishFrameTest, performsCompositionIfDirty) { +TEST_F(DisplayFinishFrameTest, skipsCompositionIfEmpty) { + auto args = getDisplayCreationArgsForGpuVirtualDisplay(); + std::shared_ptr<impl::Display> gpuDisplay = impl::createDisplay(mCompositionEngine, args); + + mock::RenderSurface* renderSurface = new StrictMock<mock::RenderSurface>(); + gpuDisplay->setRenderSurfaceForTest(std::unique_ptr<RenderSurface>(renderSurface)); + + // We expect no calls to queueBuffer if composition was skipped. + EXPECT_CALL(*renderSurface, queueBuffer(_)).Times(0); + EXPECT_CALL(*renderSurface, beginFrame(false)); + + gpuDisplay->editState().isEnabled = true; + gpuDisplay->editState().usesClientComposition = false; + gpuDisplay->editState().layerStackSpace.setContent(Rect(0, 0, 1, 1)); + gpuDisplay->editState().dirtyRegion = Region(Rect(0, 0, 1, 1)); + gpuDisplay->editState().lastCompositionHadVisibleLayers = false; + + gpuDisplay->beginFrame(); + gpuDisplay->finishFrame({}, std::move(mResultWithoutBuffer)); +} + +TEST_F(DisplayFinishFrameTest, performsCompositionIfDirtyAndNotEmpty) { auto args = getDisplayCreationArgsForGpuVirtualDisplay(); std::shared_ptr<impl::Display> gpuDisplay = impl::createDisplay(mCompositionEngine, args); @@ -989,11 +1013,15 @@ TEST_F(DisplayFinishFrameTest, performsCompositionIfDirty) { // We expect a single call to queueBuffer when composition is not skipped. EXPECT_CALL(*renderSurface, queueBuffer(_)).Times(1); + EXPECT_CALL(*renderSurface, beginFrame(true)); gpuDisplay->editState().isEnabled = true; gpuDisplay->editState().usesClientComposition = false; gpuDisplay->editState().layerStackSpace.setContent(Rect(0, 0, 1, 1)); gpuDisplay->editState().dirtyRegion = Region(Rect(0, 0, 1, 1)); + gpuDisplay->editState().lastCompositionHadVisibleLayers = true; + + gpuDisplay->beginFrame(); gpuDisplay->finishFrame({}, std::move(mResultWithBuffer)); } diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp index edeb3cacb5..e08e61118f 100644 --- a/services/surfaceflinger/DisplayDevice.cpp +++ b/services/surfaceflinger/DisplayDevice.cpp @@ -541,7 +541,7 @@ void DisplayDevice::animateRefreshRateOverlay() { } } -bool DisplayDevice::setDesiredActiveMode(const ActiveModeInfo& info) { +bool DisplayDevice::setDesiredActiveMode(const ActiveModeInfo& info, bool force) { ATRACE_CALL(); LOG_ALWAYS_FATAL_IF(!info.mode, "desired mode not provided"); @@ -559,7 +559,7 @@ bool DisplayDevice::setDesiredActiveMode(const ActiveModeInfo& info) { } // Check if we are already at the desired mode - if (getActiveMode()->getId() == info.mode->getId()) { + if (!force && getActiveMode()->getId() == info.mode->getId()) { return false; } diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h index e4a79a40fa..c11a896953 100644 --- a/services/surfaceflinger/DisplayDevice.h +++ b/services/surfaceflinger/DisplayDevice.h @@ -205,7 +205,7 @@ public: } }; - bool setDesiredActiveMode(const ActiveModeInfo&) EXCLUDES(mActiveModeLock); + bool setDesiredActiveMode(const ActiveModeInfo&, bool force = false) EXCLUDES(mActiveModeLock); std::optional<ActiveModeInfo> getDesiredActiveMode() const EXCLUDES(mActiveModeLock); void clearDesiredActiveModeState() EXCLUDES(mActiveModeLock); ActiveModeInfo getUpcomingActiveMode() const REQUIRES(kMainThreadContext) { diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 496adb69d2..90f06c0414 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -408,7 +408,7 @@ void Layer::prepareBasicGeometryCompositionState() { const auto& drawingState{getDrawingState()}; const auto alpha = static_cast<float>(getAlpha()); const bool opaque = isOpaque(drawingState); - const bool usesRoundedCorners = getRoundedCornerState().radius != 0.f; + const bool usesRoundedCorners = hasRoundedCorners(); auto blendMode = Hwc2::IComposerClient::BlendMode::NONE; if (!opaque || alpha != 1.0f) { @@ -489,7 +489,7 @@ void Layer::preparePerFrameCompositionState() { compositionState->hasProtectedContent = isProtected(); compositionState->dimmingEnabled = isDimmingEnabled(); - const bool usesRoundedCorners = getRoundedCornerState().radius != 0.f; + const bool usesRoundedCorners = hasRoundedCorners(); compositionState->isOpaque = isOpaque(drawingState) && !usesRoundedCorners && getAlpha() == 1.0_hf; @@ -1955,27 +1955,22 @@ Layer::RoundedCornerState Layer::getRoundedCornerState() const { const auto& parent = mDrawingParent.promote(); if (parent != nullptr) { parentSettings = parent->getRoundedCornerState(); - if (parentSettings.radius > 0) { + if (parentSettings.hasRoundedCorners()) { ui::Transform t = getActiveTransform(getDrawingState()); t = t.inverse(); parentSettings.cropRect = t.transform(parentSettings.cropRect); - // The rounded corners shader only accepts 1 corner radius for performance reasons, - // but a transform matrix can define horizontal and vertical scales. - // Let's take the average between both of them and pass into the shader, practically we - // never do this type of transformation on windows anyway. - auto scaleX = sqrtf(t[0][0] * t[0][0] + t[0][1] * t[0][1]); - auto scaleY = sqrtf(t[1][0] * t[1][0] + t[1][1] * t[1][1]); - parentSettings.radius *= (scaleX + scaleY) / 2.0f; + parentSettings.radius.x *= t.getScaleX(); + parentSettings.radius.y *= t.getScaleY(); } } // Get layer settings Rect layerCropRect = getCroppedBufferSize(getDrawingState()); - const float radius = getDrawingState().cornerRadius; + const vec2 radius(getDrawingState().cornerRadius, getDrawingState().cornerRadius); RoundedCornerState layerSettings(layerCropRect.toFloatRect(), radius); - const bool layerSettingsValid = layerSettings.radius > 0 && layerCropRect.isValid(); + const bool layerSettingsValid = layerSettings.hasRoundedCorners() && layerCropRect.isValid(); - if (layerSettingsValid && parentSettings.radius > 0) { + if (layerSettingsValid && parentSettings.hasRoundedCorners()) { // If the parent and the layer have rounded corner settings, use the parent settings if the // parent crop is entirely inside the layer crop. // This has limitations and cause rendering artifacts. See b/200300845 for correct fix. @@ -1989,7 +1984,7 @@ Layer::RoundedCornerState Layer::getRoundedCornerState() const { } } else if (layerSettingsValid) { return layerSettings; - } else if (parentSettings.radius > 0) { + } else if (parentSettings.hasRoundedCorners()) { return parentSettings; } return {}; @@ -2110,7 +2105,8 @@ void Layer::writeToProtoDrawingState(LayerProto* layerInfo) { layerInfo->set_effective_scaling_mode(getEffectiveScalingMode()); layerInfo->set_requested_corner_radius(getDrawingState().cornerRadius); - layerInfo->set_corner_radius(getRoundedCornerState().radius); + layerInfo->set_corner_radius( + (getRoundedCornerState().radius.x + getRoundedCornerState().radius.y) / 2.0); layerInfo->set_background_blur_radius(getBackgroundBlurRadius()); layerInfo->set_is_trusted_overlay(isTrustedOverlay()); LayerProtoHelper::writeToProtoDeprecated(transform, layerInfo->mutable_transform()); diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 6c40c83e5f..bbff3e01a2 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -134,13 +134,14 @@ public: struct RoundedCornerState { RoundedCornerState() = default; - RoundedCornerState(FloatRect cropRect, float radius) + RoundedCornerState(const FloatRect& cropRect, const vec2& radius) : cropRect(cropRect), radius(radius) {} // Rounded rectangle in local layer coordinate space. FloatRect cropRect = FloatRect(); // Radius of the rounded rectangle. - float radius = 0.0f; + vec2 radius; + bool hasRoundedCorners() const { return radius.x > 0.0f && radius.y > 0.0f; } }; using FrameRate = scheduler::LayerInfo::FrameRate; @@ -597,7 +598,7 @@ public: // corner crop does not intersect with its own rounded corner crop. virtual RoundedCornerState getRoundedCornerState() const; - bool hasRoundedCorners() const override { return getRoundedCornerState().radius > .0f; } + bool hasRoundedCorners() const override { return getRoundedCornerState().hasRoundedCorners(); } virtual PixelFormat getPixelFormat() const { return PIXEL_FORMAT_NONE; } /** diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index d8b43c9700..96d7c3d0c5 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -768,9 +768,7 @@ auto Scheduler::chooseDisplayMode() -> std::pair<DisplayModePtr, GlobalSignals> DisplayModePtr Scheduler::getPreferredDisplayMode() { std::lock_guard<std::mutex> lock(mPolicyLock); // Make sure the stored mode is up to date. - if (mPolicy.mode) { - mPolicy.mode = chooseDisplayMode().first; - } + mPolicy.mode = chooseDisplayMode().first; return mPolicy.mode; } diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index af25be9fa2..7f94a0b77e 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -1540,7 +1540,7 @@ bool SurfaceFlinger::isFpsDeferNeeded(const ActiveModeInfo& info) { return false; } -void SurfaceFlinger::setDesiredActiveMode(const ActiveModeInfo& info) { +void SurfaceFlinger::setDesiredActiveMode(const ActiveModeInfo& info, bool force) { ATRACE_CALL(); if (!info.mode) { @@ -1553,7 +1553,6 @@ void SurfaceFlinger::setDesiredActiveMode(const ActiveModeInfo& info) { return; } - if (isFpsDeferNeeded(info)) { return; } @@ -1563,7 +1562,7 @@ void SurfaceFlinger::setDesiredActiveMode(const ActiveModeInfo& info) { mDolphinWrapper.dolphinSetVsyncPeriod(mVsyncPeriod); } - if (display->setDesiredActiveMode(info)) { + if (display->setDesiredActiveMode(info, force)) { scheduleComposite(FrameHint::kNone); // Start receiving vsync samples now, so that we can detect a period @@ -4603,8 +4602,14 @@ void SurfaceFlinger::requestDisplayMode(DisplayModePtr mode, DisplayModeEvent ev } ATRACE_CALL(); + if (display->isInternal() && !isDisplayActiveLocked(display)) { + ALOGV("%s(%s): Inactive display", __func__, to_string(display->getId()).c_str()); + return; + } + if (!display->refreshRateConfigs().isModeAllowed(mode->getId())) { - ALOGV("Skipping disallowed mode %d", mode->getId().value()); + ALOGV("%s(%s): Disallowed mode %d", __func__, to_string(display->getId()).c_str(), + mode->getId().value()); return; } @@ -5470,7 +5475,7 @@ status_t SurfaceFlinger::setTransactionState( bool SurfaceFlinger::applyTransactionState(const FrameTimelineInfo& frameTimelineInfo, Vector<ComposerState>& states, - const Vector<DisplayState>& displays, uint32_t flags, + Vector<DisplayState>& displays, uint32_t flags, const InputWindowCommands& inputWindowCommands, const int64_t desiredPresentTime, bool isAutoTimestamp, const client_cache_t& uncacheBuffer, @@ -5479,7 +5484,8 @@ bool SurfaceFlinger::applyTransactionState(const FrameTimelineInfo& frameTimelin const std::vector<ListenerCallbacks>& listenerCallbacks, int originPid, int originUid, uint64_t transactionId) { uint32_t transactionFlags = 0; - for (const DisplayState& display : displays) { + for (DisplayState& display : displays) { + display.sanitize(permissions); transactionFlags |= setDisplayStateLocked(display); } @@ -6225,7 +6231,7 @@ void SurfaceFlinger::setPowerModeInternal(const sp<DisplayDevice>& display, hal: mInterceptor->savePowerModeUpdate(display->getSequenceId(), static_cast<int32_t>(mode)); } const auto refreshRate = display->refreshRateConfigs().getActiveMode()->getFps(); - if (*currentMode == hal::PowerMode::OFF) { + if (!currentMode || *currentMode == hal::PowerMode::OFF) { // Turn on the display if (display->isInternal() && (!activeDisplay || !activeDisplay->isPoweredOn())) { onActiveDisplayChangedLocked(display); @@ -8532,8 +8538,10 @@ ftl::SharedFuture<FenceResult> SurfaceFlinger::captureScreenCommon( std::unique_ptr<RenderArea> renderArea = renderAreaFuture.get(); if (!renderArea) { ALOGW("Skipping screen capture because of invalid render area."); - captureResults.result = NO_MEMORY; - captureListener->onScreenCaptureCompleted(captureResults); + if (captureListener) { + captureResults.result = NO_MEMORY; + captureListener->onScreenCaptureCompleted(captureResults); + } return ftl::yield<FenceResult>(base::unexpected(NO_ERROR)).share(); } @@ -8794,9 +8802,19 @@ status_t SurfaceFlinger::setDesiredDisplayModeSpecsInternal( return NO_ERROR; } - scheduler::RefreshRateConfigs::Policy currentPolicy = - display->refreshRateConfigs().getCurrentPolicy(); + if (display->isInternal() && !isDisplayActiveLocked(display)) { + // The policy will be be applied when the display becomes active. + ALOGV("%s(%s): Inactive display", __func__, to_string(display->getId()).c_str()); + return NO_ERROR; + } + + return applyRefreshRateConfigsPolicy(display); +} +status_t SurfaceFlinger::applyRefreshRateConfigsPolicy(const sp<DisplayDevice>& display, + bool force) { + const scheduler::RefreshRateConfigs::Policy currentPolicy = + display->refreshRateConfigs().getCurrentPolicy(); ALOGV("Setting desired display mode specs: %s", currentPolicy.toString().c_str()); // TODO(b/140204874): Leave the event in until we do proper testing with all apps that might @@ -8824,7 +8842,7 @@ status_t SurfaceFlinger::setDesiredDisplayModeSpecsInternal( if (display->refreshRateConfigs().isModeAllowed(preferredDisplayMode->getId())) { ALOGV("switching to Scheduler preferred display mode %d", preferredDisplayMode->getId().value()); - setDesiredActiveMode({preferredDisplayMode, DisplayModeEvent::Changed}); + setDesiredActiveMode({preferredDisplayMode, DisplayModeEvent::Changed}, force); uint32_t hwcDisplayId; if (isDisplayExtnEnabled() && getHwcDisplayId(display, &hwcDisplayId)) { setDisplayExtnActiveConfig(hwcDisplayId, preferredDisplayMode->getId().value()); @@ -9630,14 +9648,23 @@ void SurfaceFlinger::onActiveDisplaySizeChanged(const sp<DisplayDevice>& activeD void SurfaceFlinger::onActiveDisplayChangedLocked(const sp<DisplayDevice>& activeDisplay) { ATRACE_CALL(); + // During boot, SF powers on the primary display, which is the first display to be active. In + // that case, there is no need to force setDesiredActiveMode, because DM is about to send its + // policy via setDesiredDisplayModeSpecs. + bool forceApplyPolicy = false; + if (const auto display = getDisplayDeviceLocked(mActiveDisplayToken)) { display->getCompositionDisplay()->setLayerCachingTexturePoolEnabled(false); + forceApplyPolicy = true; } if (!activeDisplay) { ALOGE("%s: activeDisplay is null", __func__); return; } + + ALOGI("Active display is %s", to_string(activeDisplay->getPhysicalId()).c_str()); + mActiveDisplayToken = activeDisplay->getDisplayToken(); activeDisplay->getCompositionDisplay()->setLayerCachingTexturePoolEnabled(true); updateInternalDisplayVsyncLocked(activeDisplay); @@ -9646,9 +9673,11 @@ void SurfaceFlinger::onActiveDisplayChangedLocked(const sp<DisplayDevice>& activ onActiveDisplaySizeChanged(activeDisplay); mActiveDisplayTransformHint = activeDisplay->getTransformHint(); - // Update the kernel timer for the current active display, since the policy - // for this display might have changed when it was not the active display. - toggleKernelIdleTimer(); + // The policy of the new active/leader display may have changed while it was inactive. In that + // case, its preferred mode has not been propagated to HWC (via setDesiredActiveMode). In either + // case, the Scheduler's cachedModeChangedParams must be initialized to the newly active mode, + // and the kernel idle timer of the newly active display must be toggled. + applyRefreshRateConfigsPolicy(activeDisplay, forceApplyPolicy); } status_t SurfaceFlinger::addWindowInfosListener( diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index c41d7ab816..448cc56e38 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -809,7 +809,7 @@ private: // Called on the main thread in response to initializeDisplays() void onInitializeDisplays() REQUIRES(mStateLock); // Sets the desired active mode bit. It obtains the lock, and sets mDesiredActiveMode. - void setDesiredActiveMode(const ActiveModeInfo& info) REQUIRES(mStateLock); + void setDesiredActiveMode(const ActiveModeInfo& info, bool force = false) REQUIRES(mStateLock); status_t setActiveModeFromBackdoor(const sp<IBinder>& displayToken, int id); // Sets the active mode and a new refresh rate in SF. void updateInternalStateWithChangedMode() REQUIRES(mStateLock); @@ -832,6 +832,9 @@ private: const std::optional<scheduler::RefreshRateConfigs::Policy>& policy, bool overridePolicy) EXCLUDES(mStateLock); + status_t applyRefreshRateConfigsPolicy(const sp<DisplayDevice>&, bool force = false) + REQUIRES(mStateLock); + void commitTransactions() EXCLUDES(mStateLock); void commitTransactionsLocked(uint32_t transactionFlags) REQUIRES(mStateLock); void doCommitTransactions() REQUIRES(mStateLock); @@ -865,7 +868,7 @@ private: * Transactions */ bool applyTransactionState(const FrameTimelineInfo& info, Vector<ComposerState>& state, - const Vector<DisplayState>& displays, uint32_t flags, + Vector<DisplayState>& displays, uint32_t flags, const InputWindowCommands& inputWindowCommands, const int64_t desiredPresentTime, bool isAutoTimestamp, const client_cache_t& uncacheBuffer, const int64_t postTime, diff --git a/services/surfaceflinger/tests/DisplayConfigs_test.cpp b/services/surfaceflinger/tests/DisplayConfigs_test.cpp index 2dc96b8511..c58fe4831c 100644 --- a/services/surfaceflinger/tests/DisplayConfigs_test.cpp +++ b/services/surfaceflinger/tests/DisplayConfigs_test.cpp @@ -149,4 +149,4 @@ TEST_F(RefreshRateRangeTest, setAllowGroupSwitching) { } // namespace android // TODO(b/129481165): remove the #pragma below and fix conversion issues -#pragma clang diagnostic pop // ignored "-Wextra"
\ No newline at end of file +#pragma clang diagnostic pop // ignored "-Wextra" diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp index bbfedc7685..fbc532eb77 100644 --- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp +++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp @@ -664,7 +664,8 @@ struct BaseLayerProperties { EXPECT_EQ(false, layer.source.buffer.isY410BT2020); EXPECT_EQ(true, layer.source.buffer.usePremultipliedAlpha); EXPECT_EQ(false, layer.source.buffer.isOpaque); - EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius); + EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius.x); + EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius.y); EXPECT_EQ(ui::Dataspace::UNKNOWN, layer.sourceDataspace); EXPECT_EQ(LayerProperties::COLOR[3], layer.alpha); return resultFuture; @@ -714,7 +715,8 @@ struct BaseLayerProperties { EXPECT_EQ(half3(LayerProperties::COLOR[0], LayerProperties::COLOR[1], LayerProperties::COLOR[2]), layer.source.solidColor); - EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius); + EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius.x); + EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius.y); EXPECT_EQ(ui::Dataspace::UNKNOWN, layer.sourceDataspace); EXPECT_EQ(LayerProperties::COLOR[3], layer.alpha); return resultFuture; @@ -792,7 +794,8 @@ struct CommonSecureLayerProperties : public BaseLayerProperties<LayerProperties> const renderengine::LayerSettings layer = layerSettings.back(); EXPECT_THAT(layer.source.buffer.buffer, IsNull()); EXPECT_EQ(half3(0.0f, 0.0f, 0.0f), layer.source.solidColor); - EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius); + EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius.x); + EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius.y); EXPECT_EQ(ui::Dataspace::UNKNOWN, layer.sourceDataspace); EXPECT_EQ(1.0f, layer.alpha); return resultFuture; diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp index f04221cc21..5ddd1c86d5 100644 --- a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp +++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp @@ -120,51 +120,6 @@ void DisplayTransactionTest::injectFakeNativeWindowSurfaceFactory() { }); } -sp<DisplayDevice> DisplayTransactionTest::injectDefaultInternalDisplay( - std::function<void(FakeDisplayDeviceInjector&)> injectExtra) { - constexpr PhysicalDisplayId DEFAULT_DISPLAY_ID = PhysicalDisplayId::fromPort(255u); - constexpr int DEFAULT_DISPLAY_WIDTH = 1080; - constexpr int DEFAULT_DISPLAY_HEIGHT = 1920; - constexpr HWDisplayId DEFAULT_DISPLAY_HWC_DISPLAY_ID = 0; - - // The DisplayDevice is required to have a framebuffer (behind the - // ANativeWindow interface) which uses the actual hardware display - // size. - EXPECT_CALL(*mNativeWindow, query(NATIVE_WINDOW_WIDTH, _)) - .WillRepeatedly(DoAll(SetArgPointee<1>(DEFAULT_DISPLAY_WIDTH), Return(0))); - EXPECT_CALL(*mNativeWindow, query(NATIVE_WINDOW_HEIGHT, _)) - .WillRepeatedly(DoAll(SetArgPointee<1>(DEFAULT_DISPLAY_HEIGHT), Return(0))); - EXPECT_CALL(*mNativeWindow, perform(NATIVE_WINDOW_SET_BUFFERS_FORMAT)); - EXPECT_CALL(*mNativeWindow, perform(NATIVE_WINDOW_API_CONNECT)); - EXPECT_CALL(*mNativeWindow, perform(NATIVE_WINDOW_SET_USAGE64)); - EXPECT_CALL(*mNativeWindow, perform(NATIVE_WINDOW_API_DISCONNECT)).Times(AnyNumber()); - - auto compositionDisplay = - compositionengine::impl::createDisplay(mFlinger.getCompositionEngine(), - compositionengine::DisplayCreationArgsBuilder() - .setId(DEFAULT_DISPLAY_ID) - .setPixels({DEFAULT_DISPLAY_WIDTH, - DEFAULT_DISPLAY_HEIGHT}) - .setPowerAdvisor(&mPowerAdvisor) - .build()); - - constexpr bool kIsPrimary = true; - auto injector = FakeDisplayDeviceInjector(mFlinger, compositionDisplay, - ui::DisplayConnectionType::Internal, - DEFAULT_DISPLAY_HWC_DISPLAY_ID, kIsPrimary); - - injector.setNativeWindow(mNativeWindow); - if (injectExtra) { - injectExtra(injector); - } - - auto displayDevice = injector.inject(); - - Mock::VerifyAndClear(mNativeWindow.get()); - - return displayDevice; -} - bool DisplayTransactionTest::hasPhysicalHwcDisplay(HWDisplayId hwcDisplayId) const { return mFlinger.hwcPhysicalDisplayIdMap().count(hwcDisplayId) == 1; } diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h index f5235ce953..60f773fcb0 100644 --- a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h +++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h @@ -42,6 +42,7 @@ #include <renderengine/mock/RenderEngine.h> #include <ui/DebugUtils.h> +#include "FakeDisplayInjector.h" #include "TestableScheduler.h" #include "TestableSurfaceFlinger.h" #include "mock/DisplayHardware/MockComposer.h" @@ -90,7 +91,9 @@ public: void injectFakeBufferQueueFactory(); void injectFakeNativeWindowSurfaceFactory(); sp<DisplayDevice> injectDefaultInternalDisplay( - std::function<void(TestableSurfaceFlinger::FakeDisplayDeviceInjector&)>); + std::function<void(TestableSurfaceFlinger::FakeDisplayDeviceInjector&)> injectExtra) { + return mFakeDisplayInjector.injectInternalDisplay(injectExtra); + } // -------------------------------------------------------------------- // Postcondition helpers @@ -115,6 +118,8 @@ public: sp<GraphicBuffer> mBuffer = new GraphicBuffer(); Hwc2::mock::PowerAdvisor mPowerAdvisor; + FakeDisplayInjector mFakeDisplayInjector{mFlinger, mPowerAdvisor, mNativeWindow}; + // These mocks are created by the test, but are destroyed by SurfaceFlinger // by virtue of being stored into a std::unique_ptr. However we still need // to keep a reference to them for use in setting up call expectations. diff --git a/services/surfaceflinger/tests/unittests/FakeDisplayInjector.h b/services/surfaceflinger/tests/unittests/FakeDisplayInjector.h new file mode 100644 index 0000000000..6e4bf2b06e --- /dev/null +++ b/services/surfaceflinger/tests/unittests/FakeDisplayInjector.h @@ -0,0 +1,96 @@ +/* + * Copyright 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. + */ + +#pragma once + +#include <gmock/gmock.h> + +#include "TestableSurfaceFlinger.h" +#include "mock/DisplayHardware/MockPowerAdvisor.h" +#include "mock/system/window/MockNativeWindow.h" + +namespace android { + +using FakeDisplayDeviceInjector = TestableSurfaceFlinger::FakeDisplayDeviceInjector; +using android::hardware::graphics::composer::hal::HWDisplayId; +using android::Hwc2::mock::PowerAdvisor; + +struct FakeDisplayInjectorArgs { + PhysicalDisplayId displayId = PhysicalDisplayId::fromPort(255u); + HWDisplayId hwcDisplayId = 0; + bool isPrimary = true; +}; + +class FakeDisplayInjector { +public: + FakeDisplayInjector(TestableSurfaceFlinger& flinger, Hwc2::mock::PowerAdvisor& powerAdvisor, + sp<mock::NativeWindow> nativeWindow) + : mFlinger(flinger), mPowerAdvisor(powerAdvisor), mNativeWindow(nativeWindow) {} + + sp<DisplayDevice> injectInternalDisplay( + const std::function<void(FakeDisplayDeviceInjector&)>& injectExtra, + FakeDisplayInjectorArgs args = {}) { + using testing::_; + using testing::AnyNumber; + using testing::DoAll; + using testing::Mock; + using testing::Return; + using testing::SetArgPointee; + + constexpr ui::Size kResolution = {1080, 1920}; + + // The DisplayDevice is required to have a framebuffer (behind the + // ANativeWindow interface) which uses the actual hardware display + // size. + EXPECT_CALL(*mNativeWindow, query(NATIVE_WINDOW_WIDTH, _)) + .WillRepeatedly(DoAll(SetArgPointee<1>(kResolution.getWidth()), Return(0))); + EXPECT_CALL(*mNativeWindow, query(NATIVE_WINDOW_HEIGHT, _)) + .WillRepeatedly(DoAll(SetArgPointee<1>(kResolution.getHeight()), Return(0))); + EXPECT_CALL(*mNativeWindow, perform(NATIVE_WINDOW_SET_BUFFERS_FORMAT)); + EXPECT_CALL(*mNativeWindow, perform(NATIVE_WINDOW_API_CONNECT)); + EXPECT_CALL(*mNativeWindow, perform(NATIVE_WINDOW_SET_USAGE64)); + EXPECT_CALL(*mNativeWindow, perform(NATIVE_WINDOW_API_DISCONNECT)).Times(AnyNumber()); + + auto compositionDisplay = compositionengine::impl:: + createDisplay(mFlinger.getCompositionEngine(), + compositionengine::DisplayCreationArgsBuilder() + .setId(args.displayId) + .setPixels(kResolution) + .setPowerAdvisor(&mPowerAdvisor) + .build()); + + auto injector = FakeDisplayDeviceInjector(mFlinger, compositionDisplay, + ui::DisplayConnectionType::Internal, + args.hwcDisplayId, args.isPrimary); + + injector.setNativeWindow(mNativeWindow); + if (injectExtra) { + injectExtra(injector); + } + + auto displayDevice = injector.inject(); + + Mock::VerifyAndClear(mNativeWindow.get()); + + return displayDevice; + } + + TestableSurfaceFlinger& mFlinger; + Hwc2::mock::PowerAdvisor& mPowerAdvisor; + sp<mock::NativeWindow> mNativeWindow; +}; + +} // namespace android diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp index 5872a472cd..b58add8f21 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp @@ -14,11 +14,11 @@ * limitations under the License. */ -#include "mock/MockEventThread.h" #undef LOG_TAG #define LOG_TAG "LibSurfaceFlingerUnittests" #include "DisplayTransactionTestHelpers.h" +#include "mock/DisplayHardware/MockDisplayMode.h" #include <scheduler/Fps.h> @@ -42,14 +42,9 @@ public: mFlinger.onComposerHalHotplug(PrimaryDisplayVariant::HWC_DISPLAY_ID, Connection::CONNECTED); - { - DisplayModes modes = makeModes(kMode60, kMode90, kMode120, kMode90_4K); - auto configs = std::make_shared<scheduler::RefreshRateConfigs>(modes, kModeId60); - - mDisplay = PrimaryDisplayVariant::makeFakeExistingDisplayInjector(this) - .setDisplayModes(std::move(modes), kModeId60, std::move(configs)) - .inject(); - } + mDisplay = PrimaryDisplayVariant::makeFakeExistingDisplayInjector(this) + .setDisplayModes(kModes, kModeId60) + .inject(); setupScheduler(mDisplay->holdRefreshRateConfigs()); @@ -77,6 +72,8 @@ protected: static constexpr ui::Size kResolution4K{3840, 2160}; static inline const DisplayModePtr kMode90_4K = createDisplayMode(kModeId90_4K, 90_Hz, 3, kResolution4K); + + static inline const DisplayModes kModes = makeModes(kMode60, kMode90, kMode120, kMode90_4K); }; void DisplayModeSwitchingTest::setupScheduler( @@ -265,5 +262,112 @@ TEST_F(DisplayModeSwitchingTest, changeResolution_OnActiveDisplay_WithoutRefresh ASSERT_EQ(mDisplay->getActiveMode()->getId(), kModeId90_4K); } +TEST_F(DisplayModeSwitchingTest, multiDisplay) { + constexpr HWDisplayId kInnerDisplayHwcId = PrimaryDisplayVariant::HWC_DISPLAY_ID; + constexpr HWDisplayId kOuterDisplayHwcId = kInnerDisplayHwcId + 1; + + constexpr PhysicalDisplayId kOuterDisplayId = PhysicalDisplayId::fromPort(254u); + + constexpr bool kIsPrimary = false; + TestableSurfaceFlinger::FakeHwcDisplayInjector(kOuterDisplayId, hal::DisplayType::PHYSICAL, + kIsPrimary) + .setHwcDisplayId(kOuterDisplayHwcId) + .inject(&mFlinger, mComposer); + + const auto outerDisplay = mFakeDisplayInjector.injectInternalDisplay( + [&](FakeDisplayDeviceInjector& injector) { + injector.setDisplayModes(mock::cloneForDisplay(kOuterDisplayId, kModes), + kModeId120); + }, + {.displayId = kOuterDisplayId, + .hwcDisplayId = kOuterDisplayHwcId, + .isPrimary = kIsPrimary}); + + const auto& innerDisplay = mDisplay; + + EXPECT_FALSE(innerDisplay->getDesiredActiveMode()); + EXPECT_FALSE(outerDisplay->getDesiredActiveMode()); + + EXPECT_EQ(innerDisplay->getActiveMode()->getId(), kModeId60); + EXPECT_EQ(outerDisplay->getActiveMode()->getId(), kModeId120); + + mFlinger.onActiveDisplayChanged(innerDisplay); + + EXPECT_EQ(NO_ERROR, + mFlinger.setDesiredDisplayModeSpecs(innerDisplay->getDisplayToken().promote(), + kModeId90.value(), false, 0.f, 120.f, 0.f, + 120.f)); + + EXPECT_EQ(NO_ERROR, + mFlinger.setDesiredDisplayModeSpecs(outerDisplay->getDisplayToken().promote(), + kModeId60.value(), false, 0.f, 120.f, 0.f, + 120.f)); + + // Transition on the inner display. + ASSERT_TRUE(innerDisplay->getDesiredActiveMode()); + EXPECT_EQ(innerDisplay->getDesiredActiveMode()->mode->getId(), kModeId90); + + // No transition on the outer display. + EXPECT_FALSE(outerDisplay->getDesiredActiveMode()); + + const VsyncPeriodChangeTimeline timeline{.refreshRequired = true}; + EXPECT_CALL(*mComposer, + setActiveConfigWithConstraints(kInnerDisplayHwcId, + hal::HWConfigId(kModeId90.value()), _, _)) + .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE))); + + mFlinger.commit(); + + // Transition on the inner display. + ASSERT_TRUE(innerDisplay->getDesiredActiveMode()); + EXPECT_EQ(innerDisplay->getDesiredActiveMode()->mode->getId(), kModeId90); + + // No transition on the outer display. + EXPECT_FALSE(outerDisplay->getDesiredActiveMode()); + + mFlinger.commit(); + + // Transition on the inner display. + EXPECT_FALSE(innerDisplay->getDesiredActiveMode()); + EXPECT_EQ(innerDisplay->getActiveMode()->getId(), kModeId90); + + // No transition on the outer display. + EXPECT_FALSE(outerDisplay->getDesiredActiveMode()); + EXPECT_EQ(outerDisplay->getActiveMode()->getId(), kModeId120); + + mFlinger.onActiveDisplayChanged(outerDisplay); + + // No transition on the inner display. + EXPECT_FALSE(innerDisplay->getDesiredActiveMode()); + + // Transition on the outer display. + ASSERT_TRUE(outerDisplay->getDesiredActiveMode()); + EXPECT_EQ(outerDisplay->getDesiredActiveMode()->mode->getId(), kModeId60); + + EXPECT_CALL(*mComposer, + setActiveConfigWithConstraints(kOuterDisplayHwcId, + hal::HWConfigId(kModeId60.value()), _, _)) + .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE))); + + mFlinger.commit(); + + // No transition on the inner display. + EXPECT_FALSE(innerDisplay->getDesiredActiveMode()); + + // Transition on the outer display. + ASSERT_TRUE(outerDisplay->getDesiredActiveMode()); + EXPECT_EQ(outerDisplay->getDesiredActiveMode()->mode->getId(), kModeId60); + + mFlinger.commit(); + + // No transition on the inner display. + EXPECT_FALSE(innerDisplay->getDesiredActiveMode()); + EXPECT_EQ(innerDisplay->getActiveMode()->getId(), kModeId90); + + // Transition on the outer display. + EXPECT_FALSE(outerDisplay->getDesiredActiveMode()); + EXPECT_EQ(outerDisplay->getActiveMode()->getId(), kModeId60); +} + } // namespace } // namespace android diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp index 583cf5f836..b560025929 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp @@ -323,6 +323,9 @@ class SetPowerModeInternalTest : public DisplayTransactionTest { public: template <typename Case> void transitionDisplayCommon(); + + template <bool kBoot> + sp<DisplayDevice> activeDisplayTest(); }; template <PowerMode PowerMode> @@ -499,5 +502,87 @@ TEST_F(SetPowerModeInternalTest, transitionsDisplayFromOnToUnknownExternalDispla transitionDisplayCommon<ExternalDisplayPowerCase<TransitionOnToUnknownVariant>>(); } +template <bool kBoot> +sp<DisplayDevice> SetPowerModeInternalTest::activeDisplayTest() { + using Case = SimplePrimaryDisplayCase; + + // -------------------------------------------------------------------- + // Preconditions + + // Inject a primary display. + Case::Display::injectHwcDisplay(this); + auto injector = Case::Display::makeFakeExistingDisplayInjector(this); + injector.setPowerMode(kBoot ? std::nullopt : std::make_optional(PowerMode::OFF)); + + const auto display = injector.inject(); + EXPECT_EQ(display->getDisplayToken(), mFlinger.mutableActiveDisplayToken()); + + using PowerCase = PrimaryDisplayPowerCase<TransitionOffToOnVariant>; + TransitionOffToOnVariant::template setupCallExpectations<PowerCase>(this); + + constexpr size_t kTimes = kBoot ? 1 : 0; + EXPECT_CALL(*mRenderEngine, onActiveDisplaySizeChanged(display->getSize())).Times(kTimes); + EXPECT_CALL(*mEventThread, onModeChanged(display->getActiveMode())).Times(kTimes); + + if constexpr (kBoot) { + mFlinger.mutableActiveDisplayToken() = nullptr; + } + + // -------------------------------------------------------------------- + // Invocation + + mFlinger.setPowerModeInternal(display, PowerMode::ON); + + // -------------------------------------------------------------------- + // Postconditions + + // The primary display should be the active display. + EXPECT_EQ(display->getDisplayToken(), mFlinger.mutableActiveDisplayToken()); + + Mock::VerifyAndClearExpectations(mComposer); + Mock::VerifyAndClearExpectations(mRenderEngine); + Mock::VerifyAndClearExpectations(mEventThread); + Mock::VerifyAndClearExpectations(mVsyncController); + Mock::VerifyAndClearExpectations(mVSyncTracker); + Mock::VerifyAndClearExpectations(mFlinger.scheduler()); + Mock::VerifyAndClearExpectations(&mFlinger.mockSchedulerCallback()); + + return display; +} + +TEST_F(SetPowerModeInternalTest, activeDisplayBoot) { + constexpr bool kBoot = true; + activeDisplayTest<kBoot>(); +} + +TEST_F(SetPowerModeInternalTest, activeDisplaySingle) { + constexpr bool kBoot = false; + activeDisplayTest<kBoot>(); +} + +TEST_F(SetPowerModeInternalTest, activeDisplayDual) { + constexpr bool kBoot = false; + const auto innerDisplay = activeDisplayTest<kBoot>(); + + // Inject a powered-off outer display. + const auto outerDisplay = mFakeDisplayInjector.injectInternalDisplay( + [&](FakeDisplayDeviceInjector& injector) { injector.setPowerMode(PowerMode::OFF); }, + {.displayId = PhysicalDisplayId::fromPort(254u), + .hwcDisplayId = 1, + .isPrimary = false}); + + EXPECT_EQ(innerDisplay->getDisplayToken(), mFlinger.mutableActiveDisplayToken()); + + mFlinger.setPowerModeInternal(innerDisplay, PowerMode::OFF); + mFlinger.setPowerModeInternal(outerDisplay, PowerMode::ON); + + EXPECT_EQ(outerDisplay->getDisplayToken(), mFlinger.mutableActiveDisplayToken()); + + mFlinger.setPowerModeInternal(outerDisplay, PowerMode::OFF); + mFlinger.setPowerModeInternal(innerDisplay, PowerMode::ON); + + EXPECT_EQ(innerDisplay->getDisplayToken(), mFlinger.mutableActiveDisplayToken()); +} + } // namespace } // namespace android diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index a6a16a6f0f..6f54161266 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -798,7 +798,7 @@ public: return *this; } - auto& setPowerMode(hal::PowerMode mode) { + auto& setPowerMode(std::optional<hal::PowerMode> mode) { mCreationArgs.initialPowerMode = mode; return *this; } @@ -867,6 +867,10 @@ public: .deviceProductInfo = {}, .supportedModes = modes, .activeMode = activeMode->get()}; + + if (mCreationArgs.isPrimary) { + mFlinger.mutableActiveDisplayToken() = mDisplayToken; + } } state.isSecure = mCreationArgs.isSecure; diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h index a83ecbca26..6809580998 100644 --- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h +++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h @@ -33,4 +33,24 @@ inline DisplayModePtr createDisplayMode( .build(); } +inline DisplayModePtr cloneForDisplay(PhysicalDisplayId displayId, const DisplayModePtr& modePtr) { + return DisplayMode::Builder(modePtr->getHwcId()) + .setId(modePtr->getId()) + .setPhysicalDisplayId(displayId) + .setVsyncPeriod(modePtr->getVsyncPeriod()) + .setGroup(modePtr->getGroup()) + .setResolution(modePtr->getResolution()) + .build(); +} + +inline DisplayModes cloneForDisplay(PhysicalDisplayId displayId, const DisplayModes& modes) { + DisplayModes clones; + + for (const auto& [id, modePtr] : modes) { + clones.try_emplace(id, cloneForDisplay(displayId, modePtr)); + } + + return clones; +} + } // namespace android::mock |