Android11 截图过滤特定图层

865 阅读2分钟

Android11 截图过滤特定图层

思路

从可执行文件screencap源码分析,发现会执行如下方法

image.png 继续追踪ScreenshotClient类

ScreenshotClient

image.png 可以看到最终是走向了SurfaceFlinger的captureScreen方法。

captureScreen

SurfaceFlinger的captureScreen代码如下,走到了captureScreenCommon方法

captureScreenCommon

继续往下跟踪是重载函数,流程依次是

captureScreenCommon(renderArea, traverseLayers, outBuffer, ui::PixelFormat::RGBA_8888,

false /* useIdentityTransform */,ignored /* outCapturedSecureLayers */);
captureScreenCommon(renderArea, traverseLayers, *outBuffer, useIdentityTransform,

false /* regionSampling */, outCapturedSecureLayers);
captureScreenImplLocked(renderArea, traverseLayers, buffer.get(),

useIdentityTransform, forSystem, &fd,

regionSampling, outCapturedSecureLayers);

captureScreenImplLocked

captureScreenImplLocked方法代码如下,调用了renderScreenImplLocked方法

image.png

对策一

## SurfaceFlinger.cpp
void SurfaceFlinger::renderScreenImplLocked(const RenderArea& renderArea,
                                            TraverseLayersFunction traverseLayers,
                                            ANativeWindowBuffer* buffer, bool useIdentityTransform,
                                            bool regionSampling, int* outSyncFd) {
    .....
    traverseLayers([&](Layer* layer) {
        const bool supportProtectedContent = false;
        Region clip(renderArea.getBounds());
        compositionengine::LayerFE::ClientCompositionTargetSettings targetSettings{
                clip,
                useIdentityTransform,
                layer->needsFilteringForScreenshots(display.get(), transform) ||
                        renderArea.needsFiltering(),
                renderArea.isSecure(),
                supportProtectedContent,
                clearRegion,
                displayViewport,
                clientCompositionDisplay.outputDataspace,
                true,  /* realContentIsVisible */
                false, /* clearContent */
        };
        std::vector<compositionengine::LayerFE::LayerSettings> results =
                layer->prepareClientCompositionList(targetSettings);
        if (results.size() > 0) {
            for (auto& settings : results) {
                settings.geometry.positionTransform =
                        transform.asMatrix4() * settings.geometry.positionTransform;
                // There's no need to process blurs when we're executing region sampling,
                // we're just trying to understand what we're drawing, and doing so without
                // blurs is already a pretty good approximation.
                if (regionSampling) {
                    settings.backgroundBlurRadius = 0;
                }
            }
            //add start
            std::string layerName = layer->getName();
            ALOGE("Test layerName= %s ",layerName.c_str());
            if( strstr(layerName.c_str(), "Sprite") == NULL &&
                strstr(layerName.c_str(), "com.skg.benqsetting") == NULL){
                clientCompositionLayers.insert(clientCompositionLayers.end(),
                                                std::make_move_iterator(results.begin()),
                                               std::make_move_iterator(results.end()));
                ALOGE("Test push_back() ");
                renderedLayers.push_back(layer);
            }
            //add end
        }
    });
    ...
}

对策二

查看代码的时候发现,layer有一个属性mPrimaryDisplayOnly,通过设置这个变量可以实现截屏不包括当前layer,系统代码注释如下

##SurfaceFlinger.cpp

status_t SurfaceFlinger::createLayer(const String8& name, const sp<Client>& client, uint32_t w,
                                     uint32_t h, PixelFormat format, uint32_t flags,
                                     LayerMetadata metadata, sp<IBinder>* handle,
                                     sp<IGraphicBufferProducer>* gbp,
                                     const sp<IBinder>& parentHandle, const sp<Layer>& parentLayer,
                                     uint32_t* outTransformHint) {
    if (int32_t(w|h) < 0) {
        ALOGE("createLayer() failed, w or h is negative (w=%d, h=%d)",
                int(w), int(h));
        return BAD_VALUE;
    }

    ALOG_ASSERT(parentLayer == nullptr || parentHandle == nullptr,
            "Expected only one of parentLayer or parentHandle to be non-null. "
            "Programmer error?");

    status_t result = NO_ERROR;

    sp<Layer> layer;

    std::string uniqueName = getUniqueLayerName(name.string());

    bool primaryDisplayOnly = false;

    // window type is WINDOW_TYPE_DONT_SCREENSHOT from SurfaceControl.java
    // TODO b/64227542
    if (metadata.has(METADATA_WINDOW_TYPE)) {
        int32_t windowType = metadata.getInt32(METADATA_WINDOW_TYPE, 0);
        if (windowType == 441731) {
            metadata.setInt32(METADATA_WINDOW_TYPE, InputWindowInfo::TYPE_NAVIGATION_BAR_PANEL);
            primaryDisplayOnly = true;
        }
    }
    ....

    if (primaryDisplayOnly) {
        layer->setPrimaryDisplayOnly();
    }
}

可以看到当windowType == 441731的时候,primaryDisplayOnly被设置为true,继续跟踪441731,注释上已经说明441731是SurfaceControl 中的 WINDOW_TYPE_DONT_SCREENSHOT

继续跟踪WINDOW_TYPE_DONT_SCREENSHOT

image.png 可以看到,当窗口privateFlags包含PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY时,windowType会设置为WINDOW_TYPE_DONT_SCREENSHOT,而createSurfaceLocked是在wms添加窗口时调用的

image.png

WindowManager.LayoutParams params = getWindow().getAttributes();
params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
getWindow().setAttributes(params);

添加对策如下,尝试之后截图发现layer未出现。当privateFlags只有系统应用能调用到,Android Studio SDK不支持该属性更改。